HTTP/2: de wachttijd is over!

De inkt van de final spec van het HTTP/2 protocol is net een jaar droog. Hoogste tijd om de opvolger van HTTP/1.1 eens aan de tand te voelen, en te zien wat dit betekent voor ontwikkelaars!

HTTP/2 brengt vernieuwing in het fundament van het web. Niet zo fundamenteel als IPv6, en zeker niet met zo’n lang migratiepad. Je wist het vast niet, maar je hebt al maanden, zo niet jaren, met (een voorloper van) HTTP/2 gebrowsed!

Is HTTP/2 nodig? Jazeker! Al enige jaren geleden stelden grootverbruikers van het web – met Google als aanvoerder – vast dat HTTP/1.x, de ‘motor’ onder het web, tekenen van slijtage begon te vertonen op het gebied van performance en efficiëntie.

Welke problemen zagen zij?

  • Hoge bandbreedte
  • TCP slow start
  • Head of Line Blocking
  • Veel TCP connecties tegelijk open
  • time is money

Deze vijf problemen worden hieronder verder uitgediept.

HTTP/1.x gebruikt onnodig veel bandbreedte:

  • HTTP/1.x is een plaintext protocol. Handig voor debuggen, maar niet erg efficiënt wat betreft het aantal Bytes dat over de lijn gaat.
  • HTTP/1.x is een chatty protocol. Vooral de headers bevatten erg veel redundantie. Ze kunnen 200 Bytes tot zo’n 2kB groot zijn, informatie die vaak bij elk request opnieuw ongewijzigd wordt verstuurd.

TCP kent een mechanisme dat slow start heet: als de connectie net gestart is worden er kleine pakketten gestuurd, steeds groter wordend totdat ofwel een ingesteld maximum bereikt is, ofwel het pakket verloren gaat. In dat laatste geval begint de slow start weer van voren af aan. Gevolg is dat vaak alleen al voor de headers van een request vaak wel 7 of 8 roundtrips op IP-pakketniveau nodig zijn! Pas als die allemaal binnen zijn kan een server beginnen met verwerken van het request. Zie figuur 1 voor een voorbeeld van slow start.

Is HTTP/2 nodig? Jazeker! Al enige jaren geleden stelden grootverbruikers van het web – met Google als aanvoerder – vast dat HTTP/1.x, de ‘motor’ onder het web, tekenen van slijtage begon te vertonen op het gebied van performance en efficiëntie.

Welke problemen zagen zij?

  • Hoge bandbreedte
  • TCP slow start
  • Head of Line Blocking
  • Veel TCP connecties tegelijk open
  • time is money

Deze vijf problemen worden hieronder verder uitgediept.

HTTP/1.x gebruikt onnodig veel bandbreedte:

  • HTTP/1.x is een plaintext protocol. Handig voor debuggen, maar niet erg efficiënt wat betreft het aantal Bytes dat over de lijn gaat.
  • HTTP/1.x is een chatty protocol. Vooral de headers bevatten erg veel redundantie. Ze kunnen 200 Bytes tot zo’n 2kB groot zijn, informatie die vaak bij elk request opnieuw ongewijzigd wordt verstuurd.

TCP kent een mechanisme dat slow start heet: als de connectie net gestart is worden er kleine pakketten gestuurd, steeds groter wordend totdat ofwel een ingesteld maximum bereikt is, ofwel het pakket verloren gaat. In dat laatste geval begint de slow start weer van voren af aan. Gevolg is dat vaak alleen al voor de headers van een request vaak wel 7 of 8 roundtrips op IP-pakketniveau nodig zijn! Pas als die allemaal binnen zijn kan een server beginnen met verwerken van het request. Zie figuur 1 voor een voorbeeld van slow start.

Vanwege Head of Line Blocking openen browsers meerdere parallelle verbindingen. In RFC 7230 wordt aangeraden dit aantal te beperken; voorheen werd zelfs 2 als maximum genoemd. Elke nieuwe verbinding lijdt natuurlijk wel onder TCP slow start. Daarnaast kost elke verbinding capaciteit op de server; minimaal een TCP connectie, en in iets oudere HTTP servers ook een thread.

