Search code examples
node.jshttpshttp2alpn

HTTP/2 compatibility API, how to handle it


While initially I had settled for creating an HTTP/2 only server, I found the compatibility API to be a good option to support clients without, or with unknown, HTTP/2 support (fetch in react-native?).

However, I am struggling to understand how to deal with incoming requests on the HTTP/1x handler, that report req.httpVersion === "2.0". The following code is an extract from the ALPN negotiation section in the nodejs documentation:

function onRequest(req, res) {
  // Detects if it is a HTTPS request or HTTP/2
  const { socket: { alpnProtocol } } = req.httpVersion === '2.0' ?
    req.stream.session : req;

  // HERE: OK, say we have  req.httpVersion === '2.0'
  // Now, what?

  res.writeHead(200, { 'content-type': 'application/json' });
  res.end(JSON.stringify({
    alpnProtocol,
    httpVersion: req.httpVersion
  }));
}

First of all, I do not understand what the code above is supposed to do. Like, I know what it does, but there isn't anything particular to it - seems just generic HTTP/1x request handler, that sends some JSON back, and nothing more. Did I miss anything?

Other than that:

  • once the HTTP/1x handler reports req.httpVersion = "2.0", what do I do about it?
    Will it also be handled by the HTTP/2 handler (server.on("stream", ()=>{ ... })), so I should just ignore it in the HTTP/1x handler?
  • If it won't be handled by the HTTP/2 handler automatically, can -and should- I just forward the request to the HTTP/2 handler manually?
function http2handler(stream, headers){ ... }

function http1xhandler(req, res) {
  if (req.httpVersion === "2.0") {
    // handle as HTTP/2
    return http2handler(req.stream, req.headers);
  }

  // handle as HTTP/1x ...
}

const server = createSecureServer(
  { cert, key, allowHTTP1: true },
  http1xhandler
).listen(4443);

server.on("stream", http2handler);
  • If none of the above, does the response JSON in the example play any role into indicating to the client that it should switch to HTTP/2? And if so, what exactly should the client do?

Sorry if this may be a dumb question, the nodejs documentation is awful... it seems like every bit of their documentation must end in a cliffhanger...


Solution

  • After fiddling with some code and reading more articles and answers, I think I have finally understood what goes on. I would however appreciate any feedback if anything I say here is wrong.

    The HTTP/2 compatibility API provides a common request handler for both HTTP/1x and HTTP/2 requests. As indicated by the ALPN negotiation documentation, the developer should limit the response to the public HTTP/1x API - until an HTTP/2 request has been identified. You can check the req.httpVersion to identify these, and branch your code as needed.

    If you specify both a compatibility request handler (callback function on createSecureServer), and an on("stream", ...) event handler (which is HTTP/2 only), the server behavior is:

    • for HTTP/1x requests, only the compatibility request handler will be called
    • for HTTP/2 requests, both the the compatibility, and the on("stream", ...) request handlers will be called - but only one of the handlers should ever respond to the request. If you try to respond to the request from both handlers you will get:
      Error [ERR_HTTP2_HEADERS_SENT]: Response has already been initiated.

    The way that I have chosen to deal with these is to create independent HTTP/1x and HTTP/2 handlers, each dealing with it's own cake. The compatiblity handler should ignore HTTP/2 requests. In code:

    function http1xhandler(req, res) {
      if (req.httpVersion === "2.0") {
        // Ignore HTTP/2 requests, will be handled by the on("stream", ...) event handler
        // Or, you can answer the HTTP/2 request here, using HTTP/2 features as well
        return;
      }
    
      // Handle HTTP/1x request
      res.writeHead(200, { 'content-type': 'text/plain' });
      res.end("Hello HTTP/1x!");
    }
    
    const server = createSecureServer(
      { cert, key, allowHTTP1: true },
      http1xhandler
    ).listen(4443);
    
    // Do not use the following if you are handling
    // HTTP/2 in the HTTP/1x compatibility callback
    
    function http2handler(stream, headers){
      stream.respond({
          'content-type': 'text/plain; charset=utf-8',
          ':status': 200
        });
      stream.end("Hello HTTP/2!");
    }
    
    server.on("stream", http2handler);
    

    Hopefully this will be useful to someone else looking into these APIs.