Playing with HTTP/2

HTTP/2 is a new major revision of HTTP protocol. It's designed for low-latency transport of content over the web. In this article I will show you how to set up a sample web application (with Jetty), how to analyze HTTP/2 traffic (with TLS decryption) and how to simulate high latency to see the difference between HTTP/1.1 and HTTP/2 in practice. I'm not going to explain the rationale behind HTTP/2 here, so if you're not familiar with it, I suggest reading RFC 7540 and/or "High Performance Browser Networking" by Ilya Grigorik.

Setting up Jetty

We will use Jetty 9.3 which supports HTTP/2 by default. Setting it up with the sample web app is as easy as:

# download and unpack jetty:
wget  http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-distribution/9.3.10.v20160621/jetty-distribution-9.3.10.v20160621.tar.gz

tar -xzf jetty-distribution-9.3.10.v20160621.tar.gz

export JETTY_HOME=$(pwd)/jetty-distribution-9.3.10.v20160621

# prepare demo dir
mkdir demo  
cd demo  
java -jar $JETTY_HOME/start.jar \  
         --add-to-startd=http,https,http2,http2c,deploy

# enable servlets module required for smart push (more info below)
echo "--module=servlets" >> start.d/server.ini

# download demo application
wget http://kaczmarzyk.net/blog/2016/06/ROOT.war -O webapps/ROOT.war

# start Jetty
java -jar $JETTY_HOME/start.jar  

The demo page contains a picture which is built out of 220 small images. You can access it with HTTP/1.1 on http://localhost:8080/ or with HTTP/2 over TLS on https://localhost:8443/ (you will need a modern browser and have to accept a self-signed certificate).

Even with the minimal latency of localhost, the difference in page loading might be clearly visible. Later on I will show you how to simulate higher latency to make it even more explicit.

Capturing and analyzing traffic

We will now capture traffic with tcpdump and then analyze it with Wireshark (the latter could be used for both of these tasks, but it is easier to paste a tcpdump command here). Capturing traffic on lo interface and port 8443 is as easy as:

tcpdump -i lo -s0 -XX -w /tmp/tcpdump.out port 8443  

But since HTTP/2 traffic is over TLS, we need a way to decrypt it. To achieve that, we need the access to browser TLS keys. You can use SSLKEYLOGFILE environment variable to tell Firefox/Chrome to which file to log these keys to:

export SSLKEYLOGFILE=/tmp/keylog.txt  
firefox https://localhost:8443  

After loading the page, you can stop tcpdump with CTRL+C. Then you are ready to analyze the captured packets with Wireshark. I used Wireshark 2.0.4. Older version that was default for my Linux Mint had some problems with the decryption.

First of all, you need to tell Wireshark to interpret traffic on port 8443 as HTTP over TLS. Go to Edit->Preferences->Protocols->HTTP and then add 8443 to "SSL/TLS Ports" section.

Then, choose File->Open and then select the /tmp/tcpdump.out file. To decrypt the TLS session, choose Edit->Preferences->Protocols->SSL and set "(Pre)-Master-Secret log filename" to /tmp/keylog.txt.

After that, you should be able to see HTTP/2 streams exchanged between the browser and the server:
Captured HTTP/2 traffic

Jetty's "smart push"

The sample web application has Jetty's "smart push" enabled. Jetty analyzes incoming requests and builds asset dependency tree based on Referer HTTP headers. On subsequent page loads, it uses HTTP/2 PUSH_PROMISE to send additional resources such as CSS styles or images even before the browser asks for them.

It requires servlets module to be enabled (we have done it during Jetty setup). Once done, enabling this feature is as easy as adding the following filter definition to ROOT.war/WEB-INF/web.xml:

<filter>  
    <filter-name>PushFilter</filter-name>
    <filter-class>org.eclipse.jetty.servlets.PushCacheFilter</filter-class>
    <async-supported>true</async-supported>
</filter>  
<filter-mapping>  
    <filter-name>PushFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>  

You can remove it to see how it affects the page load.

Adding latency

To make the difference between HTTP/1.1 and HTTP/2 even more visible, you can simulate high latency on lo interface. It is easy to set up with tc command (available on most of Linux distributions):

sudo tc qdisc add dev lo root handle 1:0 netem delay 400ms  

The command above adds 400ms latency. With such setting, the page load on my machine was ~4s for HTTP/2 and ~30s for HTTP/1.1. You can play with tc to see that the difference is even larger for higher latency values.

When you are done with the experiments, you can disable the delay with the following command:

sudo tc qdisc del dev lo root  

Summary

I believe that HTTP/2 is a game changer for the web development. However, I think that it's always essential to understand your application environment (e.g. the expected latency) and to test the features to fully understand them. In this short article I showed you some basic tools to run your own benchmarks. I hope you will find it helpful!


comments powered by Disqus