In het recente verleden werden ‘download versneller’ plug-ins gebruikt. Wat deze feitelijk deden was vele parallelle verbindingen openen; veel hoger dan de standaard browser limieten van 2 tot 8. Daarmee veroorzaakten ze veel extra load op servers.

Web shops meten duidelijk meer verkopen bij zelfs de kleinste verbetering in page load tijd. Wanneer de problemen hierboven verholpen worden, kan die significant stijgen. Daarnaast is verkleining van de gebruikte bandbreedte een winstpunt; dat verkleint de maandelijkse rekening van de ISP…

‘In den beginne’ bestond een pagina uit enkele resources – HTML, JS, CSS en wat plaatjes. Inmiddels gaat het vaak om een honderdtal resources en vele MegaBytes, alleen al voor de eerste pagina. Dan begint HTTP/1.x echt te ‘kraken’ en page load tijd flink te stijgen.

Om klanten de rijke gebruikerservaring waaraan ze gewend zijn te blijven bieden, en deze nog verder uit te bouwen, zal HTTP/2 een onmisbare schakel worden.

RFC 7540 – waarin het HTTP/2 protocol is vastgelegd – is gepubliceerd op 14 mei 2015. Het is grotendeels gebaseerd op Google’s SPDY/4. Veel browsers en diverse servers ondersteunden dat protocol al. Dat is de reden dat HTTP/2 relatief snel uitgerold kan worden.

Doel van HTTP/2 is het verkleinen van latency, verbeteren van page load tijd en verbeteren van network&server resource-gebruik. Hiervoor is het gebruik van de onderliggende TCP verbinding – het zogenaamde “over the wire” protocol – compleet herzien.

HTTP/2 in een notendop:

  • een binair protocol
  • multiplexed – geen Head of Line Blocking. Een request/response-paar heet een stream en heeft een eigen stream id. Op deze manier kunnen meerdere streams afwisselend pakketjes (‘frames’) sturen. Grote stukken data worden in meerdere frames opgesplitst. Elke stream heeft daarvoor individuele flow control instelling, waarmee back pressure toe te passen is. Een frame benoemt naast zijn stream id ook zijn frame type, bijvoorbeeld, HEADERS of DATA.
  • één persistente verbinding per host is voldoende – TCP slow start is nauwelijks meer een probleem.
  • comprimeert headers om bandbreedte te sparen
  • stelt servers in staat “push” berichten te sturen – de browser zet deze meteen in z’n cache. Server Push is voorlopig de enige functie die toegankelijk is in applicaties.
  • prioriteit en onderlinge afhankelijkheid van requests is een optionele hint naar de server. De browser kan hiermee bijvoorbeeld voorrang vragen voor HTML, of plaatjes pas als laatste laten opleveren. De waarden zijn dynamisch aanpasbaar, bijvoorbeeld als de gebruiker scrolt tijdens het renderen, of naar een andere tab switcht

Id | Frame type

0x0 | DATA

0x1 | HEADERS

0x2 | PRIORITY

0x3 | RST_STREAM

0x4 | SETTINGS

0x5 | PUSH_PROMISE

0x6 | PING

0x7 | GOAWAY

0x8 | WINDOW_UPDATE

0x9 | CONTINUATION

0xa | ALTSVC

