HTTP/2

slid.es/seanm/http2

About Me

Sean MacAvaney

macavaney.us  @SeanMacAvaney • sean.macavaney@gmail.com

  • Software Engineering student at MSOE
  • Software Developer at iit/SourceTech
  • Interest in web technologies

Overview

  • Background
  • Technical Details
  • Practical Details

Background

1991: HTTP/0.9

Demo

1996: HTTP/1.0

(RFC 1945)

Demo

1999: HTTP/1.1

(RFC 2616, replaced by RFCs 7230-7237)

2009: SPDY

2015: HTTP/2

(RFC 7540)

What's changed since 1999?

(Source: HTTP Archive, 2011-present, Alexa Top 1,000 sites)

Fast downloads, slow uploads.

Goal:
Improve perceived load time of modern web pages

Technical Details

Multiplexed Streams

Flow

Control

Server

Push

Header Compression

HTTP/1.1 Problem:
Only 1 request at a time per TCP socket*

(This makes loading many resources slow.)


 Client -----------WAITING------------------------WAITING---------------- ...
         \                         /\                                  /
          \                       /  \                                /
       Request 1         Response 1  Request 2                Response 2
            \                   /      \                            /
             \                 /        \                          /
 Server ---------PROCESSING-----WAITING----------PROCESSING------------- ...
        ^                                                              ^
        t=0                                                        t=100

Head-of-Line Blocking

HTTP/1.1 Solution:

More Connections!

~ 6±2 Per Host

"A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy... These guidelines are intended to improve HTTP response times and avoid congestion." RFC 2616 § 8.1.4  

HTTP/1.1 Solution:

Domain Sharding!

HTTP/2 Solution:

Allow multiple concurrent requests on the same TCP connection.

 

Streams & Frames

HTTP/2 Streams

Over a single TCP socket, HTTP allows for many streams of data.

http://stackoverflow.com/questions/10480122

HTTP/2 Frames

Each stream is made up of many frames of data.

 

Each stream is initialized with a HEADERS or PUSH_PROMISE frame.

HTTP/2 Frames

  • 0x00 - DATA

  • 0x01 - HEADERS

  • 0x02 - PRIORITY

  • 0x03 - RST_STREAM

  • 0x04 - SETTINGS

  • 0x05 - PUSH_PROMISE

  • 0x06 - PING

  • 0x07 - GOAWAY

  • 0x08 - WINDOW_UPDATE

  • 0x09 - CONTINUATION

Stream: 1

Type: Headers

Stream: 1

Type: Headers

Stream: 1

Type: Data

Stream: 3

Type: Headers

Stream: 5

Type: Headers

Stream: 3

Type: Headers

Stream: 5

Type: Headers

Stream: 5

Type: Data

Stream: 3

Type: Data

Client

Server

GET /html

GET /img

GET /css

Stream: 1

Type: Headers

Stream: 1

Type: Headers

Stream: 1

Type: Data

Stream: 2

Type: Headers

Stream: 4

Type: Headers

Stream: 4

Type: Data

Stream: 2

Type: Data

Client

Server

GET /html

GET /img

GET /css

Stream: 2

Type: Promise

Stream: 4

Type: Promise

Stream: 1

Type: Headers

Stream: 1

Type: Headers

Stream: 1

Type: Data

Client

Server

GET /html

GET /img

GET /css

Stream: 2

Type: Promise

Stream: 4

Type: Promise

Stream: 2

Type: Reset

Stream: 4

Type: Reset

HTTP/1.1 Problem:
High Constant Request Overhead

HTTP/1.1 Request & Response

GET / HTTP/1.1
Host: www.example.com
Cookie: sessionId=mbi2pyrjqxp2rvxc4x4t52rl; _ga=GA1.2.4532145987.69853215412; mode=1
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36
DNT: 1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,uk;q=0.6


----------------------------------------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Wed, 18 Mar 2015 14:16:57 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip

[compressed & chucked response data]

474 bytes

318 bytes

HTTP/1.1 Solution:

Make fewer requests.

Sacrifice resource granularity for file size.

