Qt Network in Qt 6

In this blog post we want to tell you about some of the recent updates and changes that Qt Network module received in Qt 6, and also about some potential future developments.

QNetworkAccessBackend

QNetworkAccessBackend is an abstract base class used to interface with our cache, file and ftp backends. We have had QNetworkAccessBackend in Qt for a while, but it was not possible to use outside Qt Network in any reasonable way. Now it is! With Qt 6 we planned to move the ftp backend out of Qt Network and to distribute it separately as a plugin. To do this, we made QNetworkAccessBackend more friendly to use from the outside and gave QNetworkAccessManager the ability to load these plugins at runtime. As with some of our other plugin interfaces, it does not have the strict backwards-compatibility promises that Qt itself has, and it requires linking to QtNetworkPrivate to be able to use it.

We wanted to be able to specialize some of the behavior of various backends. For example, a non-network backend doesn't need us to prepare a list of proxies for the target URL. For this we added three different enumerations with a few values in each:

  • TargetType
  • SecurityFeatures
  • IOFeatures

Of these, only 'TargetType' is required, currently only specifying if the backend's supported targets are networked and/or local.

Due to this approach some functions may not do anything if certain features aren’t enabled. One such example is the IO feature 'ZeroCopy' which enables the functions readPointer() and advanceReadPointer(). If it is not enabled these two functions will not be called. The main idea of having this feature system is that we can make these kinds of improvements without breaking any existing code. New code would have to opt in to the new feature. Although, to reiterate: we cannot guarantee the same level of backwards-compatibility for network access backend plugins as for applications made with Qt.

To implement a network access backend plugin you'll need to implement a class inheriting from QNetworkAccessBackendFactory. This class will do the work of creating the backend if the requested scheme is supported. You will also need to create a class inheriting from QNetworkAccessBackend, and override all pure-virtual functions, as well as 'read()', or 'readPointer()' and 'advanceReadPointer()' if your class supports the 'ZeroCopy' feature. As for existing uses, both the QNetworkAccessCacheBackend and the QNetworkAccessFileBackend are updated to use the new interface, although those are directly compiled into Qt rather than being distributed as plugins at the moment.

Protocols

In Qt 6 we dropped SPDY support. SPDY was an experimental protocol with an open specification, and it was developed primarily at Google. SPDY became the predecessor and prototype for the HTTP/2 protocol; the introduction of HTTP/2 obsoleted SPDY, and today SPDY is deprecated.

While maintaining compatibility with HTTP/1.1 (for example with methods, status codes, URIs, and most header fields) , HTTP/2 also adds:

  • data compression of HTTP headers
  • multiplexing multiple requests over a single TCP connection
  • HTTP/2 Server Push
  • Negotiation mechanisms that allow client and server to select HTTP/2 or HTTP/1.1

Prior to Qt 6, HTTP/2 protocol had to be manually enabled by setting one of these attributes:

  • QNetworkRequest::Http2AllowedAttribute
  • QNetworkRequest::Http2DirectAttribute,

E.g.:

request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);

For 'Http2AllowedAttribute', QNetworkAccessManager uses Application Layer Protocol Negotiation for 'https' scheme and protocol upgrade header 'h2c' for 'http' scheme. The second attribute is useful if you know in advance that the server supports HTTP/2 in so-called 'direct' mode, without protocol negotiation.

In Qt 6, HTTP/2 support is enabled by default: this means the attribute 'Http2AllowedAttribute' is set to 'true'. If HTTP/2 cannot be negotiated, QNetworkAccessManager will fall back to HTTP/1.1. If your application can only use HTTP/1.1, you can set the attribute 'Http2AllowedAttribute' to 'false' for a new network request:

request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);

There is an exception to this 'enabled by default' rule:


QNetworkAccessManager::connectToHostEncrypted(host, port, configuration);

Using this function an application can pre-connect to a server without sending any requests. If we were to enable HTTP/2 on such a connection, this connection would be useless for an application that wants to work with HTTP/1.1 and disables HTTP/2 by using request attributes. HTTP/2 can be enabled explicitly by setting the desired list of protocol in the 'configuration' parameter, e.g.:


auto tlsConfig = QSslConfiguration::defaultConfiguration();
tlsConfig.setAllowedNextProtocols({QSslConfiguration::ALPNProtocolHTTP2});
manager.connectToHostEncrypted(host, port, tlsConfig);

