Streaming is available in most browsers,
and in the Developer app.
-
Accelerate networking with HTTP/3 and QUIC
The web is changing, and the next major version of HTTP is here. Learn how HTTP/3 reduces latency and improves reliability for your app and discover how its underlying transport, QUIC, unlocks new innovations in your own custom protocols using new transport functionality and multi-streaming connection groups.
Resources
Related Videos
Tech Talks
WWDC22
WWDC21
-
Download
♪ Bass music playing ♪ ♪ Rui Paulo: Hello, everyone, I'm Rui. Today, my colleague Eric and I are going to talk about how you can speed up your app's networking with HTTP/3 and QUIC -- two new protocols available in iOS 15 and macOS Monterey. We'll begin by exploring the evolution of HTTP and how HTTP/3 improves performance. We'll also talk about QUIC, the new transport protocol behind HTTP/3. After that, we'll explain how you can use HTTP/3 in URLSession and how you should configure your HTTP server to support HTTP/3. Finally, we'll take a deep dive into the API for using QUIC and how you can implement your custom networking protocols on top of QUIC. Let's then begin with the evolution of HTTP. Let's say we need to fetch a resource. We set up a connection, send the request, wait for the server to process it, and receive a response. Now, if we want to fetch another resource before the first one finishes, we have go through the same process again: setting up a connection, sending the request, waiting for processing, and receiving the response, this time indicated in dark green. Here's another example for a third resource, in orange. As outlined by the diagram, a lot of time is spent on connection setup. What if we reuse a single HTTP/1 connection? We saved the connection set up time, but a request can only be sent after the previous response has ended. This is known as head-of-line blocking. In the past, HTTP implementations used many parallel connections to overcome this problem. The number of parallel HTTP connections was even configurable by the app. However, this led to inefficient networking behaviors for both client and server. HTTP/2 solves head-of-line blocking by multiplexing multiple streams on a single connection. Requests are sent earlier, and data from different streams can be interleaved. This allows more efficient use of a single TCP connection, as idle waiting time is drastically reduced. With HTTP/3, connection set up is much faster, so requests can go out sooner. However, that's not the only benefit of HTTP/3. HTTP/3 streams are independent, which is different from HTTP/2, where all streams share a single TCP connection. On most networks, packets are lost. This is a normal event on wireless networks and a natural part of detecting the capacity of a network. In HTTP/2, packet loss may affect many streams since all of the HTTP/2 streams share a single TCP connection. In HTTP/3, only the corresponding HTTP stream is affected. Data that belongs to other streams can be delivered earlier. We've just shown how HTTP/3 can establish a connection sooner and how it can better cope with packet loss. These improvements are enabled by the underlying transport protocol: QUIC. QUIC is a new reliable transport protocol that has been standardized by the Internet Engineering Task Force. It's based on the same concepts of TCP but provides end-to-end encryption, multiplexed streams, and authentication. QUIC's security builds on top of the well-known TLS 1.3 protocol. The major benefit of QUIC is improved performance. Let's explore how QUIC achieves just that. QUIC relies on TLS 1.3 to perform a secure handshake and does not require the familiar TCP three-way handshake, reducing the handshake time to a single round trip. Multiplexed streams are a key concept of QUIC, so it doesn't suffer from head-of-line blocking. A QUIC endpoint is able to communicate more complex information about packets it has received to the other endpoint and is not encumbered by TCP's limits, so QUIC connections experience improved loss recovery. The protocol also supports connection migration which allows connections to move seamlessly across different network interfaces without reestablishing a session, for example, between cellular networks and Wi-Fi. If you would like to learn more about networking delays, please watch the "Reduce network delays for your app" session. Let's talk about how you can use HTTP/3 in your app. If you're using URLSession, you don't need to change your app since iOS 15 and macOS Monterey ship with HTTP/3 enabled by default. Once you enable HTTP/3 on your server, you're good to go. Both the upcoming HTTP/3 RFC version and the earlier HTTP/3 Draft version 29 are supported. So how can you make sure your app is using HTTP/3? Let's use Instruments to find out! In Xcode 13, we're introducing a new instrument within the networking profiling template to inspect HTTP Traffic. It taps into URLSession directly, so there's no setup required. We can use Instruments to find out if our app is using HTTP/3 or an earlier HTTP version. We're going to launch an iOS app that, upon starting, fetches a set of dog pictures. We'll then inspect HTTP headers to find out how the server is advertising HTTP/3. Let's go ahead and select the Network profiling template.
Let's click Record on the top left. A prompt will be displayed indicating the privacy implications of recording HTTP Traffic. After we agree, Instruments will begin recording HTTP transactions.
Next, Instruments will show a plot with all HTTP transactions per app and per domain. We have now captured all the data that we need. We can click the Pause button located on the top left.
Let's select the domain that we're using. We can do that by Option-clicking on the HTTP transactions and then selecting the domain.
We need to configure Instruments to show the details of the HTTP Transactions. To accomplish that, make sure the menu on the left side shows HTTP Transactions.
Let's select the first request. By scrolling right, we can find the HTTP Version column which identifies the version of HTTP that this transaction used. Hm, we're still using HTTP/2, but why is that? On the right-hand side, we can find the extended detail view that contains the response headers. This give us the answer: the server used HTTP Alternative Services to advertise support for HTTP/3.
URLSession won't use HTTP/3 unless it was advertised. In this example, HTTP/3 was advertised through the Alt-Svc HTTP header. It's common for HTTP servers to advertise support for HTTP/3, using this header. This information is remembered for future connections, and we call this "service discovery." Now let's record the app again.
Once Instruments relaunches the app, the same set of HTTP transactions take place. Again, we can now pause Instruments.
Let's zoom in again and inspect the first transaction. Since we've remembered that the server supported HTTP/3, we're now using HTTP/3. HTTP/3 service discovery is transparent to your app. Discovery of HTTP/3 server support happens in two ways. The recommended approach is to configure your DNS server to advertise support for HTTP/3 through the HTTPS resource record. Simply configure the application layer protocol to advertise HTTP/3 using the h3 string. You should also configure your server to add a new header that advertises HTTP/3 using Alternative Services. Your server should send a Alt-Svc header that advertises HTTP/3. This includes the port number and the max-age of the service, in seconds. The advantage of the DNS record is that since the information is in DNS, an HTTP/3 connection can be established the very first time your app tries to contact to your server. When you know that your server supports HTTP/3, and you would like to speed up the discovery process, you can use the assumesHTTP3Capable property. This allows the HTTP stack to assume that you have an HTTP/3 server but does not guarantee that HTTP/3 will be used. Networks may still block HTTP/3, or your server may not actually support HTTP/3. In that case, we'll fall back to HTTP/2. HTTP allows clients to specify priorities for each resource. Since resources are often related, priorities allow the server to send some resources earlier than others, based on the needs of the client. For example, user experience for web browsing can be improved by prioritizing resources that affect web page rendering the most. A priority scheme was introduced in HTTP/2 but it was not often respected due to its complexity. For that reason, the old priority model was removed from HTTP/3. A new, simpler model, which relies on HTTP headers, is used by the HTTP/3 stack. In this model, priority is specified with an urgency parameter -- zero to seven -- and an optional incremental delivery parameter. When using URLSession, the API to support priorities remains the same. You still specify the HTTP priority, using the priority property, which is communicated to the server using urgency. You can enable incremental delivery with the prefersIncrementalDelivery property. The default priority is 3. URLSession infers incremental delivery depending on whether a convenience API -- like the async data method -- is used. When your app is downloading content that cannot be processed until the entire resource is downloaded, you should set this property to false. Dynamically changing the priority of a resource after the request was sent is also supported. For example, you can prefetch photos at a lower priority and then raise that priority as the user navigates to that section of your app. Next, my colleague Eric is going to explain how you can change your custom networking protocols to adopt QUIC. Thank you. Eric Kinnear: Thanks, Rui! As we discussed earlier, HTTP/3 is built on top of QUIC, which provides multiplexed streams, similar to those found in HTTP/2, but without the problems introduced by sharing a single TCP connection as the underlying transport. A QUIC transport connection -- or QUIC tunnel -- multiplexes data for multiple unidirectional or bidirectional QUIC streams. Streams can be created by either endpoint, can concurrently send data interleaved with other streams, and have states similar to the traditional streams provided by TCP. Best of all, QUIC has TLS 1.3 security built right in and can better respond to changing network conditions. These capabilities are useful for more than just HTTP. If your application is exchanging non-request/response based data, could benefit from having multiplexed streams that share an underlying transport context, or is implementing any other custom protocol -- such as peer-to-peer communication or RPC calls -- consider using QUIC transport for your app. In iOS 15 and macOS Monterey, NWProtocolQUIC joins the other built-in protocols provided by Network.framework. Creating a connection that uses QUIC is very familiar. Simply provide your endpoint and the newly available QUIC parameters. These parameters specify the ALPN string, the application layer protocol to negotiate with the server. Set a state update handler -- just like usual -- to be able to respond as the connection makes progress and becomes ready. And, finally, start your connection on a dispatch queue that you'd like to use for those state updates and other callbacks. Now that you've got a QUIC stream established, you can send and receive data just like any other NWConnection. Use the send function to provide the data you'd like to send to the remote endpoint, and schedule subsequent sends when complete. Use receive to handle incoming data, and schedule subsequent receives when it completes. Last year, we introduced the Connection Group object in Network.framework to make it easier to handle situations where multiple connections are related or grouped. QUIC streams that are multiplexed on an underlying transport context -- or tunnel -- are logically grouped based on that relationship and can be used with a new group type: NWMultiplexGroup. A Connection Group follows a lifecycle similar to that of the other Network.framework objects and allows you to reason about the state of the underlying QUIC tunnel shared by your QUIC streams. It also allows you to create new outgoing streams from a specific QUIC tunnel as well as receive new incoming streams initiated by the remote endpoint. To create a connection group for a multiplexing protocol, use a multiplex group descriptor. In this case, we'll create a group descriptor to example.com on port 443. Next, we create a NWConnectionGroup with that descriptor and QUIC parameters, providing our ALPN string when we create them. Just like with an NWConnection, we set a state update handler, but this time, it's tracking the state of the underlying QUIC tunnel, rather than the state of an individual stream. Finally, we start the connection group, providing our callback queue. New outgoing streams can be created by initializing a NWConnection from the group or by calling the extract function on the group. Incoming streams initiated by the remote endpoint can be handled by setting the new connection handler on the group. These connections can be set up as usual with a state handler -- this time tracking the stream state -- and started with the queue to use for callbacks. Just like with other protocols, you can use QUIC.Options for configuration when creating your parameter's object. For QUIC, you can configure the transport parameters listed in the QUIC specification, and you can also customize properties of individual streams when creating them from a connection group; for example, if you want create a new, unidirectional stream. If you're using NWListener to run a server in your app, it has also been enhanced to allow you to receive new incoming QUIC tunnels via the newConnectionGroupHandler. Your newConnectionGroupHandler will be called every time someone establishes a new QUIC tunnel to your server. Inside that handler, you can set up the group as usual to receive state updates. This is also a good place to set the new connection handler we were just discussing. If you'd like to receive subsequent streams opened on this tunnel, start the group with the queue to use for callbacks, and you're all set! Finally, you can use NWProtocolMetadata to access information about streams. For example, you might want to check the stream ID of a newly created stream. And when you're done with a stream, if your custom protocol has defined applicationError codes, you can use the metadata to communicate any errors to the remote endpoint before you cancel the stream. So we've just explored how we can use the new NWMultiplexGroup type to create and manage QUIC tunnels, and from that group, create individual NWConnections for each QUIC stream. We can use a NWListener to listen for incoming tunnels, and use the resulting connection groups to receive new incoming streams. On those streams, we can send and receive data just like on any other connection, and we can use QUIC protocol options to specify transport parameters and configure streams, while using QUIC protocol metadata to inspect the streams and communicate QUIC-specific information to the remote endpoint. Now that we've improved the networking in our app by adopting QUIC, how do we tell if it's working? We can launch our app while debugging, with a new environment variable that outputs qlog files. qlog is a new standardized logging format proposed in the IETF, which allows you to export even richer information about how your QUIC connections are behaving than would be present in a traditional packet capture. After your test run, you can use the Devices window in Xcode to download your app's container with the qlog files for analysis. And there are a number of different open-source visualizations that make it much easier to introspect the behavior of your QUIC connections. Today, we've examined the improvements that HTTP/3 provides for your HTTP traffic. On the client, it's already on by default for users of the modern networking APIs, so enable HTTP/3 on your server to take advantage of its increased performance and resilience to changing network conditions. If you're using a custom, non-HTTP networking protocol, use the new multiplexing protocol support built into Network.framework to create QUIC connections with NWConnectionGroup. And whichever protocol you're using, you can use new debugging tools to visualize the awesome benefits of the next generation of networking protocols. Thanks for watching. ♪
-
-
13:20 - Using QUIC in your app
// Create a connection using QUIC let connection = NWConnection(host: "example.com", port: 443, using: .quic(alpn: ["myproto"])) // Set the state update handler to be notified when the connection is ready connection.stateUpdateHandler = { newState in switch newState { case .ready: print("Connected using QUIC!") default: break } } // Start the connection with callback queue connection.start(queue: queue)
-
15:08 - Establish a tunnel with NWMultiplexGroup
// Establish a tunnel with NWMultiplexGroup // Create a group let descriptor = NWMultiplexGroup(to: .hostPort(host: "example.com", port: 443)) let group = NWConnectionGroup(with: descriptor, using: .quic(alpn: ["myproto"])) // Set the state update handler to be notified when the group is ready group.stateUpdateHandler = { newState in switch newState { case .ready: print("Connected using QUIC!") default: break } } // Start the group with callback queue group.start(queue: queue)
-
15:45 - Manage streams with NWConnectionGroup
// Manage streams with NWConnectionGroup // Create a new outgoing stream let connection = NWConnection(from: group) // Receive new incoming streams initiated by the remote endpoint group.newConnectionHandler = { newConnection in // Set state update handler on incoming stream newConnection.stateUpdateHandler = { newState in // Handle stream states } // Start the incoming stream newConnection.start(queue: queue) }
-
16:43 - Receive incoming QUIC tunnels from NWListener
// Receive incoming QUIC tunnels from NWListener // Set the new connection group handler listener.newConnectionGroupHandler = { group in group.stateUpdateHandler = { newState in // Handle tunnel states } group.newConnectionHandler = { stream in // Set up and start new incoming streams } group.start(queue: queue) }
-
17:22 - Access QUIC metadata
// Access QUIC metadata to learn about and modify streams // Find the stream ID of a particular QUIC stream if let metadata = connection.metadata(definition: NWProtocolQUIC.definition) as? NWProtocolQUIC.Metadata { print("QUIC Stream ID is \(metadata.streamIdentifier)") // Some time later... // Set the application error, if appropriate, before cancelling the stream metadata.applicationError = 0x100 connection.cancel() }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.