this.A2A=this.A2A||{};(function(_){var window=this; try{ var m,p,aa,ba,ca,da,ea,r,ga,ha,ia,la,ja,ka,ma,na,oa,v;m=this;p=function(a){return void 0!==a};aa=function(a,b,c){a=a.split(".");c=c||m;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&p(b)?c[d]=b:c[d]?c=c[d]:c=c[d]={}};ba=function(a,b){for(var c=a.split("."),d=b||m,e;e=c.shift();)if(null!=d[e])d=d[e];else return null;return d};ca=function(){}; da=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; else if("function"==b&&"undefined"==typeof a.call)return"object";return b};ea=function(a){return"array"==da(a)};_.fa=function(a){var b=da(a);return"array"==b||"object"==b&&"number"==typeof a.length};r=function(a){return"string"==typeof a};ga=function(a){return"number"==typeof a};ha=function(a){return"function"==da(a)};ia=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b};la=function(a){return a[ja]||(a[ja]=++ka)};ja="closure_uid_"+(1E9*Math.random()>>>0);ka=0; ma=function(a,b,c){return a.call.apply(a.bind,arguments)};na=function(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}};_.u=function(a,b,c){_.u=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ma:na;return _.u.apply(null,arguments)}; oa=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}};v=Date.now||function(){return+new Date};_.x=function(a,b){function c(){}c.prototype=b.prototype;a.o=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.Ff=function(a,c,f){for(var g=Array(arguments.length-2),k=2;k<arguments.length;k++)g[k-2]=arguments[k];return b.prototype[c].apply(a,g)}}; var pa=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,pa);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))};_.x(pa,Error);pa.prototype.name="CustomError";var qa;var ra=function(a){return/^[\s\xa0]*$/.test(a)},sa=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},Aa=function(a){if(!ta.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(ua,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(va,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(wa,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(xa,"&quot;"));-1!=a.indexOf("'")&&(a=a.replace(ya,"&#39;"));-1!=a.indexOf("\x00")&&(a=a.replace(za,"&#0;"));return a},ua=/&/g,va=/</g, wa=/>/g,xa=/"/g,ya=/'/g,za=/\x00/g,ta=/[\x00&<>"']/,Ba=function(a){return null==a?"":String(a)},Ca=function(){return Math.floor(2147483648*Math.random()).toString(36)+Math.abs(Math.floor(2147483648*Math.random())^v()).toString(36)},Da=function(a,b){return a<b?-1:a>b?1:0},Ea=function(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})},Fa=function(a){var b=r(void 0)?"undefined".replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08"):"\\s";return a.replace(new RegExp("(^"+ (b?"|["+b+"]+":"")+")([a-z])","g"),function(a,b,e){return b+e.toUpperCase()})}; var Ga=function(a){Ga[" "](a);return a};Ga[" "]=ca;var Ha=function(a,b){try{return Ga(a[b]),!0}catch(c){}return!1};var Ia,La,Ma,Na,Qa,Ra,Ta;Ia=Array.prototype;_.Ja=Ia.indexOf?function(a,b,c){return Ia.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(r(a))return r(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1};_.Ka=Ia.forEach?function(a,b,c){Ia.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=r(a)?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)}; La=Ia.filter?function(a,b,c){return Ia.filter.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=[],f=0,g=r(a)?a.split(""):a,k=0;k<d;k++)if(k in g){var l=g[k];b.call(c,l,k,a)&&(e[f++]=l)}return e};Ma=Ia.map?function(a,b,c){return Ia.map.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=Array(d),f=r(a)?a.split(""):a,g=0;g<d;g++)g in f&&(e[g]=b.call(c,f[g],g,a));return e}; Na=Ia.some?function(a,b,c){return Ia.some.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=r(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return!0;return!1};Qa=function(a,b){var c=(0,_.Ja)(a,b),d;(d=0<=c)&&_.Pa(a,c);return d};_.Pa=function(a,b){Ia.splice.call(a,b,1)};Ra=function(a){return Ia.concat.apply(Ia,arguments)};_.Sa=function(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]}; Ta=function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(_.fa(d)){var e=a.length||0,f=d.length||0;a.length=e+f;for(var g=0;g<f;g++)a[e+g]=d[g]}else a.push(d)}}; var Ua=function(a,b,c){for(var d in a)b.call(c,a[d],d,a)},Va=function(a,b){for(var c in a)if(b.call(void 0,a[c],c,a))return!0;return!1},Wa=function(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b},Xa="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Ya=function(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var f=0;f<Xa.length;f++)c=Xa[f],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}},Za=function(a){var b= arguments.length;if(1==b&&ea(arguments[0]))return Za.apply(null,arguments[0]);for(var c={},d=0;d<b;d++)c[arguments[d]]=!0;return c}; var $a;a:{var ab=m.navigator;if(ab){var bb=ab.userAgent;if(bb){$a=bb;break a}}$a=""}var cb=function(a){return-1!=$a.indexOf(a)};var db=function(){return cb("Edge")||cb("Trident")||cb("MSIE")};var eb=function(){return cb("Edge")};var fb=cb("Opera")||cb("OPR"),y=db(),gb=cb("Gecko")&&!(-1!=$a.toLowerCase().indexOf("webkit")&&!eb())&&!(cb("Trident")||cb("MSIE"))&&!eb(),z=-1!=$a.toLowerCase().indexOf("webkit")&&!eb(),hb=z&&cb("Mobile"),ib=m.navigator||null,jb=ib&&ib.platform||"",kb=cb("Macintosh"),lb=function(){var a=$a;if(gb)return/rv\:([^\);]+)(\)|;)/.exec(a);if(y&&eb())return/Edge\/([\d\.]+)/.exec(a);if(y)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(z)return/WebKit\/(\S+)/.exec(a)},mb=function(){var a=m.document;return a? a.documentMode:void 0},nb=function(){if(fb&&m.opera){var a=m.opera.version;return ha(a)?a():a}var a="",b=lb();b&&(a=b?b[1]:"");return y&&!eb()&&(b=mb(),b>(0,window.parseFloat)(a))?String(b):a}(),ob={},A=function(a){var b;if(!(b=ob[a])){b=0;for(var c=sa(String(nb)).split("."),d=sa(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var g=c[f]||"",k=d[f]||"",l=RegExp("(\\d*)(\\D*)","g"),n=RegExp("(\\d*)(\\D*)","g");do{var t=l.exec(g)||["","",""],w=n.exec(k)||["","",""];if(0==t[0].length&& 0==w[0].length)break;b=Da(0==t[1].length?0:(0,window.parseInt)(t[1],10),0==w[1].length?0:(0,window.parseInt)(w[1],10))||Da(0==t[2].length,0==w[2].length)||Da(t[2],w[2])}while(0==b)}b=ob[a]=0<=b}return b},qb=function(a){return y&&(eb()||pb>=a)},rb=m.document,sb=mb(),pb=!rb||!y||!sb&&eb()?void 0:sb||("CSS1Compat"==rb.compatMode?(0,window.parseInt)(nb,10):5); var tb=!y||qb(9),ub=!y||qb(9),vb=y&&!A("9");!z||A("528");gb&&A("1.9b")||y&&A("8")||fb&&A("9.5")||z&&A("528");gb&&!A("8")||y&&A("9");var B=function()

Concatination of JS/CSS Files

Spriting Images

.icon {
  background-image:url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAA);
}

Inlining Resources

HTTP/2 Solution:

Reduce Overhead by Compressing Headers

HPACK

Compression designed for headers.

 

Demo: macavaney.us/hpack

Why not use gzip?

(Like we do for HTTP response bodies)

Starting an HTTP/2 Connection

Negotiation over TLS

 

Application-Layer Protocol Negotiation (ALPN) with the "h2" identifier

Demo

Negotiation in Cleartext

 

  • Service discovery:
    
    Alt-Svc: h2c
    Upgrade: h2c
  • Client connection preface:
    PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n

Security

SPDY Requires TLS

HTTP/2 Can Operate in Cleartext

But most major browsers so far have made statements that they will only support HTTP/2 over TLS.

Padding

HEADERS, DATA, and PUSH_PROMISE frames can include a padding segment to conceal the length of data to an observer.

Header Table Size

To help reduce the risk of Denial-of-Service attacks, memory requirements can be limited by the server, reducing the load.

Practical Details

  • Browser Support
  • Server Support
  • Adoption

Browser Support

  • Chrome 41+ (45+ for Android)
  • Windows 10 versions of IE/Edge
  • Firefox 36+
  • Safari 9+ (El Capitan, iOS9)
  • Opera 28+

 

50-60% of users (caniuse.com/#feat=http2)

Server Support

  • Apache 2.4.12 with mod_h2
  • IIS on Windows 10 / Server 2016
  • nginx 1.9.5
  • NodeJS Package

 

https://github.com/http2/http2-spec/wiki/Implementations

Microsfot

using System.Web;

public class ExamplePushHttpHandler : IHttpHandler
{
   public void ProcessRequest(HttpContext context)
   {
      // Push GET /OtherResource to the client automatically
      context.Response.PushPromise("/OtherResource");
      
      . . .
   }

   . . .
}

NodeJS

npm install http2
var fs = require('fs');

var options = {
  key: fs.readFileSync('./example/localhost.key'),
  cert: fs.readFileSync('./example/localhost.crt')
};

require('http2').createServer(options, function(req, res) {
  if (res.push) {
    var push = res.push('/client.js');
    push.writeHead(200);
    fs.createReadStream(path.join(__dirname, '/client.js')).pipe(push);
  }
  
  . . .
}).listen(8080);

Adoption

 

isthewebhttp2yet.com/measurements/overview.html

 

HTTP/2 Indicator - Chrome Extension

HTTP/2

By seanm