Although HTTP3/QUIC and cronet don’t have much to do with Flutter, it’s just that when I was recently sorting out Flutter-related information, I realized that many people don’t know about it, so I put it together to talk about it.
This article also focuses on some simplified integration of existing information to understand.
Why HTTP3/QUIC? The core reason is still that the existing protocols are no longer able to meet the demand, to give the simplest and uncritical example:
When you take your cell phone at home and use Wi-Fi to download movies, at this time you feel hungry and want to go downstairs to eat, then you take your cell phone with you and leave the house, during which you have no intention to quit the App, this process of cell phone traffic will be changed from Wi-Fi to 5G network, then the network link will be changed into a brand new network environment, and it will be a brand new IP address from Wi-Fi to 5G, then, as a new link. TCP will not be able to reuse the previous data and state, that is, the previous link is broken, then the download behavior may be interrupted, and you do not realize that the resource has not been downloaded until you want to use it after you are full.
As we know, in the HTTP/2 era, we have to go through multiple TCP handshakes for each network request, especially now that HTTPS is basically mandatory, so the TLS encrypted handshake is also indispensable. However, in the mobile scenario, in fact, the user’s network environment is very likely to keep changing, so the user may be forced to interrupt the TCP link during the process of moving.
Restarting a TCP connection during app usage brings some bad experiences, such as waiting for a new handshake, restarting the download, rebuilding the context, and other delays, and QUIC includes an implementation that solves this problem by link reuse through connection migration, which we’ll talk about in more detail later.
QUIC
You can’t talk about HTTP/3 without talking about QUIC (Quick UDP Internet Connections), because QUIC is a universal transport protocol, it’s the soul of HTTP/3, and the magic is that it runs on top of the UDP protocol.
UDP should not be a stranger to you because it is relatively unreliable compared to TCP. It has been labeled as “unreliable” because UDP does not provide any features, such as handshaking to establish a connection, or automatic retransmission if a packet is lost.
So what’s the advantage of UDP? Well, it’s definitely fast, because UDP doesn’t have to wait for handshakes, and there’s no queue head blocking, so it’s always been very good, so is QUIC built on top of UDP for the sake of performance?
Not really, or the key factor isn’t, because if QUIC is treated as a new standalone protocol running directly on top of IP, that means that HTTP/3 would be incompatible with many existing hardware devices, which is obviously unrealistic, but if it’s built on top of UDP, then QUIC has much better compatibility and deployment support.
So the end result is that QUIC reimplemented a generic “new TCP” protocol on top of UDP.
Of course, it would make sense to simply say that HTTP/3 is replacing TCP with UDP, not because UDP is faster, but so that QUIC can be deployed with better compatibility, and QUIC itself is a new, advanced “TCP” based on UDP.
So what are some of the great things about QUIC? Among them is the aforementioned ability to keep the connection going longer in a mobile environment.
QUIC supports connection migration.
As we know earlier, when the user’s phone switches between Wi-Fi and 5G network, the TCP link will be broken, if we define the link between the App and the server is to represent a link by the four elements of “App IP + App Port + Server IP + Server Port”, then the IP of the App side changes when the Wi-Fi and 5G network is in progress, so the old TCP link cannot be reused. For the server, this is a brand new link, so the old TCP link cannot be reused.
To solve this problem, QUIC proposes the connection identifier (CID), where each connection is assigned another CID number on top of the four previously mentioned elements, which can be used to uniquely identify it between App-server endpoints.
Simply put, because here CID is defined at the transport layer in QUIC, it does not change when switching networks, as shown in the figure below, which is normally included in the front-end of every QUIC packet, and the CID is one of the few pieces of data in the QUIC header that is not encrypted.
Through the CID, in the four elements mentioned earlier, even if the IP of the app changes, the QUIC server and the app only need to look at the CID to know that it’s still the same connection as before, so there’s no need to re-shake hands, and you can continue to reuse the previous state of the download, which is also the connection migration mentioned earlier.
Of course, this previous presentation is relatively simplified by how, given security concerns, CIDs are not used directly, but rather mapped.
Assuming that both the App and the server know that there are CIDs A, B, and C that are mapped to connection X, then the App might use A to mark packets on Wi-Fi and B on 5G, and the list of mappings is fully encrypted in QUIC, so that the hacker only knows about A, B, and C, but not X.
Of course, this is still simplified logic, just for better understanding, in reality the logic related to CID is more complex, just through these, you should be able to more quickly understand why QUIC can do connection migration and its key implementation concept.
In this way, with QUIC support, in a mobile scenario, when the network is switched, you can see the following figure on the right side of the phone using QUIC quickly responded to the request, while the left side of the phone because of the need to establish a new link, so you need to wait for a timeout, re-shake handshake successfully before requesting the efficiency of the comparison.
TLS Integration
In fact, through the beginning of the architecture diagram, you should also be able to see that, unlike HTTP/2, QUCI he directly integrated TLS, the purpose is to speed up and reduce the HTTPS request is the time required.
Because in the past, when using HTTPS requests, HTTP data needed to be encrypted by TLS before being transmitted by TCP, although the number of encrypted handshakes required by TLS has been optimized as versions have evolved, the overhead of encryption still exists in comparison.
This is not the case in QUIC, which encapsulates TLS, so for TCP mode where both TLS and TCP protocols require separate handshakes, QUIC combines the transport and encryption handshakes into a single handshake, which saves round-trip time, but also indicates that QUIC must use TLS, which will be fully encrypted at all times under HTTP/3, and that QUIC also encrypts its s (in addition to the preceding CID, etc.) packet header fields, and even transport layer information is generally no longer readable by middleware.
So, QUIC defaults to deep encryption, and he can also save the overhead of TLS to some extent compared to the previous TCP. Of course, QUIC uses TLS to encrypt each packet individually, whereas TLS-TCP can encrypt multiple packets at the same time, so QUIC may also be slower in high throughput scenarios.
Optimizing multiplexed byte streams
Simply put, multiplexed byte streaming is when a single TCP connection downloads different resources and mixes the data from different files when transferring them, for example, ABC three kinds of data are mixed together and transferred.
In the TCP era, TCP does not know the mix of data in the multiplexed byte stream, that is, TCP does not know the data is ABC, it straight pipe transmission data, if the C data loss, TCP will think the entire data transmission loss, which leads to the entire link in the other AB because of this wait and slow down, which is the legendary head of the queue blocking problem.
QUIC in a sense solves the problem of queue head blocking in the transport layer, because QUIC will know that there are multiplexed multibyte streams exist, is the real sense of “understanding” multiplexing, so it can be on a per-flow basis to perform packet loss monitoring and recovery logic.
This, of course, creates an inherently incompatible difference between TCP and QUIC, which is “theoretically” incompatible with HTTP/2 operation, since HTTP/2 also includes the concept of running multiple streams over a single TCP connection.
In fact, TCP was never designed to transfer multiple independent files over a single TCP, it’s just that it’s used in real-world scenarios, so there’s this strange compatibility, and QUIC solves the problem that’s always existed on TCP by transferring multiple byte streams at the transport layer and dealing with packet loss on a per-stream basis.
Overall, QUIC is an incompatible upgrade of TCP, but because it is based on UDP implementation, it is compatible with existing devices, and integrates TLS, full encryption by default, and support for connection migration, etc., so that more reliable network operation can be realized in unreliable network scenarios.
Flutter under QUIC: Cronet
Now that we know that HTTP/3 and QUIC make for a better experience, it’s time to talk about Cronet, which is called Cronet because it’s a Chromium web stack, and it uses the same web engine as Chromium.
The most important thing about using Cronet is that it can support both TCP and QUIC protocols. Generally speaking, when an app sends a request, it will state “I support QUIC”, and then when the server receives the request, it will decide whether to use QUIC or not according to its own situation.
Currently, it is said that as soon as Cronet knows that the server supports QUIC, subsequent requests from Cronet will start to use QUIC, and the protocol discovery process for QUIC is done by recognizing special fields in the response header.
The core network engine of Cronet is completely based on C/C++, so it can be used in Android, but also can be used by Dart through FFI. In Google products, YouTube, Google App, Google Photos, Maps, etc. are all using Cronet to handle network requests, so it’s a good idea to use Cronet to handle network requests, so it’s a good idea to use Cronet to handle network requests. Overall, Cronet is still relatively reliable.
So really, for Cronet, Flutter and Android are both pretty much the same situation.
Cronet’s advancement in Flutter is largely dependent on the Dart language development process, for example:
Dart 2.18 provides experimental support for two platform-specific http libraries forpackage:http
:
cupertino_http
macOS/iOS support based onNSURLSession
.
cronet_http
Based on Cronet, a web library support on Android.
Dart 3.2
Improved package:jnigen to implement direct call support for Java and Kotlin, now migrating package:cronet_http (the wrapper for the Android Cronet HTTP client) from handwritten bindings to an auto-generated wrapper
Currently, Flutter can use Cronet by introducing the cronet_http package, and the new version of dio also implements the corresponding cronet_adapter, so if you use dio, you can basically use Cronet directly.
The use of Cronet can also be categorized into using the Google Play supported version and using the embedded Cronet supported version:
If your App uses GP services, then you can actually not implant additional Cronet dependencies, the advantage is that Cronet’s updates and iterations have nothing to do with your App, and the upgrades and maintenance are completely handed over to the GP, and the relative size will be much smaller.
If you don’t use or aren’t in a position to use the GP service, then you can also use an embedded runtime, such asflutter run --dart-define=cronetHttpNoPlay=true
.
If you’re using Dio, it’s “easy” to use Cronet with one line of configuration, but for now Cronet will only work on Android, on iOS NativeAdapter
uses cupertino_http
which is based on NSURLSession
.
final dioClient = Dio();
dioClient.httpClientAdapter = NativeAdapter();