Is alles dan helemaal anders? Nee hoor! Voor ontwikkelaars is bijna alles gelijk gebleven aan HTTP/1.x. Het gebruik van HTTP methods (bijvoorbeeld GET en POST), status codes (bijvoorbeeld 200 OK), header velden (bijvoorbeeld Content-Type) & URI’s (bijvoorbeeld https://www.ordina.nl/) blijft bij het oude. Ook de standaard poorten 80 & 443 blijven in gebruik.

Hierboven werden enkele problemen met HTTP/1.x benoemd. Om daar omheen te werken, zijn in de loop der tijd enkele workarounds bedacht. Met HTTP/2 worden die overbodig en sommige gevallen zelfs nadelig; je kunt ze – na volledige migratie – verwijderen uit je applicatie.

Het gaat dan om:

  • domain sharding: statische content wordt over meerdere hosts verspreid. Vaak wordt dit toegepast in combinatie met een CDN: Content Delivery Network. Hiermee wordt om de per-host browserlimiet van 2-8 connecties heen gewerkt. Bij HTTP/2 geeft één persistente connectie vanwege multiplexing al voldoende capaciteit.
  • sprites: het combineren van vele kleine icoontjes in 1 plaatje, waar vervolgens met CSS het juiste icoon weer wordt ‘uitgesneden’. Ook hier geeft de enkele multiplexed verbinding, in combinatie met (header) compressie, voldoende capaciteit om zelfs vele kleine plaatjes allemaal apart op te halen. Daarnaast kan via Server Push het plaatje al opgeleverd worden, nog voordat er om gevraagd wordt.
  • resource inlining & concatenation, ook wel bekend als minification. Wordt vaak toegepast bij Javascript en CSS. Vaak worden meerdere kleine files tot één grote samengevoegd & gecomprimeerd. In sommige gevallen worden ze zelfs inline in HTML geplaatst. Alle noodzaak hiertoe vervalt, door de veel grotere throughput van vele kleine bestanden.

Server Push

Iedereen veert natuurlijk op bij het magische woord ‘push’. HTTP/2 biedt het, en servers ontsluiten dit tevens naar applicaties.

Verwacht er geen wonderen van – HTTP/2 definieert bijvoorbeeld geen Javascript API. Wil je in je frontend code pushberichten afhandelen, dan blijft de keuze Server Sent Events, XHR/Ajax of Websockets. Dat kan natuurlijk wel – transparant – over een HTTP/2 connectie.

Je moet de toepassing van Server Push eerder zoeken in het voorspellend opsturen van statics; bij initiële page load kan de server alle benodigde static content opsturen. Dit kan het aantal round trips, en daarmee de browser render tijd, flink verkleinen.

Jetty past dit transparant toe met een Servlet Filter die de Referrer header inspecteert en daarmee dynamisch een lijst opbouwt van statics ‘die de gebruiker wel nodig zal hebben’.

Arnout Engelen gaat in de volgende editie dieper in op HTTP/2 Server Push.

Support

Caniuse.com noemt een globale browser ondersteuning van bijna 70%. In Nederland is dat bijna 85%. Het gaat dan om (de nieuwste versies van): Chrome, Opera, Firefox, IE11 (Windows 10), Safari (OSX 10.11), Amazon Silk en Edge.

Dat is voor de meeste applicaties voldoende om te veronderstellen dat de gebruiker HTTP/2 tot zijn of haar beschikking heeft.

In principe is HTTP/2 volledig backwards compatible. Als de browser of server – of de tussenliggende proxy of load balancer – geen HTTP/2 spreekt, wordt teruggevallen op HTTP/1.1. Server Push zal dan niet beschikbaar zijn.

Server- en middlewareleveranciers zijn druk bezig om hun software en appliances geschikt te maken voor HTTP/2.

Hierbij een opsomming van de minimale versie van server software die nodig is om HTTP/2 ondersteuning te krijgen:

  • Apache httpd 2.4.17 met mod_http2
  • nginx 1.9.5
  • JBoss Wildfly 9 met Undertow
  • Tomcat 9
  • Jetty 9.3
  • Netty 4
  • NodeJs 5

Ook in de volgende software wordt – nu of zeer binnenkort – HTTP/2 ondersteund:

  • F5 BIG-IP – de bekende load balancer
  • Microsoft IIS – in Windows 10 & Windows Server 2016
  • HAProxy 1.6 – enige load balancer die momenteel naar het achterland HTTP/2 kan praten. Apache httpd kan dat bijvoorbeeld nog niet. Je hebt dit nodig als je wilt load balancen en tevens gebruik wilt maken van applicatieve server push, statics ook via je appserver wilt uitserveren, of ook echt in je REST calls de extra capaciteit van HTTP/2 wilt kunnen benutten.

Bij de CDN leveranciers ondersteunen o.a. CloudFlare en Akamai het HTTP/2 protocol.

De overgang van websites naar HTTP/2 is in volle gang. Volgens isthewebhttp2yet.com zijn er al meer dan 125.000 sites die HTTP/2 volledig ondersteunen. Daarbij zitten grote jongens zoals Google, Wikipedia, Twitter en Facebook. Ook de site van het bedrijf achter Jetty, webtide.com, vind je er terug. De bekende webwinkel bol.com niet, een willekeurige obscure site zoals own3d.xyz dan weer wel…

Debugging

Vanwege het binaire karakter van HTTP/2, dienen ook debugging tools aangepast te worden. Alle tekstgebaseerde tools werken niet meer, of vallen in het beste geval terug op HTTP/1.1

Fiddler en Charles vallen – door ontbreken van support in .Net – altijd terug op HTTP/1.1.

De netwerkanalysetool Wireshark ondersteunt HTTP/2 volledig.

In Firefox kun je een HTTP/2 verbinding – naast HTTP/2.0 200 OK in de response header bron – herkennen aan de ‘meta’ response header X-Firefox-Spdy: h2 die Firefox zelf toevoegt.

Chrome is wat bewerkelijker, maar toont wel meer informatie. Twee URLs die informatie geven over je lokale browser:

chrome://net-internals/#http2

chrome://net-internals/#events&q=type:HTTP2_SESSION%20is:active

Een korte handleiding:

  1. Selecteer in de 3e tab een HTTP2_SESSION regel
    • Soms moet je wat heen een weer klikken & refreshen om hier wat te zien.
  2. Rechts zie je nu de verschillende packets

De bekende command line tool curl vertelt sinds versie 7.43.0 bij uitvoeren van curl --version:

curl 7.47.1 (i686-pc-cygwin) libcurl/7.47.1 OpenSSL/1.0.2g zlib/1.2.8 libidn/1.29 libpsl/0.12.0 (+libidn/1.29) libssh2/1.7.0 nghttp2/1.7.1

Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp

Features: Debug IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets Metalink PSL

Let met name op nghttp2 en HTTP2 om te controleren of je al up-to-date bent!

Fabian Stäber beschrijft op zijn blog unrestful.io de open source command line tool h2c die hij ontwikkeld heeft. Geschreven in Go en voor vele platforms als executable te downloaden van github.

Een voorbeeld:

  • start in console 1 h2c start --dump
  • start in console 2 h2c get https://webtide.com. Alle content verschijnt dan in console 2, inclusief eventuele pushed data.

In console 1 scrolt heel veel informatie langs, onder andere je eerste echte Push Promise:

-> HEADERS(1)

        + END_STREAM

        + END_HEADERS

        :method: GET

        :scheme: https

        :authority: webtide.com

        :path: /

 

    <- PUSH_PROMISE(1)

        + END_HEADERS

        Promised Stream Id: 2

        :scheme: https

        :method: GET

        :authority: webtide.com

        :path: /wp-content/plugins/crayon-syntax-highlighter/fonts/monaco.css?ver=_2.7.2_beta

        host: webtide.com

De push promise hoort bij stream 1 en geeft aan dat er een stream 2 response komt op de – nog niet door de browser opgevraagde! – GET URL zoals genoemd bij :path:.

Security

Enige tijd is er binnen de HTTP/2 Working Group over gesproken om TLS – het protocol onder HTTPS – verplicht te stellen in HTTP/2. Daar was echter geen consensus voor. De meeste browsermakers hebben ervoor gekozen om plaintext HTTP/2 niet te ondersteunen. Alleen Android Browser staat momenteel toe om niet-versleuteld te communiceren met HTTP/2. In de praktijk betekent dit dat HTTP/2 verkeer (bijna) altijd encrypted zal zijn. Wil je een site hosten met HTTP/2 dan moet er een CA-gesigneerd certificaat beschikbaar zijn.

Wat wel is afgesproken, is om over te gaan van Next Protocol Negotiation (NPN) naar Application-Layer Protocol Negotiation (ALPN) Extension. Dit scheelt 1 roundtrip in het opzetten van de TLS verbinding door middels van het meteen meesturen van de header “tls-applayerprotoneg” in de ClientHello en ServerHello berichten. Details zijn vastgelegd in RFC 7639.

Ook is er afgesproken om de minimaal vereiste algoritmen en sleutelgroottes ‘op te krikken’. Hieronder zullen we zien dat dat impact heeft op Java applicaties!

Er wordt in specificaties wel gesproken over ‘h2://’ voor encrypted en ‘h2c://’ voor unencrypted HTTP/2. Let wel: dit zijn geen URI prefixes; dat blijft ‘http(s)://’.

Java

Gevolgen & kansen voor (Java-)ontwikkelaars zijn initieel niet groot. Nadat je je ontwikkelstraat hebt geupgrade – appserver, load balancer, debugging tools, et cetera – zou je kunnen overwegen om sprites, minification & domain sharding niet meer te gebruiken.

De volgende technieken blijven nuttig:

  • serveer statische content via een CDN
  • minimaliseer DNS lookups
  • zet CSS includes bovenaan in de HTML en Javascript includes onderin

Early adopters hebben één voordeel en dat is SEO oftewel Search Engine Optimisation: Google zal je site hoger ranken als de page load tijd kleiner is!

In Servlet 4.0 (JSR 369), dat in 2017 beschikbaar komt in JEE8, komt een PushBuilder om Server Push mogelijk te maken.

Zie listing 1.



javax.servlet.http.PushBuilder pb = request.getPushBuilder().path("/my-image.gif");

pb.push();

De appserver zal dan op eigen initiatief een ‘virtueel’ request naar je applicatie sturen, in dit geval op /my-image.gif. De push promise en de data worden dan als aparte frames op de response gezet.

Jetty biedt nu al een eigen push API implementatie die zeer waarschijnlijk in JSR 369 gestandaardiseerd wordt.

JSR 369 zal mogelijk ook toegang bieden tot de stream id en priority en dependency waarden ervan.

In JSE 9 – gepland Q1 2017 – zitten twee JEP’s die de JDK HTTP/2 compatible gaan maken:

  • JEP 244: TLS Application-Layer Protocol Negotiation Extension
  • JEP 110: HTTP/2 Client

Ook zonder Java9 kun je nu al enkele asynchrone client implementaties gebruiken: Jetty, Netty en OkHttp.

Jetty’s HTTP/2 client is beschikbaar op maven GAV-coördinaten org.eclipse.jetty.http2:http2-http-client-transport:9.3.6.v20151106.

Listing 2 illustreert de high level API hiervan.



SslContextFactory sslContextFactory = ...;

HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client());

