Search code examples
node.jsrestifynode-streams

Handling of conditionally unconsumed http.IncomingMessage


I want to forward an upstream http.IncomingMessage to a client via a restify server. This is what I came up till now. It provides the forwarding capability. However I assume that this could cause a memory leak:

var server = restify.createServer()

server.get('/test', function(req, res, next) {
    var upstreamReq = createUpstreamReq() // just creates a http.ClientRequest

    upstreamReq.on('response', function(upstreamRes) {
        if (upstreamRes.statusCode === 404) {
            // (1) I guess this leaks the upstreamRes body ?
            return next(new restify.errors.NotFoundError())
        }
        if (upstreamRes.statusCode !== 200) {
            // (2) is there a better way than pipeing the response to /dev/null?
            // I guess the advantage of this approach is that we can reuse the connection (not closed) ?
            upstreamRes.pipe(fs.createWriteStream('/dev/null'))
            return next(new restify.errors.InternalServerError())
        }
        res.setHeader('Content-Type', upstreamRes.header('Content-Type'))
        res.setHeader('Content-Length', upstreamRes.header('Content-Length'))
        upstreamRes.pipe(res)
        return next()
    })

    upstreamReq.end()
})
  • I assume that in the case of a upstream 404 this code leaks the upstreamRes body (1) because it is never consumed (no pipe(somewhere))?
  • One obvious solution (2) that shouldn't leak the upstreamRes body is to pipe it to /dev/null. Is there an alternative/better solution for this problem?

Solution

  • It seems that I skipped over an important section in the documentation about http.ClientRequest:

    If no 'response' handler is added, then the response will be entirely discarded. However, if you add a 'response' event handler, then you must consume the data from the response object, either by calling response.read() whenever there is a 'readable' event, or by adding a 'data' handler, or by calling the .resume() method. Until the data is consumed, the 'end' event will not fire. Also, until the data is read it will consume memory that can eventually lead to a 'process out of memory' error.

    https://nodejs.org/api/http.html#http_class_http_clientrequest

    So the correct answer seems to be:

    • You need to consume the response (a http.IncomingMessage) for each http.ClientRequest
    • If you aren't interested in the returned data the recommended approach is to call upstreamRes.resume(). This starts the flow of data even if no consumer is attached.