QHttpNetworkConnection: fall back gracefully to HTTP/1.1

Both SPDY and HTTP/2 work with a single qhttpnetworkchannel (and this means one
socket per qhttpnetworkconnection). Normally, HTTP/1.1 connection is using up to 6
channels/sockets though. At the moment a failure to negotiate SPDY/HTTP/2 leaves us
with a downgraded HTTP/1.1 connection (with only one channel vs. default 6).
Since we initialize channels (and establish connections) in a 'lazy' manner
it's ok to pre-allocate all 6 channels and then either use 1 (if SPDY/HTTP/2
indeed was negotiated) or switch back to 6 in case of failure.

Change-Id: Ia6c3061463c4d634aaed05ce0dde47bfb5e24dd8
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Timur Pocheptsov 2017-02-20 10:34:47 +01:00
parent 9e7d769462
commit 8b9d246225
3 changed files with 59 additions and 28 deletions

View File

@ -82,27 +82,31 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &host
: state(RunningState), : state(RunningState),
networkLayerState(Unknown), networkLayerState(Unknown),
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true) hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true)
, activeChannelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY || type == QHttpNetworkConnection::ConnectionTypeHTTP2) || type == QHttpNetworkConnection::ConnectionTypeSPDY
? 1 : defaultHttpChannelCount) #endif
#else ? 1 : defaultHttpChannelCount)
, channelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2 ? 1 : defaultHttpChannelCount) , channelCount(defaultHttpChannelCount)
#endif // QT_NO_SSL
#ifndef QT_NO_NETWORKPROXY #ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy) , networkProxy(QNetworkProxy::NoProxy)
#endif #endif
, preConnectRequests(0) , preConnectRequests(0)
, connectionType(type) , connectionType(type)
{ {
// We allocate all 6 channels even if it's SPDY or HTTP/2 enabled
// connection: in case the protocol negotiation via NPN/ALPN fails,
// we will have normally working HTTP/1.1.
Q_ASSERT(channelCount >= activeChannelCount);
channels = new QHttpNetworkConnectionChannel[channelCount]; channels = new QHttpNetworkConnectionChannel[channelCount];
} }
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type) QHttpNetworkConnection::ConnectionType type)
: state(RunningState), networkLayerState(Unknown), : state(RunningState), networkLayerState(Unknown),
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true), hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true),
channelCount(channelCount) activeChannelCount(connectionCount), channelCount(connectionCount)
#ifndef QT_NO_NETWORKPROXY #ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy) , networkProxy(QNetworkProxy::NoProxy)
#endif #endif
@ -147,7 +151,7 @@ void QHttpNetworkConnectionPrivate::pauseConnection()
state = PausedState; state = PausedState;
// Disable all socket notifiers // Disable all socket notifiers
for (int i = 0; i < channelCount; i++) { for (int i = 0; i < activeChannelCount; i++) {
if (channels[i].socket) { if (channels[i].socket) {
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
if (encrypt) if (encrypt)
@ -163,7 +167,7 @@ void QHttpNetworkConnectionPrivate::resumeConnection()
{ {
state = RunningState; state = RunningState;
// Enable all socket notifiers // Enable all socket notifiers
for (int i = 0; i < channelCount; i++) { for (int i = 0; i < activeChannelCount; i++) {
if (channels[i].socket) { if (channels[i].socket) {
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
if (encrypt) if (encrypt)
@ -184,7 +188,7 @@ void QHttpNetworkConnectionPrivate::resumeConnection()
int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const
{ {
for (int i = 0; i < channelCount; ++i) for (int i = 0; i < activeChannelCount; ++i)
if (channels[i].socket == socket) if (channels[i].socket == socket)
return i; return i;
@ -210,7 +214,7 @@ bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QAbstractSocket *sock
channels[otherSocket].ensureConnection(); channels[otherSocket].ensureConnection();
} }
if (channelCount == 1) { if (activeChannelCount < channelCount) {
if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6) if (networkLayerState == HostLookupPending || networkLayerState == IPv4or6)
networkLayerState = QHttpNetworkConnectionPrivate::Unknown; networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
channels[0].close(); channels[0].close();
@ -405,7 +409,7 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica
// select another channel // select another channel
QAuthenticator* otherAuth = 0; QAuthenticator* otherAuth = 0;
for (int i = 0; i < channelCount; ++i) { for (int i = 0; i < activeChannelCount; ++i) {
if (i == fromChannel) if (i == fromChannel)
continue; continue;
if (isProxy) if (isProxy)
@ -886,7 +890,7 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply)
Q_Q(QHttpNetworkConnection); Q_Q(QHttpNetworkConnection);
// check if the reply is currently being processed or it is pipelined in // check if the reply is currently being processed or it is pipelined in
for (int i = 0; i < channelCount; ++i) { for (int i = 0; i < activeChannelCount; ++i) {
// is the reply associated the currently processing of this channel? // is the reply associated the currently processing of this channel?
if (channels[i].reply == reply) { if (channels[i].reply == reply) {
channels[i].reply = 0; channels[i].reply = 0;
@ -989,7 +993,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
return; return;
//resend the necessary ones. //resend the necessary ones.
for (int i = 0; i < channelCount; ++i) { for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) { if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
channels[i].resendCurrent = false; channels[i].resendCurrent = false;
@ -1009,7 +1013,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
return; return;
// try to get a free AND connected socket // try to get a free AND connected socket
for (int i = 0; i < channelCount; ++i) { for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].socket) { if (channels[i].socket) {
if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) { if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket)) if (dequeueRequest(channels[i].socket))
@ -1047,7 +1051,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// return fast if there is nothing to pipeline // return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return; return;
for (int i = 0; i < channelCount; i++) for (int i = 0; i < activeChannelCount; i++)
if (channels[i].socket && channels[i].socket->state() == QAbstractSocket::ConnectedState) if (channels[i].socket && channels[i].socket->state() == QAbstractSocket::ConnectedState)
fillPipeline(channels[i].socket); fillPipeline(channels[i].socket);
@ -1063,7 +1067,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
int normalRequests = queuedRequests - preConnectRequests; int normalRequests = queuedRequests - preConnectRequests;
neededOpenChannels = qMax(normalRequests, preConnectRequests); neededOpenChannels = qMax(normalRequests, preConnectRequests);
} }
for (int i = 0; i < channelCount && neededOpenChannels > 0; ++i) { for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
bool connectChannel = false; bool connectChannel = false;
if (channels[i].socket) { if (channels[i].socket) {
if ((channels[i].socket->state() == QAbstractSocket::ConnectingState) if ((channels[i].socket->state() == QAbstractSocket::ConnectingState)
@ -1093,7 +1097,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply) void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply)
{ {
for (int i = 0 ; i < channelCount; ++i) { for (int i = 0 ; i < activeChannelCount; ++i) {
if (channels[i].reply == reply) { if (channels[i].reply == reply) {
// emulate a readyRead() from the socket // emulate a readyRead() from the socket
QMetaObject::invokeMethod(&channels[i], "_q_readyRead", Qt::QueuedConnection); QMetaObject::invokeMethod(&channels[i], "_q_readyRead", Qt::QueuedConnection);
@ -1212,7 +1216,7 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info)
// connection will then be disconnected. // connection will then be disconnected.
void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup() void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup()
{ {
if (channelCount > 1) { if (activeChannelCount > 1) {
// At this time all channels should be unconnected. // At this time all channels should be unconnected.
Q_ASSERT(!channels[0].isSocketBusy()); Q_ASSERT(!channels[0].isSocketBusy());
Q_ASSERT(!channels[1].isSocketBusy()); Q_ASSERT(!channels[1].isSocketBusy());
@ -1250,7 +1254,7 @@ void QHttpNetworkConnectionPrivate::startNetworkLayerStateLookup()
void QHttpNetworkConnectionPrivate::networkLayerDetected(QAbstractSocket::NetworkLayerProtocol protocol) void QHttpNetworkConnectionPrivate::networkLayerDetected(QAbstractSocket::NetworkLayerProtocol protocol)
{ {
for (int i = 0 ; i < channelCount; ++i) { for (int i = 0 ; i < activeChannelCount; ++i) {
if ((channels[i].networkLayerPreference != protocol) && (channels[i].state == QHttpNetworkConnectionChannel::ConnectingState)) { if ((channels[i].networkLayerPreference != protocol) && (channels[i].state == QHttpNetworkConnectionChannel::ConnectingState)) {
channels[i].close(); channels[i].close();
} }
@ -1347,7 +1351,7 @@ void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy)
d->networkProxy = networkProxy; d->networkProxy = networkProxy;
// update the authenticator // update the authenticator
if (!d->networkProxy.user().isEmpty()) { if (!d->networkProxy.user().isEmpty()) {
for (int i = 0; i < d->channelCount; ++i) { for (int i = 0; i < d->activeChannelCount; ++i) {
d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user()); d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user());
d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password()); d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password());
} }
@ -1363,7 +1367,7 @@ QNetworkProxy QHttpNetworkConnection::cacheProxy() const
void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy) void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy)
{ {
Q_D(QHttpNetworkConnection); Q_D(QHttpNetworkConnection);
for (int i = 0; i < d->channelCount; ++i) for (int i = 0; i < d->activeChannelCount; ++i)
d->channels[i].setProxy(networkProxy); d->channels[i].setProxy(networkProxy);
} }
@ -1395,7 +1399,7 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config
return; return;
// set the config on all channels // set the config on all channels
for (int i = 0; i < d->channelCount; ++i) for (int i = 0; i < d->activeChannelCount; ++i)
d->channels[i].setSslConfiguration(config); d->channels[i].setSslConfiguration(config);
} }
@ -1418,7 +1422,7 @@ void QHttpNetworkConnection::ignoreSslErrors(int channel)
return; return;
if (channel == -1) { // ignore for all channels if (channel == -1) { // ignore for all channels
for (int i = 0; i < d->channelCount; ++i) { for (int i = 0; i < d->activeChannelCount; ++i) {
d->channels[i].ignoreSslErrors(); d->channels[i].ignoreSslErrors();
} }
@ -1434,7 +1438,7 @@ void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int
return; return;
if (channel == -1) { // ignore for all channels if (channel == -1) { // ignore for all channels
for (int i = 0; i < d->channelCount; ++i) { for (int i = 0; i < d->activeChannelCount; ++i) {
d->channels[i].ignoreSslErrors(errors); d->channels[i].ignoreSslErrors(errors);
} }

View File

@ -243,6 +243,9 @@ public:
bool encrypt; bool encrypt;
bool delayIpv4; bool delayIpv4;
// Number of channels we are trying to use at the moment:
int activeChannelCount;
// The total number of channels we reserved:
const int channelCount; const int channelCount;
QTimer delayedConnectionTimer; QTimer delayedConnectionTimer;
QHttpNetworkConnectionChannel *channels; // parallel connections to the server QHttpNetworkConnectionChannel *channels; // parallel connections to the server

View File

@ -1082,12 +1082,36 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
} }
} }
Q_FALLTHROUGH(); Q_FALLTHROUGH();
case QSslConfiguration::NextProtocolNegotiationNone: case QSslConfiguration::NextProtocolNegotiationNone: {
protocolHandler.reset(new QHttpProtocolHandler(this)); protocolHandler.reset(new QHttpProtocolHandler(this));
QList<QByteArray> protocols = sslConfiguration.allowedNextProtocols();
const int nProtocols = protocols.size();
// Clear the protocol that we failed to negotiate, so we do not try
// it again on other channels that our connection can create/open.
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2)
protocols.removeAll(QSslConfiguration::ALPNProtocolHTTP2);
else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY)
protocols.removeAll(QSslConfiguration::NextProtocolSpdy3_0);
if (nProtocols > protocols.size()) {
sslConfiguration.setAllowedNextProtocols(protocols);
const int channelCount = connection->d_func()->channelCount;
for (int i = 0; i < channelCount; ++i)
connection->d_func()->channels[i].setSslConfiguration(sslConfiguration);
}
connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP); connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
// re-queue requests from SPDY queue to HTTP queue, if any // We use only one channel for SPDY or HTTP/2, but normally six for
requeueSpdyRequests(); // HTTP/1.1 - let's restore this number to the reserved number of
// channels:
if (connection->d_func()->activeChannelCount < connection->d_func()->channelCount) {
connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
// re-queue requests from SPDY queue to HTTP queue, if any
requeueSpdyRequests();
}
break; break;
}
default: default:
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError, emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"detected unknown Next Protocol Negotiation protocol"); "detected unknown Next Protocol Negotiation protocol");