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 <edward.welbourne@qt.io>
This commit is contained in:
parent
75b5db3ce6
commit
50eb44cc9b
@ -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)
|
||||
|
@ -93,7 +93,8 @@ public:
|
||||
enum ConnectionType {
|
||||
ConnectionTypeHTTP,
|
||||
ConnectionTypeSPDY,
|
||||
ConnectionTypeHTTP2
|
||||
ConnectionTypeHTTP2,
|
||||
ConnectionTypeHTTP2Direct
|
||||
};
|
||||
|
||||
#ifndef QT_NO_BEARERMANAGEMENT
|
||||
|
@ -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<QSslSocket *>(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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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<QByteArray> protocols;
|
||||
protocols << QSslConfiguration::ALPNProtocolHTTP2
|
||||
<< QSslConfiguration::NextProtocolHttp1_1;
|
||||
|
@ -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<QNetworkRequest::LoadControl>
|
||||
(newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute,
|
||||
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
|
||||
@ -1239,7 +1245,9 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByte
|
||||
|
||||
q->setAttribute(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 {
|
||||
|
@ -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
|
||||
|
@ -92,6 +92,7 @@ public:
|
||||
HTTP2WasUsedAttribute,
|
||||
OriginalContentLengthAttribute,
|
||||
RedirectPolicyAttribute,
|
||||
Http2DirectAttribute,
|
||||
|
||||
User = 1000,
|
||||
UserMax = 32767
|
||||
|
Loading…
Reference in New Issue
Block a user