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:
parent
9e7d769462
commit
8b9d246225
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
|
Loading…
Reference in New Issue
Block a user