HTTP/2

Are my best practices outdated?

Slides by Alberto Varela / @artberri

I'm Alberto Varela, 

ninja 
rockstar 
wizard 
arquitect 
crafter 
developer 

at 

Find me at github.com/artberri | twiter.com/artberri | berriart.com | alberto@berriart.com

Brief history of HTTP

  • HTTP/0.9 (1991)
    In 1991, Berners-Lee outlined the motivation for the new protocol and listed several high-level design goals, it unofficially acquired the HTTP 0.9 label.
  • HTTP/1.0 (1996)
    In 1996 the HTTP Working Group (HTTP-WG) published RFC 1945, which documented the "common usage" of the many HTTP/1.0 implementations found
  • HTTP/1.1 (1999)
    The first official standard was officially released in 1997, roughly six months after the publication of HTTP/1.0. Then, in 1999, a number of improvements and updates were incorporated into the standard.
  • HTTP/2 (2015)
    The HTTP/2 specification was published as RFC 7540 in May 2015, it was derived from the earlier experimental SPDY protocol, originally developed by Google in 2012


The focus on bandwidth


Latency vs Bandwidth


More Bandwidth Doesn’t Matter (much) Mike Belshe, Google. August 2010

Let's take a look to the HTTP/1.1 request

1. The DNS Lookup

Let's take a look to the HTTP/1.1 request

2.1 The Hand-shaking (TCP)

                            Note left of Client: Hey! I wan't to talk, check my ID
                            Client-->Server: SYN
                            Note right of Server: "Look nice! Here's mine!"
                            Server-->Client: SYN+ACK
                            Note left of Client: "Look nice too! Let's talk."
                            Client-->Server: ACK
                            Note left of Client: "This is what I want (Finally!)"
                            Client-->Server: request?
                        

Let's take a look to the HTTP/1.1 request

2.2 The Hand-shaking (TLS)

                                Note over Client, Server: TCP Handshake
                                Client-->Server: ClientHello
                                Server-->Client: ServerHello, Certificate, ServerHelloDone
                                Client-->Server: ClientKeyExchange, ChangeCipherSpec, Finished
                                Server-->Client: ChangeCipherSpec, Finished
                                Client-->Server: ...
                            
                                Note over Client, Server: TCP Handshake
                                Client-->Server: ClientHello
                                Server-->Client: ServerHello, ChangeCipherSpec, Finished
                                Client-->Server: ChangeCipherSpec, Finished
                                Client-->Server: GET /page
                            

The Page load

Get the first HTML and... make a new request, and a new one, and...

                            Client->Server: GET /page (with handshakes,...)
                            Server->Client: Some HTML
                            Client->Server: GET /style.css /script.js (new connections)
                            Server->Client: Some CSS and a ton of JS
                            Client->Server: GET /image-x.jpg (more connections)
                            Server->Client: Some images
                            Client->Server: GET /more-images-x.jpg (HOL blocking)
                            Server->Client: Some more images...
                        

Main problems

  • Create connections is expensive
    There are 1-3 RT before request data is sent
  • Head-of-line blocking (HoL blocking)
    Doing a new request has to wait for the ones before to complete.
  • Server knows the dependencies but can't send them
    Client need to request everything after reading the html
  • Headers size
    Increase of header size and lack of compression

HTTP/1.1: London vs Sidney

This demo consists of an image made with a 224 tiles grid, inspired by Golang's Gophertiles

London: 0.000

Sidney: 0.000

HTTP/2 to the rescue!

















HTTP/2 summary

  • Retain the semantics of HTTP/1.1
  • Single TCP connection​
    • Stream​ -> Request -> Frame
      • Streams are multiplexed​
      • Streams are prioritized​​
  • Binary Framing Layer​
    • Prioritization, Flow Control, Server Push​
  • Header Compression (HPACK)

The HTTP/2 request

                            Client->Server: GET /page (with handshakes,...)
                            Note right of Server: Single connection, single handshake
                            Server->Client: Some HTML
                            Note right of Server: The server can push
                            Server-->Client: PUSH /style.css
                            Server-->Client: PUSH /script.js
                            Client->Server: GET /image-1.jpg /image-2.jpg /image-3.jpg ...
                            Note right of Server: No HoL blocking
                            Server->Client: image1
                            Server->Client: image2
                            Server->Client: image3
                        

London: HTTP/1.1 vs HTTP/2

This demo consists of an image made with a 224 tiles grid, inspired by Golang's Gophertiles

HTTP/1.1 (LONDON): 0.000

HTTP/2 (LONDON): 0.000

Sidney: HTTP/1.1 vs HTTP/2

This demo consists of an image made with a 224 tiles grid, inspired by Golang's Gophertiles

HTTP/1.1 (SIDNEY): 0.000

HTTP/2 (SIDNEY): 0.000

Finally...

Best practices or hacks?

Use a content delivery network

Curiosity:
It takes around 130ms for light to travel around the circumference of the earth.

Cache resources on the client

Tips:

Concatenate files

Downsides:
  • Expensive cache invalidations​​
  • Unnecessary resources​
  • Delays resource processing


Tips:
  • Focus and improve your caching policy​
  • Take care of CD​
  • 'Bundlelize' your assets and measure

Image spriting

Downsides:
  • Expensive cache invalidations​​
  • Breaks stream prioritization
  • Unnecessary resources​
  • Delays resource processing


Caution:
  • There is still people using HTTP/1.x​
  • Because of size we can get better compression of images with spriting​

Minimize the size of HTTP requests & responses

Tips:
  • Minify HTML/CSS/JS size​
  • Minimize cookies​
  • Eliminate unnecessary metadata (e.g. headers)​
  • Optimize images​​​
  • Compress with GZip​

Minimize redirects

Tips:
  • Use 301/302 over <meta http-equiv="refresh“ …>​
  • Remove them after a while/they are need​

Domain sharding

Downsides:
  • Unnecessary DNS query, TCP connection,
    and TLS handshake​
  • Breaks stream prioritization (can’t prioritize
    between domains)​


Tips:
  • You can keep sharding if TLS certificate is
    valid for both hosts and points to same IP.
    You will get the best of both worlds.​

Reduce DNS lookups

Tip:
  • <link rel='dns-prefetch' href='...' />

Inline assets

Downsides:
  • Browsers can’t cache individual assets​​
  • Breaks stream prioritization​​


Tips:
  • Server Push!!​

Server push

Server push, which is defined in the HTTP/2 specification, allows a server to pre‑emptively push resources to a remote client, anticipating that the client may soon request those resources.

Server push


Source: Nginx

Server push

  • Not all servers/languages/PaaS implement Server Push, check yours. (E.g. Nginx didn't implent it until Feb 2018 although H2 was implemented since Sep 2015)
  • Not all servers/languages/PaaS implement it in the same way
  • Some servers allow to push using Preload Headers
  • It is not a silver bullet: HTTP/2 push is tougher than I thought (Jake Archibald)

Server push

Simple example using Nginx


server {
    # Ensure that HTTP/2 is enabled for the server
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # whenever a client requests demo.html, also push
    # /style.css, /image1.jpg and /image2.jpg
    location = /demo.html {
        http2_push /style.css;
        http2_push /image1.jpg;
        http2_push /image2.jpg;
    }
}
                    

Server push

Simple example using Nginx and the preload header with PHP


server {
    # Ensure that HTTP/2 is enabled for the server
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # Intercept Link header and initiate requested Pushes
    location = /myapp {
        proxy_pass http://upstream;
        http2_push_preload on;
    }
}
                    

header("Link: <{$uri}>; rel=preload; as=image", false);
                    

Server push

Nginx pushing only on the first visit (using cookies)


server {
    listen 443 ssl http2 default_server;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;
    http2_push_preload on;

    location = /demo.html {
        add_header Set-Cookie "session=1";
        add_header Link $resources;
    }
}

map $http_cookie $resources {
    "~*session=1" "";
    default "</style.css>; as=style; rel=preload, </image1.jpg>; as=image; rel=preload,
             </image2.jpg>; as=style; rel=preload";
}
                    

Do's:

  • Use content delivery network​
  • Cache resources on the client​​
  • Minimize the Size of HTTP Requests and response​​
  • Minimize redirects​​​
  • Reduce DNS Lookups​


Don'ts:

  • Concatenate files​​
  • Image spriting​
  • Domain sharding​​​
  • Inline Assets​

Resources used