Daniela Matos de Carvalho

November 06, 2017

HTTP/2 in Node.js core

HTTP/2 is starting to be used more and more (it jumped from 11% in the beginning of this year to 18% total usage on the web). If you recall our last blogposts on HTTP/2, such as HTTP/2: a look into the future of the web, Alternatives to HTTP/2 or Optimize with Server Push and Service Workers, you may remember some of the details in the HTTP/2 protocol and the differences from version one. Check them out if you have not, as we are not going into RFC details in this blogpost.

In our latest blogposts, we mentioned that a version of HTTP/2 was starting to be implemented under the nodejs/http2 repository, but it was not decided at the time if it was going to be merged into the Node.js core modules or if it would be an external module. The main reason behind having a new implementation was that other implementations do not fully respect the HTTP/2 RFC.

Eventually, a pull request was submitted to add it to core. It was merged and included as experimental under a flag (--expose-http2) in August (version 8.4.0).

After the Twitter announcement saying that the flag was about to be removed in 8.x LTS release, Node.js eventually got rid of the flag in version 8.8.0 (Oct, 24). HTTP/2 is still considered to be experimental and despite the fact it is still not 100% finished, you can already start experimenting with it and check the HTTP/2 API documentation.

Playing around

Creating a HTTP/2 server shouldn't be that difficult. In fact if you already have experience with Node.js it should be quite similar to what you are used to. Check out the following example:

const http2 = require('http2'),
  fs = require('fs')

const options = {
  key: fs.readFileSync('keys/server.key'),
  cert: fs.readFileSync('keys/server.crt'),
}

const server = http2.createSecureServer(options)
server.on('stream', (stream, requestHeaders) => {
  stream.respond({
    'content-type': 'text/html',
    ':status': 200,
  })
  stream.end('<h1>Hi, and welcome to YLD blog!</h1>')
})
server.listen(3000)

The main differences are that we are requiring 'http2' and sending credential options to http2.createSecureServer function. Browsers only implement HTTP/2 under HTTPS, be sure you're setting up a TLS connection.

You probably noticed that we're using streams everywhere. However, there's a Compatibility API that allows easy migrations from HTTP/1 to HTTP/2. This is how you can set up request and response handlers as you are used to:

// ...

const onRequestHandler = (req, res) => {
  const {
    socket: { alpnProtocol },
  } = req.httpVersion === '2.0' ? req.stream.session : req
  res.writeHead(
    200,
    `Hi, and welcome to YLD blog! The server you are talking with supports ${alpnProtocol} `
  )
}

const server = http2.createSecureServer(options, onRequestHandler).listen(3000)

Request and response handlers will work as expected, despite the fact they already have HTTP/2 extra information such as if the connection is using ALPN (used in HTTP/2 only).

One of the most interesting features in HTTP/2 is server push and it is quite simple to push files. In the next example we can see that, every time the index route is requested, a CSS file is pushed:

const onRequestHandler = (req, res) => {
  const currentUrl = url.parse(req.url);

  if (currentUrl.pathname === '/') {
    const cssFile = {
      path: '/style.css',
      filePath: './style.css',
      headers: {
        'content-type': 'text/css'
      }
    };
    pushAsset(res.stream, cssFile);

    // ...

and you could implement pushAsset as follows:

const pushAsset = (stream, file) => {
  const filePath = path.join(__dirname, file.filePath)
  stream.pushStream({ [HTTP2_HEADER_PATH]: file.path }, (pushStream) => {
    pushStream.respondWithFile(filePath, file.headers)
  })
}

In fact the Node.js API lets you push the file using respondWithFile or the file descriptor with respondWithFD. file.path is quite relevant here because it will be the path that is going to be requested. e.g. if the file is requested by window in /styles/style.css the path will need to be adapted accordingly. In our case it's just /style.css. Note that we can push as many files as we want and you can push what you think is relevant for the index page. Check out the complete example.

You can also test around with Server Push and Service Workers like we did in Optimize with Server Push and Service Workers. Check out an example using the HTTP/2 Node.js core implementation.

Frameworks and expectations, stay tuned!

The bad new about this is that frameworks such as Express or Hapi.js don't yet support HTTP/2. The good news is that this is about to be implemented and it should be quite similar to the implementation used for spdy or other old HTTP/2 modules for Node.js.

Check out the following threads if you want to be updated on this:

Express:

Hapi.js:

Want to learn more?

Daniela in EmpireJS conference in NYC presenting HTTP/2 with music

I gave a talk at EmpireConf about this topic on October, 13th. If you're interested on this and if you want to learn more about HTTP/2, check my talk The rise of HTTP/2, where I explain HTTP/2 with the metaphor of an orchestra! You can also find code examples in github.com/sericaia/http2-examples-empireconf repository.

Originally published at blog.yld.io on November 6, 2017 by Daniela Matos de Carvalho (@sericaia on Twitter/Github)