HTTP/3: A QUIC Update

Software Development Engineer

Trivikram Kamat

@trivikram

@trivikr

#NodeJSInteractive

#http3

#quic

Tree

Weak

Rum

+

+

Tri

vik

ram

.

.

What's my name again?

My history with Node.js

  • Have been using Node.js for 4 years

  • Started contributing to Node.js core in Oct 2017

    • helped make HTTP/2 stable

  • Became Node.js core collaborator in March 2018

  • Organized and mentored four Code+Learn events

What are we going to cover?

  • What's HTTP/1.1, and why HTTP/2 was required

  • What's HTTP/2, and why HTTP/3 was required

  • What's HTTP/3, and 

  • We'll see some sample code!

when is it coming?

James Snell

is a Node.js TSC member, who is heading the work on implementing QUIC

Big Thanks to sponsors:

Tatsuhiro Tsujikawa

A big thank you to tatsuhiro-san for his amazing work on ngtcp2

@tatsuhiro-t

HTTP/0.9

Published: 1991

The one line protocol

GET /mypage.html
<HTML>
  A very simple HTML page
</HTML>

HTTP/1.0

Published: May 1996

Building extensibility

GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)
200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML> 
A page with an image
  <IMG src="/myimage.gif">
</HTML>

Versioning information

Browser information

Status code

Headers

<resource>

HTTP/1.1

Published: June 1999

The standardized protocol

GET /en-US/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/en-US/docs/Glossary/Simple_header

HTTP/1.1 improvements

  • Persistent connection: using a single TCP connection to send and receive multiple HTTP requests/responses (serial requests)

  • Pipelining: send a second request before the answer for the first one is fully transmitted, lowering the latency for the communication

  • Chunked transfer encoding: streaming data transfer where stream is divided into a series of non-overlapping "chunks"

  • Additional Cache Control mechanisms

  • Content negotiation

Issue #1: Three roundtrips per request

Issue #2: Multiple TCP+TLS connections are created for concurrent requests

http/1.1 server

const https = require("https");
const fs = require("fs");

const options = {
  key: fs.readFileSync("ssl/localhost.key"),
  cert: fs.readFileSync("ssl/localhost.cert")
};

https
  .createServer(options, (req, res) => {
    if (req.url === "/") {
      fs.createReadStream("./files/index.html").pipe(res);
    } else if (req.url === "/style.css") {
      fs.createReadStream("./files/style.css").pipe(res);
    } else if (req.url === "/script.js") {
      fs.createReadStream("./files/script.js").pipe(res);
    } else if (req.url === "/globe.png") {
      fs.createReadStream("./files/globe.png").pipe(res);
    }
  })
  .listen(3000);

http/1.1 server

const https = require("https");
const fs = require("fs");

const options = {
  key: fs.readFileSync("ssl/localhost.key"),
  cert: fs.readFileSync("ssl/localhost.cert")
};

https
  .createServer(options, (req, res) => {
    if (req.url === "/") {
      fs.createReadStream(`./files/index.html`).pipe(res);
    } else {
      // regular expression for filename requested
      const re = /\/(\w+)*/;
      const filename = req.url.replace(re, "$1");
      fs.createReadStream(`./files/${filename}`).pipe(res);
    }
  })
  .listen(3000);

index.html

<html>
  <head>
    <link rel="stylesheet" type="text/css"
          href="./style.css" />
  </head>
  <body>
    <img id="image" src="./globe.png" />
    <div>
      <div>Hello</div>
      <div id="name">World</div>
    </div>
    <script type="text/javascript" 
            src="./script.js"></script>
  </body>
</html>

style.css

body {
  display: flex;
  align-items: center;
}

div {
  font-size: 100px;
}

script.js

setTimeout(() => {
  document.getElementById("name")
    .innerHTML = "#NodeJSInteractive!";
  document.getElementById("image")
    .src = "./njsi.png";
}, 1000);

globe.png

Load the webpage

examine TCP connections with netstat

Issues with HTTP/1.1

  • Three round-trips per request

  • Multiple TCP+TLS connections for concurrent requests

HTTP/2

Published: May 2015

http/2 server

const http2 = require("http2");
const fs = require("fs");

