Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

404 with await ctx.render('404') has application/json content type #646

Closed
niftylettuce opened this issue Jan 19, 2016 · 16 comments
Closed
Milestone

Comments

@niftylettuce
Copy link
Contributor

this is because the body is empty per

if (null == val) {

so the only way to fix is to not only set ctx.throw(404) but to also reset the content type when you're sending a 404 error

we should maybe account for 404 errors here before we unset everything?

@jonathanong
Copy link
Member

what is ctx.render()?

@niftylettuce
Copy link
Contributor Author

for koa@next when I have error middleware like this at the top of my file, I have to explicitly say ctx.type = 'html'; for example... (maybe I'm doing it wrong?) -- this was from code I did the other day

// initialize try/catch error handling right away
// adapted from: https://github.com/koajs/onerror/blob/master/index.js
// https://github.com/koajs/examples/issues/20#issuecomment-31568401
app.use(async (ctx, next) => {

  try {
    await next();
    if (ctx.status === 404)
      ctx.throw(404);
  } catch (err) {

    if (err.name) {
      switch (err.name) {
      case 'ValidationError':
        err.status = 400;
        if (err.errors) {
          let errors = _.map(
            Object.values(err.errors),
            (e) => {
              switch (e.kind) {
              case 'required':
                return `${_.upperFirst(e.path)} is required`;
              default:
                return _.trimEnd(e.message, '.');
              }
            }
          ).join(', ');
          err.message = `${err.message}: ${errors}`;
        }
        break;
      }
    }

    ctx.status = err.status || 500;
    ctx.body = Boom.create(err.status, err.message).output.payload;

    app.emit('error', err, ctx);

    let type = ctx.accepts(['text', 'json', 'html']);

    if (!type) {
      ctx.status = 406;
      ctx.body = Boom.notAcceptable().output.payload;
    }

    switch (type) {
    case 'html':
      if (ctx.status === 404) {
        ctx.type = 'html';
        // https://github.com/koajs/koa/issues/646
        await ctx.render('404');
      } else {
        ctx.flash('error', err.message);
        ctx.redirect('/');
      }
      break;
    case 'json':
      ctx.body = JSON.stringify(ctx.body);
      break;
    default: // case 'text':
      ctx.type = 'text';
      ctx.body = JSON.stringify(ctx.body, null, 2);
      break;
    }

  }

});

@niftylettuce
Copy link
Contributor Author

see how i use ctx.render in that block -- it's my nunjucks rendering via https://github.com/hanai/koa-nunjucks-promise

@niftylettuce
Copy link
Contributor Author

also for reference, Boom is https://github.com/hapijs/boom

@jonathanong
Copy link
Member

yeah i always throw the error and let upstream error handlers handle those pages. i also don't set headers unless i know the status code isn't gonna be an error, which makes things a little more robust, but is more annoying to code.

i also don't do content negotiation anymore :P hard to cache

@niftylettuce
Copy link
Contributor Author

how the heck do we do this then? for example: https://github.com/niftylettuce/ratelimit/blob/master/src/index.js#L67-L74

@jonathanong
Copy link
Member

you'd have to remove the headers yourself. :(

@niftylettuce
Copy link
Contributor Author

how do I do that? @jonathanong very confused here...

@jonathanong
Copy link
Member

this.res._headers = {};

hacks galore!

unless you want to make a PR for #571 :)

@niftylettuce
Copy link
Contributor Author

@jonathanong how do we override that onerror, via app.onerror = fn? is there any examples of someone doing this?

@jonathanong
Copy link
Member

just doing app.context.onerror = should work, but onerror should only be used when extremely necessary.

instead, this and similar middleware should be relying on an upstream error handler like:

app.use(function* (next) {
  try {
    yield next
  } catch (err) {
    // handle error
    this.status = err.status || 500
    this.body = { message: err.message }
  }
})

but then you have to do special cases for middleware like that rate limit one... which is why it's better just to have a public API

@jonathanong
Copy link
Member

oh shit. i read the code wrong. i thought you were setting that body after await next but you're early returning.

what's the issue?

@dead-horse dead-horse added this to the 2.0.0 milestone Jan 20, 2016
@niftylettuce
Copy link
Contributor Author

the issue is that headers are removed from the request on an error, when I don't want them to be

@jonathanong
Copy link
Member

only if onerror handles it. if you make your own middleware, you can do wahtever you want.

@PlasmaPower
Copy link
Contributor

Does c826467 resolve this?

@jonathanong
Copy link
Member

yes c826467 should resolve this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants