From 50eb44cc9bd47fd91e01d36e0cb0d124cfc6e736 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Mon, 31 Jul 2017 14:58:29 +0200 Subject: Introduce Http2DirectAttribute Now that we have a proper ALPN/NPN + Protocol Upgrade, we can also add H2Direct - this can be useful for our users that have to work with either Secure Transport or a TLS implementation not supporting ALPN/NPN and with 'h2direct' servers in case they have prior knowledge of HTTP/2 support. The difference with RFC 7540 is the fact we also allow this 'direct' in case of 'https' scheme (it appears existing HTTP/2 server implementations support such mode too). [ChangeLog][QtNetwork] Add Http2DirectAttribute to enable 'direct' HTTP/2 protocol without ALPN/NPN and without protocol upgrade negotiations. Task-number: QTBUG-61397 Change-Id: I0499d33ec45dede765890059fd9542dab236bd5d Reviewed-by: Edward Welbourne --- src/network/access/qhttpnetworkconnection.cpp | 6 +++-- src/network/access/qhttpnetworkconnection_p.h | 3 ++- .../access/qhttpnetworkconnectionchannel.cpp | 26 ++++++++++++++++++---- src/network/access/qhttpnetworkrequest.cpp | 14 +++++++++++- src/network/access/qhttpnetworkrequest_p.h | 4 ++++ src/network/access/qhttpthreaddelegate.cpp | 5 +++++ src/network/access/qnetworkreplyhttpimpl.cpp | 10 ++++++++- src/network/access/qnetworkrequest.cpp | 12 ++++++++++ src/network/access/qnetworkrequest.h | 1 + 9 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 0b474ba116..107d22f14d 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -83,10 +83,11 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &host networkLayerState(Unknown), hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true) , activeChannelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2 + || type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct #ifndef QT_NO_SSL - || type == QHttpNetworkConnection::ConnectionTypeSPDY + || type == QHttpNetworkConnection::ConnectionTypeSPDY #endif - ? 1 : defaultHttpChannelCount) + ? 1 : defaultHttpChannelCount) , channelCount(defaultHttpChannelCount) #ifndef QT_NO_NETWORKPROXY , networkProxy(QNetworkProxy::NoProxy) @@ -1065,6 +1066,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() } break; } + case QHttpNetworkConnection::ConnectionTypeHTTP2Direct: case QHttpNetworkConnection::ConnectionTypeHTTP2: case QHttpNetworkConnection::ConnectionTypeSPDY: { if (channels[0].spdyRequestsToSend.isEmpty() && channels[0].switchedToHttp2) diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index f01a2318a5..22376aa696 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -93,7 +93,8 @@ public: enum ConnectionType { ConnectionTypeHTTP, ConnectionTypeSPDY, - ConnectionTypeHTTP2 + ConnectionTypeHTTP2, + ConnectionTypeHTTP2Direct }; #ifndef QT_NO_BEARERMANAGEMENT diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index b1ae29427e..f21acc7574 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -438,6 +438,10 @@ void QHttpNetworkConnectionChannel::allDone() return; } + // For clear text HTTP/2 we tried to upgrade from HTTP/1.1 to HTTP/2; for + // ConnectionTypeHTTP2Direct we can never be here in case of failure + // (after an attempt to read HTTP/1.1 as HTTP/2 frames) or we have a normal + // HTTP/2 response and thus can skip this test: if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 && !ssl && !switchedToHttp2) { if (Http2::is_protocol_upgraded(*reply)) { @@ -884,6 +888,14 @@ void QHttpNetworkConnectionChannel::_q_connected() connection->setSslContext(socketSslContext); } #endif + } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { + state = QHttpNetworkConnectionChannel::IdleState; + protocolHandler.reset(new QHttp2ProtocolHandler(this)); + if (spdyRequestsToSend.count() > 0) { + // In case our peer has sent us its settings (window size, max concurrent streams etc.) + // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection). + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } } else { state = QHttpNetworkConnectionChannel::IdleState; const bool tryProtocolUpgrade = connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2; @@ -1116,7 +1128,10 @@ void QHttpNetworkConnectionChannel::_q_encrypted() QSslSocket *sslSocket = qobject_cast(socket); Q_ASSERT(sslSocket); - if (!protocolHandler) { + if (!protocolHandler && connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { + // ConnectionTypeHTTP2Direct does not rely on ALPN/NPN to negotiate HTTP/2, + // after establishing a secure connection we immediately start sending + // HTTP/2 frames. switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) { case QSslConfiguration::NextProtocolNegotiationNegotiated: case QSslConfiguration::NextProtocolNegotiationUnsupported: { @@ -1182,7 +1197,8 @@ void QHttpNetworkConnectionChannel::_q_encrypted() emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, "detected unknown Next Protocol Negotiation protocol"); } - } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) { + } else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 + || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { // We have to reset QHttp2ProtocolHandler's state machine, it's a new // connection and the handler's state is unique per connection. protocolHandler.reset(new QHttp2ProtocolHandler(this)); @@ -1194,10 +1210,12 @@ void QHttpNetworkConnectionChannel::_q_encrypted() pendingEncrypt = false; if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY || - connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) { + connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 || + connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { // we call setSpdyWasUsed(true) on the replies in the SPDY handler when the request is sent if (spdyRequestsToSend.count() > 0) { - // wait for data from the server first (e.g. initial window, max concurrent requests) + // In case our peer has sent us its settings (window size, max concurrent streams etc.) + // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection). QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } } else { // HTTP diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 60b566299f..bd34ac7e05 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -48,7 +48,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Oper QHttpNetworkRequest::Priority pri, const QUrl &newUrl) : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0), autoDecompress(false), pipeliningAllowed(false), spdyAllowed(false), http2Allowed(false), - withCredentials(true), preConnect(false), redirectCount(0), + http2Direct(false), withCredentials(true), preConnect(false), redirectCount(0), redirectPolicy(QNetworkRequest::ManualRedirectPolicy) { } @@ -63,6 +63,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest pipeliningAllowed(other.pipeliningAllowed), spdyAllowed(other.spdyAllowed), http2Allowed(other.http2Allowed), + http2Direct(other.http2Direct), withCredentials(other.withCredentials), ssl(other.ssl), preConnect(other.preConnect), @@ -85,6 +86,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot && (pipeliningAllowed == other.pipeliningAllowed) && (spdyAllowed == other.spdyAllowed) && (http2Allowed == other.http2Allowed) + && (http2Direct == other.http2Direct) // we do not clear the customVerb in setOperation && (operation != QHttpNetworkRequest::Custom || (customVerb == other.customVerb)) && (withCredentials == other.withCredentials) @@ -350,6 +352,16 @@ void QHttpNetworkRequest::setHTTP2Allowed(bool b) d->http2Allowed = b; } +bool QHttpNetworkRequest::isHTTP2Direct() const +{ + return d->http2Direct; +} + +void QHttpNetworkRequest::setHTTP2Direct(bool b) +{ + d->http2Direct = b; +} + bool QHttpNetworkRequest::withCredentials() const { return d->withCredentials; diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index ecf8856ded..00f0e0df97 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -121,6 +121,9 @@ public: bool isHTTP2Allowed() const; void setHTTP2Allowed(bool b); + bool isHTTP2Direct() const; + void setHTTP2Direct(bool b); + bool withCredentials() const; void setWithCredentials(bool b); @@ -172,6 +175,7 @@ public: bool pipeliningAllowed; bool spdyAllowed; bool http2Allowed; + bool http2Direct; bool withCredentials; bool ssl; bool preConnect; diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index 3d17664ed4..c4225536af 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -292,12 +292,17 @@ void QHttpThreadDelegate::startRequest() QHttpNetworkConnection::ConnectionType connectionType = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2 : QHttpNetworkConnection::ConnectionTypeHTTP; + if (httpRequest.isHTTP2Direct()) { + Q_ASSERT(!httpRequest.isHTTP2Allowed()); + connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2Direct; + } #ifndef QT_NO_SSL if (ssl && !incomingSslConfiguration.data()) incomingSslConfiguration.reset(new QSslConfiguration); if (httpRequest.isHTTP2Allowed() && ssl) { + // With HTTP2Direct we do not try any protocol negotiation. QList protocols; protocols << QSslConfiguration::ALPNProtocolHTTP2 << QSslConfiguration::NextProtocolHttp1_1; diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 84b1ddf5ac..55eb7d4f08 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -762,6 +762,12 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq if (request.attribute(QNetworkRequest::HTTP2AllowedAttribute).toBool()) httpRequest.setHTTP2Allowed(true); + if (request.attribute(QNetworkRequest::Http2DirectAttribute).toBool()) { + // Intentionally mutually exclusive - cannot be both direct and 'allowed' + httpRequest.setHTTP2Direct(true); + httpRequest.setHTTP2Allowed(false); + } + if (static_cast (newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual) @@ -1239,7 +1245,9 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QListsetAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu); const QVariant http2Allowed = request.attribute(QNetworkRequest::HTTP2AllowedAttribute); - if (http2Allowed.isValid() && http2Allowed.toBool()) { + const QVariant http2Direct = request.attribute(QNetworkRequest::Http2DirectAttribute); + if ((http2Allowed.isValid() && http2Allowed.toBool()) + || (http2Direct.isValid() && http2Direct.toBool())) { q->setAttribute(QNetworkRequest::HTTP2WasUsedAttribute, spdyWasUsed); q->setAttribute(QNetworkRequest::SpdyWasUsedAttribute, false); } else { diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 60701d45be..277190b3bd 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -298,6 +298,18 @@ QT_BEGIN_NAMESPACE This attribute obsoletes FollowRedirectsAttribute. (This value was introduced in 5.9.) + \value Http2DirectAttribute + Requests only, type: QMetaType::Bool (default: false) + If set, this attribute will force QNetworkAccessManager to use + HTTP/2 protocol without initial HTTP/2 protocol negotiation. + Use of this attribute implies prior knowledge that a particular + server supports HTTP/2. The attribute works with SSL or 'cleartext' + HTTP/2. If a server turns out to not support HTTP/2, when HTTP/2 direct + was specified, QNetworkAccessManager gives up, without attempting to + fall back to HTTP/1.1. If both HTTP2AllowedAttribute and + Http2DirectAttribute are set, Http2DirectAttribute takes priority. + (This value was introduced in 5.10.) + \value User Special type. Additional information can be passed in QVariants with types ranging from User to UserMax. The default diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 68d4ae6d6b..cddd81bd19 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -92,6 +92,7 @@ public: HTTP2WasUsedAttribute, OriginalContentLengthAttribute, RedirectPolicyAttribute, + Http2DirectAttribute, User = 1000, UserMax = 32767 -- cgit v1.2.3