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:
req.httpVersion = "2.0"
, what do I do about it?server.on("stream", ()=>{ ... })
), so I should just ignore it in the HTTP/1x handler?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);
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...
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:
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.