const options = {
  key: fs.readFileSync("ssl/localhost.key"),
  cert: fs.readFileSync("ssl/localhost.cert")
};

const server = http2.createSecureServer(options).listen(3000);
server.on("stream", (stream, headers) => {
  if (headers[":path"] === "/") {
    stream.respondWithFile("./files/index.html");
  } else {
    // regular expression for filename requested
    const re = /\/(\w+)*/;
    const filename = headers[":path"].replace(re, "$1");
    stream.respondWithFile(`./files/${filename}`);
  }
});

Load the webpage

examine TCP connections with netstat

Benefits of HTTP/2

  • Multiplexing and concurrency: different HTTP requests onto the same TCP connection

  • Stream dependencies: client can indicate to server which dependencies are important

  • HPACK Header compression: reduces the length of header field encodings by exploiting the redundancy

  • Server push: server can send resources which client has not requested yet

Issues with HTTP/2

  • HPACK is stateful: the encoding and decoding tables have to be maintained by an endpoint

  • High resource consumption in the middle tier

  • Not easily routable along the network: works well is Node.js server communicates with other Node.js server, but not if there's NGINX in between

  • TCP head-of-line blocking

TCP head-of-line blocking

When a single TCP packet is lost or corrupted, all subsequent packets are blocked until the lost one can be successfully transmitted.

With HTTP/2, multiple requests and responses are carried over a single TCP connection. So all those requests/responses will be blocked in case of a dropped TCP packet.

With HTTP/1.1, if that happens, you're only blocking one request

This effect is most significant on high-latency (long-distance), and low-reliability (mobile) connections

TCP head-of-line blocking

Chain metaphor

TCP connection

CSS packet

JS packet

HTTP/3 over QUIC

draft-24 as of Dec 2019

When setting up multiple streams over QUIC connection, they are treated independently so that if any packet goes missing for one of the streams, only that stream has to pause and wait for the missing packet to get retransmitted.

CSS stream

JS stream

QUIC is built on UDP

Speaking of UDP...

I could tell you a joke

BUT...

you won't get it 😉

Let me still give it a try...

UDP

packet

bar

walks

into

a

QUIC adds the following to UDP

Error Handling

Acknowledgements

Flow Control

Bidirectional/Unidirectional streams

Packet Sequencing

Built in encryption (TLS 1.3)

QUIC also reduces roundtrips

Before we write HTTP/3 server

QUIC !== HTTP/3

HTTP/3 is an application protocol that uses QUIC as a transport protocol

HTTP/3

QUIC

UDP

IP

http/3 server

const quic = require("quic");
const fs = require("fs");

const options = {
  key: fs.readFileSync("ssl/localhost.key"),
  cert: fs.readFileSync("ssl/localhost.cert")
};

const server = quic.createSocket({ port: 3000 });
server.listen(options);
server.on("session", session => {
  session.on("stream", (stream, headers) => {
    if (headers[":path"] === "/") {
      stream.respondWithFile("./files/index.html");
    } else {
      // regular expression for filename requested
      const re = /\/(\w+)*/;
      const filename = headers[":path"].replace(re, "$1");
      stream.respondWithFile(`./files/${filename}`);
    }
  });
});

Load the webpage

HTTP/3 over QUIC in Node.js is in early stages of development

You can help build it!

nodejs/quic progress

project board

How can I write unit tests?

git clone git@github.com:trivikr/quic.git

cd quic

./configure --experimental-quic --coverage

make -j4 coverage

Current state of JavaScript tests

Current state of C++ tests

HTTP/3 IETF draft

Example webpage on QUIC

Demo

Summary

  • In HTTP/1.1, multiple TCP+TLS connections were required for concurrent requests

  • HTTP/2 added multiplexing in which multiple HTTP requests were sent onto the same TCP connection

  • TCP head-of-line blocking: the entire TCP connection is brought to halt if a single TCP packet is lost.

  • HTTP/3 over QUIC treats each stream independently, so if any packet goes missing in a stream - only that stream is affected

Thank you for listening!

Trivikram Kamat

@trivikram

@trivikr

HTTP/3: A QUIC update

By Trivikram Kamat

HTTP/3: A QUIC update

Slides for Node+JS Interactive talk from December 2019 https://njsi2019.sched.com/event/UGPM

  • 1,023