Accelerate networking with HTTP/3 and QUIC
Description: 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.
Evolution of HTTP
- HTTP/1.1
- initially we had to make a new connection for every resource that we wanted from the server
- when doing so, a lot of time is spent on connection setup instead of resource transmission
- we reuse a single HTTP/1 connection for multiple data transfers, a.k.a. head-of-line blocking, but a request can only be sent after the previous response has ended
- to over come this, in the past, HTTP implementations used many parallel connections, however this brings 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
- HTTP/3
- connections are set up much faster
- streams are independent (in HTTP/2, all streams shared a single TCP connection) - packet loss only affect one stream but not others
- uses QUIC instead of TCP
QUIC
- new transport protocol
- faster connection setup
- connection migration support - allows connections to move seamlessly across different network interfaces without reestablishing a session
- TLS 1.3 security
- standardized by the Internet Engineering Task Force (IETF)
- based on the same concepts of TCP
- provides:
- end-to-end encryption
- multiplexed streams
- authentication
Using HTTP/3
- enabled by default in
URLSession
- you only need to enable HTTP/3 on your server - supports HTTP/3 RFC and Draft 29
You can use Instruments to verify that your app uses HTTP/3 - use the networking profiling template to inspect HTTP Traffic
Using QUIC
When to use QUIC directly:
- Non-request/response pair
- Benefits from multiplexed streams
- Custom protocols
Using QUIC in your app:
- new
NWProtocolQUIC
built-in in Network.framework
// 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)
Using QUIC streams to send and receive data
// Send data on a stream
connection.send(content: data, completion: .contentProcessed { error in
// Handle error, if any
// Schedule next send
})
// Receive incoming data
connection.receive(minimumIncompleteLength: 1, maximumLength: desiredLength) {
receivedcontent, context, isComplete, receivedError in
// Handle data, error
// Schedule next receive
}
Use NWMultiplexGroup
to refer to the underlying transport shared by the group of streams.
- 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
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)
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)
}
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)
}
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()
}