How to create a zero dependency HTTP/2 static file server with Node.js (with examples)
HTTP/2 has been supported by the latest versions of the most popular browsers, including Google Chrome, Firefox, Safari and Microsoft Edge for quite sometime now. Websites delivered using HTTP/2 enjoys a wide range of new features including -
- fully multiplexed connections: all requests and responses to a domain are fully multiplexed via a single connection, making best use of available bandwidth,
- header compression: repeated headers are compressed with HPACK compression so that they are not resent with every request and response,
- PUSH: resources can be pre-emptively pushed by the server to the client, speeding up page load times.
Node.js just launched support (v8.8.1) for HTTP/2 as part of their core. In this post, we will create a simple HTTP/2 server to serve static files and then demonstrate some cool features like HTTP/2 PUSH.
Step 0: Install Node.js v8.8.1
We will need at least Node.js v8.7.0 to be installed, you can download the latest version here.
Note - HTTP/2 is still considered as an experimental feature (Stability 0) even in the latest version of Node. It is not recommended to run production workloads using this yet, since the API might change. That being said, we will keep this blog post up to date as the API changes.
Step 1: Get an SSL certificate
Even though the HTTP/2 spec does not mandate HTTPS, browsers have decided that they will only support HTTP/2 on a HTTPS connection. This would also mitigate interference from older proxies which may not understand the newer protocol. If you already have HTTPS enabled, you can skip this step. Otherwise, you can generate a free certificate with letsencrypt if you want to try this out on a hosted domain. For your local server, a self-signed certificate will work. You can find how to setup a self-signed certificate here.
Step 2: Building a Static File server
Let us start with a simple server which just serves static files. Note that if you are using node.js 8.7.0, you need to run node with the
We will be listening to the
stream event and responding to it with the corresponding file from the server root (public, in this case) using the
respondWithFile API. We are using the
mime-type module to look up the correct mime type to send along with the response.
Server Push Example
Now that we have a simple HTTP/2 server running, lets try to use one of the new features in HTTP/2 - HTTP/2 PUSH. This can lead to significant performance improvements in high latency environments, if done correctly.
We are loading a simple HTML file pointing to style.css which references to our font file. The request to the font file is only made after the CSS file is discovered in the HTML, downloaded and then parsed. This is how the waterfall would have usually looked like.
You can initiate a new PUSH with the
pushStream API. Since we know that the browser is going to be requesting the font file in the future, we can PUSH the font file as soon as the server receives the request for the HTML file. When the actual request for the font file takes place, it is claimed from the PUSH cache, instead of making a network request then.
You can check out the full API for HTTP/2 in node.js here.
Using the compatibility API
Node.js also provides a compatibility layer to make it easier to get started with HTTP/2 on your existing web application.
You simply need to replace your existing
https module with the new
http2 module and things should work out of the box. For example, this is a simple HTTP/2 server written written with the HTTP1 version of the API. The same socket can answer to both HTTPS and HTTP/2 requests. Make sure you pass in the
allowHTTP1 flag when creating the server. This would downgrade browsers which don’t support HTTP/2 back to a HTTP/1.1 connection.
It would still take time before full-blown frameworks like Express catch up to ensure that their API doesn’t break when used with HTTP/2, but with HTTP/2 server and client adoption catching up rapidly, we should get there soon!