HTTP/2 was finalized earlier this year, published as RFC 7540 and is already integrated in iOS 9 and OS X El Capitan. Additionally Apple is pushing HTTP/2 aggressively by providing the new Apple Push Notification Service (APNs) only via HTTP/2 (WWDC Session 720 - What’s New in Notifications - starting ~16:40). HTTP/2 is a great step for the web and especially for mobile devices because it solves some common problems of HTTP 1.1.

HTTP/2 vs HTTP 1.1

If you’re already familiar with the advantages of HTTP/2 you might want to skip this section. ;-)

The two most important goals of HTTP/2 for us as app developers are:

  1. High level compatibility with HTTP 1.1 - same methods (GET, PUT, POST, …), status codes (200, 404, 500,…), URIs and header fields.
  2. Improved latency

The first thing is important for us, because we don’t need to change any application logic to support HTTP/2. The second thing is important for us, because our mobile devices often operate under high latency conditions.

The main problem of HTTP 1.1 is it’s strict FIFO order of request and the resulting head-of-line blocking problem. Request are always executed in the order in which they are send. This means a large and slow request at the beginning of a connection blocks all other request until it completes. Additionally HTTP 1.1 uses a textual header format which is an overhead when sending a lot of small requests.

Features that make HTTP/2 faster than HTTP 1.1 are:

  • Only one TCP connection (per Host)
  • Multiplexed connection
  • Priorities for requests
  • Binary protocol - not textual
  • Header compression
  • Server push technologies

To learn more about HTTP/2 I recommend the WWDC 2015 Session 711 - Networking with NSURLSession - starting ~13:10.

How to test whether we are communicating via HTTP/2?

Unfortunately NSURLSession doesn’t give us any information about the used protocol version. There’s an enhancement request and little discussion in the Apple Developer Forums, but I don’t think that we’ll see an API for it in the final iOS 9.0 release.

Usually I use Charles Proxy to have a look at my outgoing network requests but apparently Charles doesn’t support neither HTTP/2 nor SPDY. Wireshark is another alternative to inspect your network traffic but unfortunately I had some issues running it on OS X 10.11, but in theory you should be able to see whether a connection was made via HTTP 1.1 or HTTP/2.

Of course you can have a look at your server logs, but maybe you don’t have access to them or your server isn’t supporting HTTP/2 yet.

What’s left is this nice test page from Akamai. It gives you a little message about whether you successfully connected to it over HTTP/2 or not.

HTTP/2 on iOS

So lets use this Akamai page to test our beloved iOS! Open the page in Safari on iOS 8 and you should see a message This browser is not HTTP/2 enabled.. If you use your iOS 9 beta device and navigate to the same page you get the more friendly You are using HTTP/2 right now! message. OK - system apps work.

Now we want to use our own app and send a simple request via good old NSURLConnection:

let request = NSURLRequest(URL: NSURL(string: "https://http2.akamai.com")!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { (res, data, error) in
  if error != nil && let data = data && let str = NSString(data: data, encoding: NSUTF8StringEncoding) {
    if str.containsString("You are using HTTP/2 right now!") {
      NSLog("Used HTTP/2")
    } else {
      NSLog("Used HTTP 1.1")
    }
  } else {
	// error...
  }
})

Have a look at the output: Used HTTP 1.1 Wait?! Why isn’t it using HTTP/2?

The reason is simple: NSURLConnection is deprecated in iOS 9 and OS X 10.11 and therefore doesn’t support HTTP/2! It’s a pretty old API and the more modern NSURLSession is around for quite a while now (since iOS 7.0).

Using NSURLSession

let url = NSURL(string: "https://http2.akamai.com")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, res, error) in
  if error != nil && let data = data && let str = NSString(data: data, encoding: NSUTF8StringEncoding) {
    if str.containsString("You are using HTTP/2 right now!") {
      NSLog("Used HTTP/2")
    } else {
      NSLog("Used HTTP 1.1")
    }
  } else {
	// error...
  }
}
task.resume()

.. iOS now sends a nice HTTP/2 request!

Having those two networking APIs it’s easy to do some benchmarking in a single app. I used the handy Network Link Conditioner of iOS (settings app -> developer menu) to simulate different network conditions. You can find the complete sample app that I used to generate the results here.

Sending a single request

Setup Try #1 Try #2 Try #3 Try #4 Try #5 Try #6 Try #7 Try #8 Average
3G HTTP 1.1 1.942 1.956 0.652 0.739 0.685 1.592 0.790 0.580 1.117
3G HTTP/2 2.015 0.546 0.678 0.703 0.521 0.511 0.818 0.701 0.812
DSL (2K) HTTP 1.1  0.944 0.368 0.349 0.337 0.335 0.491 0.332 0.339 0.437
DSL (2K) HTTP/2 0.503 0.246 0.263 0.253 0.248 0.277 0.259 0.274 0.290

For 3G that’s a speedup of ~1.376. For DSL (2K) it’s a speedup of ~1.507.

Sending 361 requests (32x32 pixel PNG images)

Setup Try #1 Try #2 Try #3 Try #4 Try #5 Try #6 Try #7 Try #8 Average
3G HTTP 1.1 30.108 26.406 30.970 27.686 28.045 27.064 26.116 26.593 27.874
3G HTTP/2 18.223 22.838 15.896 17.721 23.195 16.518 22.089 14.888 18.921
DSL (2K) HTTP 1.1 7.299 7.562 6.801 6.442 6.706 6.597 7.007 6.842 6.907
DSL (2K) HTTP/2 3.773 3.676 3.662 3.720 3.753 3.678 3.668 3.702 3.704

For 3G that’s a speedup of ~1.473. For DSL (2K) it’s a speedup of ~1.865.

As we can see requests over a high latency network connection with several requests benefit the most from HTTP/2. If you’re loading a collection of images from your servers or sending multiple requests for other reasons at once, your app might benefit a lot from HTTP/2. If you’re only sending one request per view controller you probably won’t notice a large difference on the user level.

Additional Notes

It’s not enough that the app is running on an iOS 9 device to use HTTP/2 - it must be build against iOS 9 and run on an iOS 9 device!

A list of servers already supporting HTTP/2 can be found on GitHub.

Another nice library/tool for HTTP/2 connections is nghttp2. You can send HTTP/2 request via nghttp(1) or even setup your own small test server with nghttpd(1).

It’s worth noting that HTTP/2 supports encrypted connections (HTTPS/TLS) as well as unencrypted ones, but most clients (e.g. Firefox & Chrome) do not support unencrypted connections and also Apple’s new ATS mechanism pushes us to use only encrypted connections!

To sum it up here is a checklist to support HTTP/2 in your app:

  • Update your servers to support HTTP/2
  • Update your servers to support up-to-date encryption (aka HTTPS/TLS)
  • Build against the iOS 9 SDK
  • Replace all NSURLConnection API calls with NSURLSession