HttpClient httpClient = new HttpClient(transport, sslContextFactory);    

httpClient.start();

ContentResponse response = httpClient.GET("https://google.com");

Om dit voorbeeld te runnen heb je – vanwege HTTP/2’s strengere encryptie-eisen – Java8, of Java7 met Bouncy Castle nodig. Daarnaast is ook de Jetty ALPN bootclasspath extensie benodigd. Deze kun je activeren met het command line argument -Xbootclasspath/p:alpn-boot-VERSION.jar. Een probleem is dat de versie van deze extensie heel direct gekoppeld is aan de exacte versie van de Java 7 of 8 runtime. Zo moet je bij Java 1.8.0u92 bijvoorbeeld op zoek naar org.mortbay.jetty.alpn:alpn-boot:8.1.8.v20160420.

De microservice stacks Spring boot en Dropwizard bieden al ondersteuning voor HTTP/2. De drie reactive stacks Play, Vert.x en Ratpack nog niet, en zien er – blijkt uit de commentaren bij issue tickets – tot mijn verbazing vooralsnog ook geen noodzaak voor.

Conclusie

HTTP/2 pakt succesvol het latency probleem aan waar HTTP/1.1 last van heeft. De uitrol is in volle gang. Als Java-ontwikkelaars zullen we de komende tijd gaan uitvinden hoe we er in onze applicaties heeft meest effectief gebruik van kunnen maken. Kunnen we er bijvoorbeeld krachtiger REST interfaces mee bouwen? Een spannende tijd ligt voor ons!

Meer informatie? Schrijf je in voor de cursus Web sockets & server push bij Ordina J-Technologies. In deze cursus wordt onder andere HTTP/2 behandeld, aan de hand van demo’s en diverse hands-on labs.