Default redirect policy changed

Another change that may affect your networking code is the default redirect policy that QNetworkAccessManager uses in Qt 6. The policy API was first introduced in Qt 5, with the idea in mind of switching to default automatic redirect handling in Qt 6.

QNetworkAccessManager supports several redirect policies, described by the enumeration QNetworkRequest::RedirectPolicy. These policies are:

  • ManualRedirectPolicy,
  • NoLessSafeRedirectPolicy
  • SameOriginRedirectPolicy
  • UserVerifiedRedirectPolicy

Starting from Qt 6 the default redirect policy that QNetworkAccessManager will use when making requests is QNetworkRequest::NoLessSafeRedirectPolicy (the policy that prohibits redirects from 'https' to 'http').

In case you have an application, that relies on a slot connected to QNetworkReply::redirected() to handle redirects, you will have to set the policy to QNetworkRequest::ManualRedirectPolicy with Qt 6:

req.setAttribute(RedirectPolicyAttribute, ManualRedirectPolicy);

Bearer management was removed

Bearer management was the name given to the machinery that was historically needed to support roaming and manage connectivity on certain devices. It could also select which of the device's network interfaces to use when connecting to the internet. Today this is no longer the case since operating systems typically handle roaming on their own and will automatically choose the best network interface to send data over, often providing no way (or no convenient way) for an application to influence these choices.

Although QNetworkManager provided APIs to chose which interface to use or to configure the interface in use, this new reality meant that no currently supported platforms provided a reliable way for Qt to do as requested. At the same time, the attempt to keep track of network status triggered various issues, such as heavy Wifi traffic on Windows causing high ping-times, or, after a brief network outage, this could keep Qt from recognising that the network was back, causing all subsequent requests to fail.

Given that the new reality also makes bearer management largely redundant, we decided that it was time to retire this part of Qt Network.

One feature that was still useful was the ability to check connectivity and get notified of connectivity changes. So far in Qt 6.0 it has not seen a replacement, although we've gathered up some use-cases and hope to have a replacement for that functionality in the coming minor releases. If you have specific use-cases you would like to see covered, or you want to track this task's progress, then please head over to QTBUG-86966.

QSslSocket

In Qt 6 QSslSocket received an additional API, providing information about alert messages which were received or sent as a part of the TLS protocol. We have two new enumerations:

  • QSsl::AlertLevel describes how serious problem is
  • QSsl::AlertType indicates the type of problem

There are two new signals in QSslSocket:

  • QSslSocket::alertSent()
  • QSslSocket::alertReceived()

When possible, these signals also report the textual description of the problem (if provided by the TLS library).

Another change, related to TLS handshake, is the ability to report errors encountered during handshake early, while the handshake is still in progress. These errors are reported directly from the verification callback. QSslConfiguration has a new getter and setter:

  • bool QSslConfiguration::handshakeMustInterruptOnError() const;
  • void QSslConfiguration::setHandshakeMustInterruptOnError() const;

And QSslSocket has a new signal:

  • void handshakeInterruptedOnError();

and a function to say the error must ignored:

  • void continueInterruptedHandshake();

If errors are not ignored, the underlying TLS library will send alert message to the peer.

This API is a little low-level and not of much interest for an application that uses QNetworkAccessManager. But it can be quite handy for debugging purposes or when implementing some error or warning handling logic, if you work with QSslSocket directly. This change at the moment is working with our OpenSSL backend; other TLS libraries we use do not have required API or do not provide the complete information we need.

TLS 1.3 and above

Well, this part is not exactly about what Qt 6 already has. It's also about possible future developments. As of now, Qt 6 supports the latest version of TLS protocol, which is 1.3, thanks to our OpenSSL back-end (Qt 5 also supports TLS 1.3). We are working on enabling TLS 1.3 in the Schannel backend too. Unfortunately, SecureTransport was deprecated by Apple with no available TLS library as a replacement, only some higher-level frameworks built on top of BoringSSL (note the irony of this!). This means that for our Darwin users we will provide an alternative in future. And in case of QNetworkAccessManager, a new QNetworkAccessBackend implemented on top of Apple's classes like NSUrlSession can be quite handy (with TLS 1.3 and experimental HTTP/3 support as a bonus included!).

Thank you for reading this blog post. Do not hesitate and share your opinion, comments or suggest an improvement!


Blog Topics:

Comments