Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Koa's `ctx.status` not getting sent to client

Here is my simple route:

router.post('/getFile', async (ctx) => {
  const fileName = `${ctx.request.body.file}.pdf`;
  const file = fs.createReadStream(fileName); // This file might not exist.

  file.on('error', (err) => {
    ctx.response.status = 500; // This status code doesn't make it to client when there's an error.
  });

  ctx.response.type = 'application/pdf';
  ctx.response.body = file;
});

And here is my client code:

async function main() {
  const request = {
    method: 'POST',
    body: JSON.stringify({ file: 'bad-file-name' }),
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/pdf'
    }
  };

  const response = await fetch('/getFile', request);

  if (!response.ok) {
    console.log(response.status); // This is always 404 when I give a bad file name, even though I set it to 500 above. Why?
  }
}

Everything is fine when I send a proper file name, but why is the response status code always 404 even though I set it to 500 in my server code during error? Could it be that the response is already finished sending by the time my code reaches ctx.response.body = ... in which case the code in the .on('error') isn't doing anything?

Any help would be appreciated.

like image 726
Saad Avatar asked May 18 '17 05:05

Saad


2 Answers

I think you need try something like this:

router.post('/getFile', async (ctx) => {
  const fileName = `${ctx.request.body.file}.pdf`;
  const file = fs.createReadStream(fileName); // This file might not exist.

  file.on('error', (err) => {
    ctx.response.status = 500; // This status code doesn't make it to client when there's an error.
  });

  file.on('close', () => {
    ctx.response.type = 'application/pdf';
    ctx.response.body = file;
  });
});
like image 156
Sergaros Avatar answered Oct 04 '22 08:10

Sergaros


Looking at the Koa code, it has specific handling for ENOENT (which is the error that gets thrown when a file doesn't exist):

// ENOENT support
if ('ENOENT' == err.code) err.status = 404;

From what I can see, you can't change which status code Koa will send back (and, to be fair, sending back a 404 for non-existent files does make sense).

However, there's a quick hack: because Koa explicitly checks for err.code matching ENOENT, if you change that code, you can trick Koa into returning another status code:

file.on('error', err => {
  err.code   = 'ENOEXIST'; // a made-up code
  err.status = 500;
});

Alternatively, you can first check (using fs.exists(), fs.access() or fs.stat()) to see if the file exists before creating the read stream.

like image 29
robertklep Avatar answered Oct 04 '22 06:10

robertklep