H3, meet Node.js

Early HTTP/3 for Node.js with performance metrics

uNetworking AB
3 min readMay 15, 2022

Nightly builds of uWebSockets.js now ship with experimental HTTP/3 support*. Let’s take it for a spin!

npm install uNetworking/uWebSockets.js#binaries

Just like you’re used to, server endpoints are defined via an easy-to-use URL router with pattern matching and wildcards:

const uWS = require('uWebSockets.js');uWS.H3App({
.key_file_name: './key.pem',
.cert_file_name: './cert.pem'
}).get('/*', (res, req) => {
res.end('H3llo World from Node.js!');
}).listen(9004, (listenSocket) => {
if (listenSocket) {
console.log('Listening to port 9004!');
}
});

Reaching the server with quiche-client is easy (as of now, you need an IPv6 localhost address):

quiche-client --no-verify https://[::1]:9004/> H3llo World from Node.js!

Alright, sweet! So what about performance?

Let’s begin by creating a reference point by running the most minimal Node.js HTTP/1.1 server without any routing or encryption, stressed at 100% CPU-time on a single core:

const http = require('http');const requestListener = function (req, res) {
res.end('Hello H1 from (built-in) Node.js!');
}
const server = http.createServer(requestListener); server.listen(8080);

On this particular machine, we get 18k requests per second at 100% CPU-time, one core. Now let’s compare that with H3 from uWebSockets.js.

At 100% CPU-time on a single core we can serve up to 291k requests per second — that’s encrypted and URL routed! That’s a 16x speed-up!

How can it be so much faster? Partly because HTTP/3 has proper parallelism of requests (“pipelining”). Pipelining in HTTP/1.1 is considered broken and disabled by all major web browsers, but for completeness sake, we’ll enable HTTP/1.1 pipelining in this benchmark and run it again!

All things equal, we now get 28k requests per second with Node.js built-in HTTP/1.1 server. That’s still a 10x difference with uWebSockets.js HTTP/3 support. And remember; HTTP/3 results are TLS 1.3 encrypted and URL routed!

Where do we go from here?

Clearly, performance numbers are through the roof in comparison with what you get out of the box with Node.js, and if you want the features of HTTP/3 you’re still out of luck with vanilla Node.js as it simply lacks it entirely.

uWebSockets.js now builds highly experimental HTTP/3 but will begin to stabilize this support. A rough plan is to have a stable release within 6 months, and within 1 month we should have a release with the experimental support built and shipped. In the mean time, you can begin to port your apps to the already fast and stable HTTP/1.1 support within uWebSockets.js — moving away from the built-in and sluggish Node.js counterpart.

Doing so, means you’ll be able to swap from uWS.App to uWS.H3App without changing any of your business logic — uWebSockets.js exposes HTTP/3 under the same very interface as it does HTTP/1.1.

Alright, remember this support is currently at proof-of-concept stage and very experimental so don’t be shocked if it blows up in your face the nearest month!

Thanks

[*]: Linux on Intel only; requires an IPv6 localhost address as of now. WIP.

--

--