Search code examples
node.jshttphttp-caching

How to set cache headers only if there isn't an error in sending the response in Node.js


I am doing this essentially:

app.get('/:id.:ext', (req, res) => {
  const remote = bucket.file(`images/${req.params.id}.${req.params.ext}`)
  // if (isProd)
  //   res.set('Cache-Control', 'public, max-age=604800')
  remote.createReadStream({ validation: false })
    .on('error', error => {
      console.log(error)
      res.send(`data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7`)
    })
    .pipe(res)
    .on('end', () => {
      res.set('Cache-Control', 'public, max-age=86400')
    })
})
  • When there is an error, I just return an empty image. But in this case, I don't want to set the cache headers because I don't want the blank image to be cached.
  • So I try setting the cache headers after the response is sent, but they don't go through.
  • If I set the cache headers before piping the response, I cache the error in case of error.

Wondering how to get around this.


Solution

  • You can't do what you're trying to do the way you're trying to do it. The sequence of an http response is to send the http headers, then start sending the respond body. So if you're piping a response body (like you are), you HAVE to send the headers first. You can't start sending the response body, then changing your mind about the headers. They've already been sent.

    And, you can't send headers after the http library has already starting sending the respond body. Starting to send the response body writes out the currently stored state of the http headers that go with this response and then starts writing the response.

    As best I know, the only way to deal with an error that occurs in the middle of sending the http response body is to close the http connection prematurely. The client will see the socket close without seeing the end of the http response and will understand that it got a terminated, unfinished response. You don't get a chance to send another response at that point. Error handling for that case will need to be client-side in order to decide what to do.

    Another option is to prefetch all the data you want to send to the client BEFORE you send anything. This allows you the most chances to determine if anything is going to cause an error before you've started sending the http response and then you can craft the entire response to match your error condition. You obviously can't use something like .pipe(res) if you're going to do that. Instead, you've have to load the entire response into memory (or at least a chunk of the response if you're going to send it in chunks) and only when that has been successfully pre-flighted and loaded and ready to go do you start sending the response.

    Also, another way to avoid getting an error image cached is to do a 301 (temporary redirect) to the error image URL rather than return it as the response to the original request. Then, when the browser loads that redirected URL and gets the image, it will not cache it as the original URL.