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:
Timur Pocheptsov 2017-07-31 14:58:29 +02:00
parent 75b5db3ce6
commit 50eb44cc9b
9 changed files with 72 additions and 9 deletions

View File

@ -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)

View File

@ -93,7 +93,8 @@ public:
enum ConnectionType {
ConnectionTypeHTTP,
ConnectionTypeSPDY,
ConnectionTypeHTTP2
ConnectionTypeHTTP2,
ConnectionTypeHTTP2Direct
};
#ifndef QT_NO_BEARERMANAGEMENT

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -92,6 +92,7 @@ public:
HTTP2WasUsedAttribute,
OriginalContentLengthAttribute,
RedirectPolicyAttribute,
Http2DirectAttribute,
User = 1000,
UserMax = 32767