Search code examples
node.jsexpresshttpservernginx-reverse-proxy

Why does a NodeJS http server close socket on timeout without response?


Given a NodeJS http server with a timeout of 10s:

const httpServer = require('http').createServer(app);
httpServer.timeout = 10 * 1000;

On timeout, Postman shows this without any response code:

Error: socket hang up
Warning: This request did not get sent completely and might not have all the required system headers

If the NodeJS server is behind an nginx reverse proxy, nginx returns a 502 response (upstream prematurely closed connection while reading response header from upstream). But here it is just NodeJS/express running on localhost. Still one would expect a proper http response.

According to this answer, this is expected behavior, the socket is simply destroyed.

In an architecture with an nginx reverse proxy, is it usual that the server just destroys the socket without sending a timeout response to the proxy?


Solution

  • You're setting the socket timeout when you're setting the http server timeout. The socket timeout prevents abuse from clients that might want to hang on to your connection to DOS you. It has other benefits like ensuring a certain level of service (though these are often more important when you're a client).

    The reason it uses a socket timeout instead of sending a 408 status code (Request Timeout) is because the status code might have already been sent for a successful message.

    If you want to implement a response timeout on your backend and handle it gracefully, you can timeout the response yourself. Note, you should likely respond with a 408 instead. 502 is for gateways like http proxies (nginx) to indicate that a downstream connection failed.

    Here's a simple strawman implementation of handling that.

    const httpServer = require('http').createServer((req, res) => {
        setTimeout(()=>{
            res.statusCode = 200;
            res.statusMessage = "Ok";
            res.end("Done"); // I'm never called because the timeout will be called instead;
        }, 10000)
    });
    
    httpServer.on('request', (req, res) => {
        setTimeout(()=>{
            res.statusCode = 408;
            res.statusMessage = 'Request Timeout';
            res.end();
        }, 1000)
    });
    
    httpServer.listen(8080);