HTTP/3 is the third major version of the Hypertext Transfer Protocol used to exchange information on the World Wide Web, complementing the widely-deployed HTTP/1.1 and HTTP/2. Unlike previous versions which relied on the well-established TCP (published in 1974),[1] HTTP/3 uses QUIC, a multiplexed transport protocol built on UDP.[2] On 6 June 2022, IETF published HTTP/3 as a Proposed Standard in RFC 9114.[3]
- https://en.wikipedia.org/wiki/HTTP/3
Benefits
QUIC will help fix some of HTTP/2's biggest shortcomings:
Developing a workaround for the sluggish performance when a smartphone switches from WiFi to cellular data (such as when leaving the house or office)
Decreasing the effects of packet loss — when one packet of information does not make it to its destination, it will no longer block all streams of information (a problem known as “head-of-line blocking”)
Other benefits include:
Faster connection establishment: QUIC allows TLS version negotiation to happen at the same time as the cryptographic and transport handshakes
Zero round-trip time (0-RTT): For servers they have already connected to, clients can skip the handshake requirement (the process of acknowledging and verifying each other to determine how they will communicate)
More comprehensive encryption: QUIC’s new approach to handshakes will provide encryption by default — a huge upgrade from HTTP/2 — and will help mitigate the risk of attacks
Flutter Package
Dart has not yet officially support HTTP/3 in their SDK, It is an open issue to support HTTP/3 (https://github.com/dart-lang/sdk/issues/38595). But they have an experimental packages for Flutter iOS and Android separately. It is:
https://pub.dev/packages/cronet_http/changelog An Android Flutter plugin that provides access to the Cronet HTTP client. Cronet is available as part of Google Play Services. Maintained by Dart Dev.
https://pub.dev/packages/cupertino_http A macOS/iOS Flutter plugin that provides access to the Foundation URL Loading System. Maintained by Dart Dev
Both packages can be used easily since they are using he high-level interface defined by package:http Client.
How to implement
Since we have a http client, so we only need to change the wrapper. Previously we are using BaseIOClient
, now we only need to use the package Client factory.
BaseHtttp3Client
is our static class to give the respective client factory. If its iOS/macOS it would return CoupertinoClient
, If its Android then CronetClient
, otherwise it will return default Client
;
It is easy to implement and have a minimal change, but we are losing our ability to have SSL Pinning and Upload Progress Stream. We can’t have SSL Pinning because there is way to add SecurityContext to this client. And Upload Progress Stream is can’t be implemented in this client since they send the data on a big chunk and wait the process till completed.
BaseIOClient.send | CoupertinoClient.send | CronetClient.send |
---|---|---|
final stream = request.finalize(); final StreamController<double> sc = StreamController<double>(); final contentLength = request.contentLength ?? -1; final ioRequest = (await _inner!.openUrl(request.method, request.url)) ..followRedirects = request.followRedirects ..maxRedirects = request.maxRedirects ..contentLength = contentLength ..persistentConnection = request.persistentConnection; request.headers.forEach((name, value) { ioRequest.headers.set(name, value); }); int uploadedDataCount = 0; uploadStreamCompleter?.complete(sc.stream); final processCompleter = Completer(); final requestCompleter = Completer(); final sl = stream.listen( (value) { ioRequest.add(value); uploadedDataCount += value.length; final percentage = uploadedDataCount / contentLength; sc.add(percentage.isNaN ? GeneralConstant.ninetyNinePercent : percentage); }, onError: (e, st) { ioRequest.addError(e, st); sc.addError(e, st); }, onDone: () { ioRequest.close().then((value) { sc ..add(1) ..close(); processCompleter.complete(); requestCompleter.complete(value); return; }).catchError((e, st) { sc ..addError(e, st) ..close(); return; }); }, ); await processCompleter.future; await sl.cancel(); final HttpClientResponse response = await requestCompleter.future; | final stream = request.finalize(); final bytes = await stream.toBytes(); final d = Data.fromUint8List(bytes); final urlRequest = MutableURLRequest.fromUrl(request.url) ..httpMethod = request.method ..httpBody = d; // This will preserve Apple default headers - is that what we want? request.headers.forEach(urlRequest.setValueForHttpHeaderField); final task = _urlSession.dataTaskWithRequest(urlRequest); final taskTracker = _TaskTracker(request); _tasks[task.taskIdentifier] = taskTracker; task.resume(); final maxRedirects = request.followRedirects ? request.maxRedirects : 0; final result = await taskTracker.responseCompleter.future; final response = result as HTTPURLResponse; | final stream = request.finalize(); final body = await stream.toBytes(); var headers = request.headers; if (body.isNotEmpty && !headers.keys.any((h) => h.toLowerCase() == 'content-type')) { // Cronet requires that requests containing upload data set a // 'Content-Type' header. headers = {...headers, 'content-type': 'application/octet-stream'}; } final response = await _api.start(messages.StartRequest( engineId: _engine!._engineId, url: request.url.toString(), method: request.method, headers: headers, body: body, followRedirects: request.followRedirects, maxRedirects: request.maxRedirects, )); |
How to test
We need to enable HTTP/3 support in our server first. In this case, we are using CloudFlare and they have toggle for it. So we need to turn on HTTP/3 (with QUIC) and 0-RTT Connection Resumption.
Once those toggles enabled, We add a debug pointer on the http result then make a request from the app side. HTTP/3 response will return a response header called alt-svc and we must see h3=:443
. That is indicating that the request is handled using HTTP/3.
Summary
Flutter is support HTTP/3 by using their experimental packages. But we are losing our ability to have SSL Pinning which is feature that recommended by our Penetration Tester. Other than that, we are can’t have an Upload Progress Stream, because the http/3 client doesn’t pipe the body stream they send it in a big chunk.