diff options
Diffstat (limited to 'src/network/access')
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 695 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnection_p.h | 30 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 624 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel_p.h | 35 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply.cpp | 49 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply_p.h | 8 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest.cpp | 13 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest_p.h | 5 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesshttpbackend.cpp | 5 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 8 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 10 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 2 |
12 files changed, 926 insertions, 558 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index bf4109a..7b5a6e2 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -65,19 +65,37 @@ QT_BEGIN_NAMESPACE -const int QHttpNetworkConnectionPrivate::channelCount = 6; +const int QHttpNetworkConnectionPrivate::defaultChannelCount = 6; + +// the maximum amount of requests that might be pipelined into a socket +// from what was suggested, 3 seems to be OK +const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; + QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt) : hostName(hostName), port(port), encrypt(encrypt), + channelCount(defaultChannelCount), pendingAuthSignal(false), pendingProxyAuthSignal(false) #ifndef QT_NO_NETWORKPROXY , networkProxy(QNetworkProxy::NoProxy) #endif +{ + channels = new QHttpNetworkConnectionChannel[channelCount]; +} +QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt) +: hostName(hostName), port(port), encrypt(encrypt), + channelCount(channelCount), + pendingAuthSignal(false), pendingProxyAuthSignal(false) +#ifndef QT_NO_NETWORKPROXY + , networkProxy(QNetworkProxy::NoProxy) +#endif { channels = new QHttpNetworkConnectionChannel[channelCount]; } + + QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate() { for (int i = 0; i < channelCount; ++i) { @@ -141,17 +159,6 @@ qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const return reply.d_func()->responseData.sizeNextBlock(); } -qint64 QHttpNetworkConnectionPrivate::compressedBytesAvailable(const QHttpNetworkReply &reply) const -{ - return reply.d_func()->compressedData.size(); -} - -void QHttpNetworkConnectionPrivate::eraseData(QHttpNetworkReply *reply) -{ - reply->d_func()->compressedData.clear(); - reply->d_func()->responseData.clear(); -} - void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) { QHttpNetworkRequest &request = messagePair.first; @@ -225,231 +232,8 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) reply->d_func()->requestIsPrepared = true; } -bool QHttpNetworkConnectionPrivate::ensureConnection(QAbstractSocket *socket) -{ - // make sure that this socket is in a connected state, if not initiate - // connection to the host. - if (socket->state() != QAbstractSocket::ConnectedState) { - // connect to the host if not already connected. - int index = indexOf(socket); - // resend this request after we receive the disconnected signal - if (socket->state() == QAbstractSocket::ClosingState) { - channels[index].resendCurrent = true; - return false; - } - channels[index].state = QHttpNetworkConnectionChannel::ConnectingState; - channels[index].pendingEncrypt = encrypt; - - // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done" - // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the - // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not - // check the "phase" for generating the Authorization header. NTLM authentication is a two stage - // process & needs the "phase". To make sure the QAuthenticator uses the current username/password - // the phase is reset to Start. - QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[index].authenticator); - if (priv && priv->phase == QAuthenticatorPrivate::Done) - priv->phase = QAuthenticatorPrivate::Start; - priv = QAuthenticatorPrivate::getPrivate(channels[index].proxyAuthenticator); - if (priv && priv->phase == QAuthenticatorPrivate::Done) - priv->phase = QAuthenticatorPrivate::Start; - - QString connectHost = hostName; - qint16 connectPort = port; - -#ifndef QT_NO_NETWORKPROXY - // HTTPS always use transparent proxy. - if (networkProxy.type() != QNetworkProxy::NoProxy && !encrypt) { - connectHost = networkProxy.hostName(); - connectPort = networkProxy.port(); - } -#endif - if (encrypt) { -#ifndef QT_NO_OPENSSL - QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); - sslSocket->connectToHostEncrypted(connectHost, connectPort); - if (channels[index].ignoreAllSslErrors) - sslSocket->ignoreSslErrors(); - sslSocket->ignoreSslErrors(channels[index].ignoreSslErrorsList); -#else - emitReplyError(socket, channels[index].reply, QNetworkReply::ProtocolUnknownError); -#endif - } else { - socket->connectToHost(connectHost, connectPort); - } - return false; - } - return true; -} - -bool QHttpNetworkConnectionPrivate::sendRequest(QAbstractSocket *socket) -{ - Q_Q(QHttpNetworkConnection); - int i = indexOf(socket); - switch (channels[i].state) { - case QHttpNetworkConnectionChannel::IdleState: { // write the header - if (!ensureConnection(socket)) { - // wait for the connection (and encryption) to be done - // sendRequest will be called again from either - // _q_connected or _q_encrypted - return false; - } - channels[i].written = 0; // excluding the header - channels[i].bytesTotal = 0; - if (channels[i].reply) { - channels[i].reply->d_func()->clear(); - channels[i].reply->d_func()->connection = q; - channels[i].reply->d_func()->autoDecompress = channels[i].request.d->autoDecompress; - } - channels[i].state = QHttpNetworkConnectionChannel::WritingState; - channels[i].pendingEncrypt = false; - // if the url contains authentication parameters, use the new ones - // both channels will use the new authentication parameters - if (!channels[i].request.url().userInfo().isEmpty()) { - QUrl url = channels[i].request.url(); - QAuthenticator &auth = channels[i].authenticator; - if (url.userName() != auth.user() - || (!url.password().isEmpty() && url.password() != auth.password())) { - auth.setUser(url.userName()); - auth.setPassword(url.password()); - copyCredentials(i, &auth, false); - } - // clear the userinfo, since we use the same request for resending - // userinfo in url can conflict with the one in the authenticator - url.setUserInfo(QString()); - channels[i].request.setUrl(url); - } - createAuthorization(socket, channels[i].request); -#ifndef QT_NO_NETWORKPROXY - QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request, - (networkProxy.type() != QNetworkProxy::NoProxy)); -#else - QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request, - false); -#endif - socket->write(header); - QNonContiguousByteDevice* uploadByteDevice = channels[i].request.uploadByteDevice(); - if (uploadByteDevice) { - // connect the signals so this function gets called again - QObject::connect(uploadByteDevice, SIGNAL(readyRead()), &channels[i], SLOT(_q_uploadDataReadyRead())); - - channels[i].bytesTotal = channels[i].request.contentLength(); - } else { - socket->flush(); // ### Remove this when pipelining is implemented. We want less TCP packets! - channels[i].state = QHttpNetworkConnectionChannel::WaitingState; - break; - } - // write the initial chunk together with the headers - // fall through - } - case QHttpNetworkConnectionChannel::WritingState: - { - // write the data - QNonContiguousByteDevice* uploadByteDevice = channels[i].request.uploadByteDevice(); - if (!uploadByteDevice || channels[i].bytesTotal == channels[i].written) { - if (uploadByteDevice) - emit channels[i].reply->dataSendProgress(channels[i].written, channels[i].bytesTotal); - channels[i].state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response - sendRequest(socket); - break; - } - - // only feed the QTcpSocket buffer when there is less than 32 kB in it - const qint64 socketBufferFill = 32*1024; - const qint64 socketWriteMaxSize = 16*1024; - - -#ifndef QT_NO_OPENSSL - QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); - // if it is really an ssl socket, check more than just bytesToWrite() - while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0)) - <= socketBufferFill && channels[i].bytesTotal != channels[i].written) -#else - while (socket->bytesToWrite() <= socketBufferFill - && channels[i].bytesTotal != channels[i].written) -#endif - { - // get pointer to upload data - qint64 currentReadSize; - qint64 desiredReadSize = qMin(socketWriteMaxSize, channels[i].bytesTotal - channels[i].written); - const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize); - - if (currentReadSize == -1) { - // premature eof happened - emitReplyError(socket, channels[i].reply, QNetworkReply::UnknownNetworkError); - return false; - break; - } else if (readPointer == 0 || currentReadSize == 0) { - // nothing to read currently, break the loop - break; - } else { - qint64 currentWriteSize = socket->write(readPointer, currentReadSize); - if (currentWriteSize == -1 || currentWriteSize != currentReadSize) { - // socket broke down - emitReplyError(socket, channels[i].reply, QNetworkReply::UnknownNetworkError); - return false; - } else { - channels[i].written += currentWriteSize; - uploadByteDevice->advanceReadPointer(currentWriteSize); - - emit channels[i].reply->dataSendProgress(channels[i].written, channels[i].bytesTotal); - - if (channels[i].written == channels[i].bytesTotal) { - // make sure this function is called once again - channels[i].state = QHttpNetworkConnectionChannel::WaitingState; - sendRequest(socket); - break; - } - } - } - } - break; - } - - case QHttpNetworkConnectionChannel::WaitingState: - { - QNonContiguousByteDevice* uploadByteDevice = channels[i].request.uploadByteDevice(); - if (uploadByteDevice) { - QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), &channels[i], SLOT(_q_uploadDataReadyRead())); - } - // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called - // this is needed if the sends an reply before we have finished sending the request. In that - // case receiveReply had been called before but ignored the server reply - receiveReply(socket, channels[i].reply); - break; - } - case QHttpNetworkConnectionChannel::ReadingState: - case QHttpNetworkConnectionChannel::Wait4AuthState: - // ignore _q_bytesWritten in these states - // fall through - default: - break; - } - return true; -} - -bool QHttpNetworkConnectionPrivate::shouldEmitSignals(QHttpNetworkReply *reply) -{ - // for 401 & 407 don't emit the data signals. Content along with these - // responses are send only if the authentication fails. - return (reply && reply->d_func()->statusCode != 401 && reply->d_func()->statusCode != 407); -} - -bool QHttpNetworkConnectionPrivate::expectContent(QHttpNetworkReply *reply) -{ - // check whether we can expect content after the headers (rfc 2616, sec4.4) - if (!reply) - return false; - if ((reply->d_func()->statusCode >= 100 && reply->d_func()->statusCode < 200) - || reply->d_func()->statusCode == 204 || reply->d_func()->statusCode == 304) - return false; - if (reply->d_func()->request.operation() == QHttpNetworkRequest::Head) - return !shouldEmitSignals(reply); - if (reply->d_func()->contentLength() == 0) - return false; - return true; -} void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, @@ -462,271 +246,13 @@ void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, emit reply->finishedWithError(errorCode, reply->d_func()->errorString); int i = indexOf(socket); // remove the corrupt data if any - eraseData(channels[i].reply); - closeChannel(i); + reply->d_func()->eraseData(); + channels[i].close(); // send the next request QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); } } -#ifndef QT_NO_COMPRESS -bool QHttpNetworkConnectionPrivate::expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete) -{ - Q_ASSERT(socket); - Q_ASSERT(reply); - - qint64 total = compressedBytesAvailable(*reply); - if (total >= CHUNK || dataComplete) { - int i = indexOf(socket); - // uncompress the data - QByteArray content, inflated; - content = reply->d_func()->compressedData; - reply->d_func()->compressedData.clear(); - - int ret = Z_OK; - if (content.size()) - ret = reply->d_func()->gunzipBodyPartially(content, inflated); - int retCheck = (dataComplete) ? Z_STREAM_END : Z_OK; - if (ret >= retCheck) { - if (inflated.size()) { - reply->d_func()->totalProgress += inflated.size(); - reply->d_func()->appendUncompressedReplyData(inflated); - if (shouldEmitSignals(reply)) { - // important: At the point of this readyRead(), inflated must be cleared, - // else implicit sharing will trigger memcpy when the user is reading data! - emit reply->readyRead(); - // make sure that the reply is valid - if (channels[i].reply != reply) - return true; - emit reply->dataReadProgress(reply->d_func()->totalProgress, 0); - // make sure that the reply is valid - if (channels[i].reply != reply) - return true; - - } - } - } else { - emitReplyError(socket, reply, QNetworkReply::ProtocolFailure); - return false; - } - } - return true; -} -#endif - -void QHttpNetworkConnectionPrivate::receiveReply(QAbstractSocket *socket, QHttpNetworkReply *reply) -{ - Q_ASSERT(socket); - - Q_Q(QHttpNetworkConnection); - qint64 bytes = 0; - QAbstractSocket::SocketState state = socket->state(); - int i = indexOf(socket); - - // connection might be closed to signal the end of data - if (state == QAbstractSocket::UnconnectedState) { - if (!socket->bytesAvailable()) { - if (reply && reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { - reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; - channels[i].state = QHttpNetworkConnectionChannel::IdleState; - allDone(socket, reply); - } else { - // try to reconnect/resend before sending an error. - if (channels[i].reconnectAttempts-- > 0) { - resendCurrentRequest(socket); - } else if (reply) { - reply->d_func()->errorString = errorDetail(QNetworkReply::RemoteHostClosedError, socket); - emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString); - QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); - } - } - } - } - - // read loop for the response - while (socket->bytesAvailable()) { - QHttpNetworkReplyPrivate::ReplyState state = reply ? reply->d_func()->state : QHttpNetworkReplyPrivate::AllDoneState; - switch (state) { - case QHttpNetworkReplyPrivate::NothingDoneState: - case QHttpNetworkReplyPrivate::ReadingStatusState: { - qint64 statusBytes = reply->d_func()->readStatus(socket); - if (statusBytes == -1) { - // error reading the status, close the socket and emit error - socket->close(); - reply->d_func()->errorString = errorDetail(QNetworkReply::ProtocolFailure, socket); - emit reply->finishedWithError(QNetworkReply::ProtocolFailure, reply->d_func()->errorString); - QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); - break; - } - bytes += statusBytes; - channels[i].lastStatus = reply->d_func()->statusCode; - break; - } - case QHttpNetworkReplyPrivate::ReadingHeaderState: - bytes += reply->d_func()->readHeader(socket); - if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { - if (reply->d_func()->isGzipped() && reply->d_func()->autoDecompress) { - // remove the Content-Length from header - reply->d_func()->removeAutoDecompressHeader(); - } else { - reply->d_func()->autoDecompress = false; - } - if (reply && reply->d_func()->statusCode == 100) { - reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; - break; // ignore - } - if (shouldEmitSignals(reply)) - emit reply->headerChanged(); - if (!expectContent(reply)) { - reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; - channels[i].state = QHttpNetworkConnectionChannel::IdleState; - allDone(socket, reply); - return; - } - } - break; - case QHttpNetworkReplyPrivate::ReadingDataState: { - if (!reply->d_func()->isChunked() && !reply->d_func()->autoDecompress - && reply->d_func()->bodyLength > 0) { - // bulk files like images should fulfill these properties and - // we can therefore save on memory copying - bytes = reply->d_func()->readBodyFast(socket, &reply->d_func()->responseData); - reply->d_func()->totalProgress += bytes; - if (shouldEmitSignals(reply)) { - emit reply->readyRead(); - // make sure that the reply is valid - if (channels[i].reply != reply) - return; - emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength); - // make sure that the reply is valid - if (channels[i].reply != reply) - return; - } - } - else - { - // use the traditional slower reading (for compressed encoding, chunked encoding, - // no content-length etc) - QByteDataBuffer byteDatas; - bytes = reply->d_func()->readBody(socket, &byteDatas); - if (bytes) { - if (reply->d_func()->autoDecompress) - reply->d_func()->appendCompressedReplyData(byteDatas); - else - reply->d_func()->appendUncompressedReplyData(byteDatas); - - if (!reply->d_func()->autoDecompress) { - reply->d_func()->totalProgress += bytes; - if (shouldEmitSignals(reply)) { - // important: At the point of this readyRead(), the byteDatas list must be empty, - // else implicit sharing will trigger memcpy when the user is reading data! - emit reply->readyRead(); - // make sure that the reply is valid - if (channels[i].reply != reply) - return; - emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength); - // make sure that the reply is valid - if (channels[i].reply != reply) - return; - } - } -#ifndef QT_NO_COMPRESS - else if (!expand(socket, reply, false)) { // expand a chunk if possible - return; // ### expand failed - } -#endif - } - } - if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) - break; - // everything done, fall through - } - case QHttpNetworkReplyPrivate::AllDoneState: - channels[i].state = QHttpNetworkConnectionChannel::IdleState; - allDone(socket, reply); - break; - default: - break; - } - } -} - -void QHttpNetworkConnectionPrivate::allDone(QAbstractSocket *socket, QHttpNetworkReply *reply) -{ -#ifndef QT_NO_COMPRESS - // expand the whole data. - if (expectContent(reply) && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) - expand(socket, reply, true); // ### if expand returns false, its an error -#endif - // while handling 401 & 407, we might reset the status code, so save this. - bool emitFinished = shouldEmitSignals(reply); - handleStatus(socket, reply); - // ### at this point there should be no more data on the socket - // close if server requested - int i = indexOf(socket); - if (reply->d_func()->connectionCloseEnabled()) - closeChannel(i); - // queue the finished signal, this is required since we might send new requests from - // slot connected to it. The socket will not fire readyRead signal, if we are already - // in the slot connected to readyRead - if (emitFinished) - QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection); - // reset the reconnection attempts after we receive a complete reply. - // in case of failures, each channel will attempt two reconnects before emitting error. - channels[i].reconnectAttempts = 2; -} - -void QHttpNetworkConnectionPrivate::handleStatus(QAbstractSocket *socket, QHttpNetworkReply *reply) -{ - Q_ASSERT(socket); - Q_ASSERT(reply); - - Q_Q(QHttpNetworkConnection); - - int statusCode = reply->statusCode(); - bool resend = false; - - switch (statusCode) { - case 401: - case 407: - if (handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) { - if (resend) { - int i = indexOf(socket); - - QNonContiguousByteDevice* uploadByteDevice = channels[i].request.uploadByteDevice(); - if (uploadByteDevice) { - if (uploadByteDevice->reset()) { - channels[i].written = 0; - } else { - emitReplyError(socket, reply, QNetworkReply::ContentReSendError); - break; - } - } - - eraseData(reply); - - // also use async _q_startNextRequest so we dont break with closed - // proxy or server connections.. - channels[i].resendCurrent = true; - QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); - } - } else { - int i = indexOf(socket); - emit channels[i].reply->headerChanged(); - emit channels[i].reply->readyRead(); - QNetworkReply::NetworkError errorCode = (statusCode == 407) - ? QNetworkReply::ProxyAuthenticationRequiredError - : QNetworkReply::AuthenticationRequiredError; - reply->d_func()->errorString = errorDetail(errorCode, socket); - emit q->error(errorCode, reply->d_func()->errorString); - emit channels[i].reply->finished(); - } - break; - default: - QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); - } -} - void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy) { Q_ASSERT(auth); @@ -749,6 +275,7 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica } +// handles the authentication for one channel and eventually re-starts the other channels bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend) { @@ -788,8 +315,8 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket if (priv->phase == QAuthenticatorPrivate::Done) { if ((isProxy && pendingProxyAuthSignal) ||(!isProxy && pendingAuthSignal)) { // drop the request - eraseData(channels[i].reply); - closeChannel(i); + reply->d_func()->eraseData(); + channels[i].close(); channels[i].lastStatus = 0; channels[i].state = QHttpNetworkConnectionChannel::Wait4AuthState; return false; @@ -894,7 +421,24 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor return reply; } -void QHttpNetworkConnectionPrivate::unqueueAndSendRequest(QAbstractSocket *socket) +void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair) +{ + Q_Q(QHttpNetworkConnection); + + QHttpNetworkRequest request = pair.first; + switch (request.priority()) { + case QHttpNetworkRequest::HighPriority: + highPriorityQueue.prepend(pair); + break; + case QHttpNetworkRequest::NormalPriority: + case QHttpNetworkRequest::LowPriority: + lowPriorityQueue.prepend(pair); + break; + } + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); +} + +void QHttpNetworkConnectionPrivate::dequeueAndSendRequest(QAbstractSocket *socket) { Q_ASSERT(socket); @@ -908,8 +452,9 @@ void QHttpNetworkConnectionPrivate::unqueueAndSendRequest(QAbstractSocket *socke channels[i].request = messagePair.first; channels[i].reply = messagePair.second; - sendRequest(socket); + // remove before sendRequest! else we might pipeline the same request again highPriorityQueue.removeAt(j); + channels[i].sendRequest(); return; } } @@ -921,28 +466,113 @@ void QHttpNetworkConnectionPrivate::unqueueAndSendRequest(QAbstractSocket *socke prepareRequest(messagePair); channels[i].request = messagePair.first; channels[i].reply = messagePair.second; - sendRequest(socket); + // remove before sendRequest! else we might pipeline the same request again lowPriorityQueue.removeAt(j); + channels[i].sendRequest(); return; } } } -void QHttpNetworkConnectionPrivate::closeChannel(int channel) +// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel +void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) { - channels[channel].close(); + // return fast if there is nothing to pipeline + if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) + return; + + int i = indexOf(socket); + + bool highPriorityQueueProcessingDone = false; + bool lowPriorityQueueProcessingDone = false; + + while (!highPriorityQueueProcessingDone && !lowPriorityQueueProcessingDone) { + // this loop runs once per request we intend to pipeline in. + + if (channels[i].pipeliningSupported != QHttpNetworkConnectionChannel::PipeliningProbablySupported) + return; + + // the current request that is in must already support pipelining + if (!channels[i].request.isPipeliningAllowed()) + return; + + // the current request must be a idempotent (right now we only check GET) + if (channels[i].request.operation() != QHttpNetworkRequest::Get) + return; + + // check if socket is connected + if (socket->state() != QAbstractSocket::ConnectedState) + return; + + // check for resendCurrent + if (channels[i].resendCurrent) + return; + + // we do not like authentication stuff + // ### make sure to be OK with this in later releases + if (!channels[i].authenticator.isNull() || !channels[i].authenticator.user().isEmpty()) + return; + if (!channels[i].proxyAuthenticator.isNull() || !channels[i].proxyAuthenticator.user().isEmpty()) + return; + + // check for pipeline length + if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) + return; + + // must be in ReadingState or WaitingState + if (! (channels[i].state == QHttpNetworkConnectionChannel::WaitingState + || channels[i].state == QHttpNetworkConnectionChannel::ReadingState)) + return; + + highPriorityQueueProcessingDone = fillPipeline(highPriorityQueue, channels[i]); + // not finished with highPriorityQueue? then loop again + if (!highPriorityQueueProcessingDone) + continue; + // highPriorityQueue was processed, now deal with the lowPriorityQueue + lowPriorityQueueProcessingDone = fillPipeline(lowPriorityQueue, channels[i]); + } } -void QHttpNetworkConnectionPrivate::resendCurrentRequest(QAbstractSocket *socket) +// returns true when the processing of a queue has been done +bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel) { - Q_Q(QHttpNetworkConnection); - Q_ASSERT(socket); - int i = indexOf(socket); - closeChannel(i); - channels[i].resendCurrent = true; - QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + if (queue.isEmpty()) + return true; + + for (int i = 0; i < queue.length(); i++) { + HttpMessagePair messagePair = queue.at(i); + const QHttpNetworkRequest &request = messagePair.first; + + // we currently do not support pipelining if HTTP authentication is used + if (!request.url().userInfo().isEmpty()) + continue; + + // take only GET requests + if (request.operation() != QHttpNetworkRequest::Get) + continue; + + if (!request.isPipeliningAllowed()) + continue; + + // remove it from the queue + queue.takeAt(i); + // we modify the queue we iterate over here, but since we return from the function + // afterwards this is fine. + + // actually send it + if (!messagePair.second->d_func()->requestIsPrepared) + prepareRequest(messagePair); + channel.pipelineInto(messagePair); + + // return false because we processed something and need to process again + return false; + } + + // return true, the queue has been processed and not changed + return true; } + QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket* socket) { Q_ASSERT(socket); @@ -994,7 +624,7 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) if (channels[i].reply == reply) { channels[i].reply = 0; if (reply->d_func()->connectionCloseEnabled()) - closeChannel(i); + channels[i].close(); QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); return; } @@ -1033,7 +663,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() channels[i].resendCurrent = false; channels[i].state = QHttpNetworkConnectionChannel::IdleState; if (channels[i].reply) - sendRequest(channels[i].socket); + channels[i].sendRequest(); } } QAbstractSocket *socket = 0; @@ -1045,20 +675,33 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() break; } } - if (!socket) - return; // this will be called after finishing current request. - unqueueAndSendRequest(socket); + + // this socket is free, + if (socket) + dequeueAndSendRequest(socket); + + // try to push more into all sockets + // ### FIXME we should move this to the beginning of the function + // as soon as QtWebkit is properly using the pipelining + // (e.g. not for XMLHttpRequest or the first page load) + // ### FIXME we should also divide the requests more even + // on the connected sockets + //tryToFillPipeline(socket); + // return fast if there is nothing to pipeline + if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) + return; + for (int j = 0; j < channelCount; j++) + fillPipeline(channels[j].socket); } void QHttpNetworkConnectionPrivate::_q_restartAuthPendingRequests() { // send the request using the idle socket for (int i = 0 ; i < channelCount; ++i) { - QAbstractSocket *socket = channels[i].socket; if (channels[i].state == QHttpNetworkConnectionChannel::Wait4AuthState) { channels[i].state = QHttpNetworkConnectionChannel::IdleState; if (channels[i].reply) - sendRequest(socket); + channels[i].sendRequest(); } } } @@ -1071,6 +714,13 @@ QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 d->init(); } +QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent) + : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent) +{ + Q_D(QHttpNetworkConnection); + d->init(); +} + QHttpNetworkConnection::~QHttpNetworkConnection() { } @@ -1217,6 +867,19 @@ void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int #endif //QT_NO_OPENSSL +#ifndef QT_NO_NETWORKPROXY +// only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not +// from QHttpNetworkConnectionChannel::handleAuthenticationChallenge +// e.g. it is for SOCKS proxies which require authentication. +void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth) +{ + Q_Q(QHttpNetworkConnection); + emit q->proxyAuthenticationRequired(proxy, auth, q); + int i = indexOf(chan->socket); + copyCredentials(i, auth, true); +} +#endif + QT_END_NAMESPACE diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 3a40e43..9d2c13f 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -89,6 +89,7 @@ class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject public: QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0); + QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0); ~QHttpNetworkConnection(); //The hostname to which this is connected to. @@ -153,7 +154,11 @@ class QHttpNetworkConnectionPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QHttpNetworkConnection) public: + static const int defaultChannelCount; + static const int defaultPipelineLength; + QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt); + QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt); ~QHttpNetworkConnectionPrivate(); void init(); @@ -166,12 +171,13 @@ public: bool isSocketReading(QAbstractSocket *socket) const; QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request); - void unqueueAndSendRequest(QAbstractSocket *socket); + void requeueRequest(const HttpMessagePair &pair); // e.g. after pipeline broke + void dequeueAndSendRequest(QAbstractSocket *socket); void prepareRequest(HttpMessagePair &request); - bool sendRequest(QAbstractSocket *socket); - void receiveReply(QAbstractSocket *socket, QHttpNetworkReply *reply); - void resendCurrentRequest(QAbstractSocket *socket); - void closeChannel(int channel); + + void fillPipeline(QAbstractSocket *socket); + bool fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel); + void copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy); // private slots @@ -179,9 +185,9 @@ public: void _q_restartAuthPendingRequests(); // send the currently blocked request void createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request); - bool ensureConnection(QAbstractSocket *socket); + QString errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket); - void eraseData(QHttpNetworkReply *reply); + #ifndef QT_NO_COMPRESS bool expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete); #endif @@ -191,7 +197,7 @@ public: quint16 port; bool encrypt; - static const int channelCount; + const int channelCount; QHttpNetworkConnectionChannel *channels; // parallel connections to the server bool pendingAuthSignal; // there is an incomplete authentication signal @@ -199,14 +205,11 @@ public: qint64 uncompressedBytesAvailable(const QHttpNetworkReply &reply) const; qint64 uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const; - qint64 compressedBytesAvailable(const QHttpNetworkReply &reply) const; + void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode); bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend); - void allDone(QAbstractSocket *socket, QHttpNetworkReply *reply); - void handleStatus(QAbstractSocket *socket, QHttpNetworkReply *reply); - inline bool shouldEmitSignals(QHttpNetworkReply *reply); - inline bool expectContent(QHttpNetworkReply *reply); + #ifndef QT_NO_OPENSSL QSslConfiguration sslConfiguration(const QHttpNetworkReply &reply) const; @@ -214,6 +217,7 @@ public: #ifndef QT_NO_NETWORKPROXY QNetworkProxy networkProxy; + void emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth); #endif //The request queues diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 5ffab76..9d78c55 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -41,6 +41,7 @@ #include "qhttpnetworkconnection_p.h" #include "qhttpnetworkconnectionchannel_p.h" +#include "private/qnoncontiguousbytedevice_p.h" #include <qpair.h> #include <qdebug.h> @@ -113,61 +114,646 @@ void QHttpNetworkConnectionChannel::close() } +bool QHttpNetworkConnectionChannel::sendRequest() +{ + switch (state) { + case QHttpNetworkConnectionChannel::IdleState: { // write the header + if (!ensureConnection()) { + // wait for the connection (and encryption) to be done + // sendRequest will be called again from either + // _q_connected or _q_encrypted + return false; + } + written = 0; // excluding the header + bytesTotal = 0; + if (reply) { + reply->d_func()->clear(); + reply->d_func()->connection = connection; + reply->d_func()->autoDecompress = request.d->autoDecompress; + reply->d_func()->pipeliningUsed = false; + } + state = QHttpNetworkConnectionChannel::WritingState; + pendingEncrypt = false; + // if the url contains authentication parameters, use the new ones + // both channels will use the new authentication parameters + if (!request.url().userInfo().isEmpty()) { + QUrl url = request.url(); + QAuthenticator &auth = authenticator; + if (url.userName() != auth.user() + || (!url.password().isEmpty() && url.password() != auth.password())) { + auth.setUser(url.userName()); + auth.setPassword(url.password()); + connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &auth, false); + } + // clear the userinfo, since we use the same request for resending + // userinfo in url can conflict with the one in the authenticator + url.setUserInfo(QString()); + request.setUrl(url); + } + connection->d_func()->createAuthorization(socket, request); +#ifndef QT_NO_NETWORKPROXY + QByteArray header = QHttpNetworkRequestPrivate::header(request, + (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)); +#else + QByteArray header = QHttpNetworkRequestPrivate::header(request, false); +#endif + socket->write(header); + QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); + if (uploadByteDevice) { + // connect the signals so this function gets called again + QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead())); + + bytesTotal = request.contentLength(); + } else { + state = QHttpNetworkConnectionChannel::WaitingState; + sendRequest(); + break; + } + // write the initial chunk together with the headers + // fall through + } + case QHttpNetworkConnectionChannel::WritingState: + { + // write the data + QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); + if (!uploadByteDevice || bytesTotal == written) { + if (uploadByteDevice) + emit reply->dataSendProgress(written, bytesTotal); + state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response + sendRequest(); + break; + } + + // only feed the QTcpSocket buffer when there is less than 32 kB in it + const qint64 socketBufferFill = 32*1024; + const qint64 socketWriteMaxSize = 16*1024; + + +#ifndef QT_NO_OPENSSL + QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); + // if it is really an ssl socket, check more than just bytesToWrite() + while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0)) + <= socketBufferFill && bytesTotal != written) +#else + while (socket->bytesToWrite() <= socketBufferFill + && bytesTotal != written) +#endif + { + // get pointer to upload data + qint64 currentReadSize; + qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written); + const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize); + + if (currentReadSize == -1) { + // premature eof happened + connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError); + return false; + break; + } else if (readPointer == 0 || currentReadSize == 0) { + // nothing to read currently, break the loop + break; + } else { + qint64 currentWriteSize = socket->write(readPointer, currentReadSize); + if (currentWriteSize == -1 || currentWriteSize != currentReadSize) { + // socket broke down + connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError); + return false; + } else { + written += currentWriteSize; + uploadByteDevice->advanceReadPointer(currentWriteSize); + + emit reply->dataSendProgress(written, bytesTotal); + + if (written == bytesTotal) { + // make sure this function is called once again + state = QHttpNetworkConnectionChannel::WaitingState; + sendRequest(); + break; + } + } + } + } + break; + } + + case QHttpNetworkConnectionChannel::WaitingState: + { + QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); + if (uploadByteDevice) { + QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead())); + } + + // HTTP pipelining + connection->d_func()->fillPipeline(socket); + socket->flush(); + + // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called + // this is needed if the sends an reply before we have finished sending the request. In that + // case receiveReply had been called before but ignored the server reply + receiveReply(); + break; + } + case QHttpNetworkConnectionChannel::ReadingState: + case QHttpNetworkConnectionChannel::Wait4AuthState: + // ignore _q_bytesWritten in these states + // fall through + default: + break; + } + return true; +} + + +void QHttpNetworkConnectionChannel::receiveReply() +{ + Q_ASSERT(socket); + + qint64 bytes = 0; + QAbstractSocket::SocketState socketState = socket->state(); + + // connection might be closed to signal the end of data + if (socketState == QAbstractSocket::UnconnectedState) { + if (!socket->bytesAvailable()) { + if (reply && reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { + reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; + this->state = QHttpNetworkConnectionChannel::IdleState; + allDone(); + } else { + // try to reconnect/resend before sending an error. + if (reconnectAttempts-- > 0) { + closeAndResendCurrentRequest(); + } else if (reply) { + reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::RemoteHostClosedError, socket); + emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString); + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } + } + } + } + + // read loop for the response + while (socket->bytesAvailable()) { + QHttpNetworkReplyPrivate::ReplyState state = reply ? reply->d_func()->state : QHttpNetworkReplyPrivate::AllDoneState; + switch (state) { + case QHttpNetworkReplyPrivate::NothingDoneState: + case QHttpNetworkReplyPrivate::ReadingStatusState: { + eatWhitespace(); + qint64 statusBytes = reply->d_func()->readStatus(socket); + if (statusBytes == -1 && reconnectAttempts <= 0) { + // too many errors reading/receiving/parsing the status, close the socket and emit error + close(); + reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::ProtocolFailure, socket); + emit reply->finishedWithError(QNetworkReply::ProtocolFailure, reply->d_func()->errorString); + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + break; + } else if (statusBytes == -1) { + reconnectAttempts--; + reply->d_func()->clear(); + closeAndResendCurrentRequest(); + break; + } + bytes += statusBytes; + lastStatus = reply->d_func()->statusCode; + break; + } + case QHttpNetworkReplyPrivate::ReadingHeaderState: + bytes += reply->d_func()->readHeader(socket); + if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { + if (reply->d_func()->isGzipped() && reply->d_func()->autoDecompress) { + // remove the Content-Length from header + reply->d_func()->removeAutoDecompressHeader(); + } else { + reply->d_func()->autoDecompress = false; + } + if (reply && reply->d_func()->statusCode == 100) { + reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; + break; // ignore + } + if (reply->d_func()->shouldEmitSignals()) + emit reply->headerChanged(); + if (!reply->d_func()->expectContent()) { + reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; + this->state = QHttpNetworkConnectionChannel::IdleState; + allDone(); + return; + } + } + break; + case QHttpNetworkReplyPrivate::ReadingDataState: { + if (!reply->d_func()->isChunked() && !reply->d_func()->autoDecompress + && reply->d_func()->bodyLength > 0) { + // bulk files like images should fulfill these properties and + // we can therefore save on memory copying + bytes = reply->d_func()->readBodyFast(socket, &reply->d_func()->responseData); + reply->d_func()->totalProgress += bytes; + if (reply->d_func()->shouldEmitSignals()) { + QPointer<QHttpNetworkReply> replyPointer = reply; + emit reply->readyRead(); + // make sure that the reply is valid + if (replyPointer.isNull()) + return; + emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength); + // make sure that the reply is valid + if (replyPointer.isNull()) + return; + } + } + else + { + // use the traditional slower reading (for compressed encoding, chunked encoding, + // no content-length etc) + QByteDataBuffer byteDatas; + bytes = reply->d_func()->readBody(socket, &byteDatas); + if (bytes) { + if (reply->d_func()->autoDecompress) + reply->d_func()->appendCompressedReplyData(byteDatas); + else + reply->d_func()->appendUncompressedReplyData(byteDatas); + + if (!reply->d_func()->autoDecompress) { + reply->d_func()->totalProgress += bytes; + if (reply->d_func()->shouldEmitSignals()) { + QPointer<QHttpNetworkReply> replyPointer = reply; + // important: At the point of this readyRead(), the byteDatas list must be empty, + // else implicit sharing will trigger memcpy when the user is reading data! + emit reply->readyRead(); + // make sure that the reply is valid + if (replyPointer.isNull()) + return; + emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength); + // make sure that the reply is valid + if (replyPointer.isNull()) + return; + } + } +#ifndef QT_NO_COMPRESS + else if (!expand(false)) { // expand a chunk if possible + return; // ### expand failed + } +#endif + } + } + if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) + break; + // everything done, fall through + } + case QHttpNetworkReplyPrivate::AllDoneState: + this->state = QHttpNetworkConnectionChannel::IdleState; + allDone(); + break; + default: + break; + } + } +} + +bool QHttpNetworkConnectionChannel::ensureConnection() +{ + // make sure that this socket is in a connected state, if not initiate + // connection to the host. + if (socket->state() != QAbstractSocket::ConnectedState) { + // connect to the host if not already connected. + // resend this request after we receive the disconnected signal + if (socket->state() == QAbstractSocket::ClosingState) { + resendCurrent = true; + return false; + } + state = QHttpNetworkConnectionChannel::ConnectingState; + pendingEncrypt = connection->d_func()->encrypt; + + // reset state + pipeliningSupported = PipeliningSupportUnknown; + + // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done" + // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the + // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not + // check the "phase" for generating the Authorization header. NTLM authentication is a two stage + // process & needs the "phase". To make sure the QAuthenticator uses the current username/password + // the phase is reset to Start. + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(authenticator); + if (priv && priv->phase == QAuthenticatorPrivate::Done) + priv->phase = QAuthenticatorPrivate::Start; + priv = QAuthenticatorPrivate::getPrivate(proxyAuthenticator); + if (priv && priv->phase == QAuthenticatorPrivate::Done) + priv->phase = QAuthenticatorPrivate::Start; + + QString connectHost = connection->d_func()->hostName; + qint16 connectPort = connection->d_func()->port; + +#ifndef QT_NO_NETWORKPROXY + // HTTPS always use transparent proxy. + if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !connection->d_func()->encrypt) { + connectHost = connection->d_func()->networkProxy.hostName(); + connectPort = connection->d_func()->networkProxy.port(); + } +#endif + if (connection->d_func()->encrypt) { +#ifndef QT_NO_OPENSSL + QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); + sslSocket->connectToHostEncrypted(connectHost, connectPort); + if (ignoreAllSslErrors) + sslSocket->ignoreSslErrors(); + sslSocket->ignoreSslErrors(ignoreSslErrorsList); +#else + connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError); +#endif + } else { + socket->connectToHost(connectHost, connectPort); + } + return false; + } + return true; +} + + +#ifndef QT_NO_COMPRESS +bool QHttpNetworkConnectionChannel::expand(bool dataComplete) +{ + Q_ASSERT(socket); + Q_ASSERT(reply); + + qint64 total = reply->d_func()->compressedData.size(); + if (total >= CHUNK || dataComplete) { + // uncompress the data + QByteArray content, inflated; + content = reply->d_func()->compressedData; + reply->d_func()->compressedData.clear(); + + int ret = Z_OK; + if (content.size()) + ret = reply->d_func()->gunzipBodyPartially(content, inflated); + int retCheck = (dataComplete) ? Z_STREAM_END : Z_OK; + if (ret >= retCheck) { + if (inflated.size()) { + reply->d_func()->totalProgress += inflated.size(); + reply->d_func()->appendUncompressedReplyData(inflated); + if (reply->d_func()->shouldEmitSignals()) { + QPointer<QHttpNetworkReply> replyPointer = reply; + // important: At the point of this readyRead(), inflated must be cleared, + // else implicit sharing will trigger memcpy when the user is reading data! + emit reply->readyRead(); + // make sure that the reply is valid + if (replyPointer.isNull()) + return true; + emit reply->dataReadProgress(reply->d_func()->totalProgress, 0); + // make sure that the reply is valid + if (replyPointer.isNull()) + return true; + + } + } + } else { + connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure); + return false; + } + } + return true; +} +#endif + + +void QHttpNetworkConnectionChannel::allDone() +{ +#ifndef QT_NO_COMPRESS + // expand the whole data. + if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) + expand(true); // ### if expand returns false, its an error +#endif + // while handling 401 & 407, we might reset the status code, so save this. + bool emitFinished = reply->d_func()->shouldEmitSignals(); + handleStatus(); + // ### at this point there should be no more data on the socket + // close if server requested + if (reply->d_func()->connectionCloseEnabled()) + close(); + // queue the finished signal, this is required since we might send new requests from + // slot connected to it. The socket will not fire readyRead signal, if we are already + // in the slot connected to readyRead + if (emitFinished) + QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection); + // reset the reconnection attempts after we receive a complete reply. + // in case of failures, each channel will attempt two reconnects before emitting error. + reconnectAttempts = 2; + + detectPipeliningSupport(); + + // move next from pipeline to current request + if (!alreadyPipelinedRequests.isEmpty()) { + if (resendCurrent || reply->d_func()->connectionCloseEnabled() || socket->state() != QAbstractSocket::ConnectedState) { + // move the pipelined ones back to the main queue + requeueCurrentlyPipelinedRequests(); + } else { + // there were requests pipelined in and we can continue + HttpMessagePair messagePair = alreadyPipelinedRequests.takeFirst(); + + request = messagePair.first; + reply = messagePair.second; + state = QHttpNetworkConnectionChannel::ReadingState; + resendCurrent = false; + + written = 0; // message body, excluding the header, irrelevant here + bytesTotal = 0; // message body total, excluding the header, irrelevant here + + // pipeline even more + connection->d_func()->fillPipeline(socket); + + // continue reading + receiveReply(); + } + } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) { + eatWhitespace(); + // this is weird. we had nothing pipelined but still bytes available. better close it. + if (socket->bytesAvailable() > 0) + close(); + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } else if (alreadyPipelinedRequests.isEmpty()) { + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } +} + +void QHttpNetworkConnectionChannel::detectPipeliningSupport() +{ + // detect HTTP Pipelining support + QByteArray serverHeaderField = reply->headerField("Server"); + if ( + // check for broken servers in server reply header + // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining + (!serverHeaderField.contains("Microsoft-IIS/4.")) + && (!serverHeaderField.contains("Microsoft-IIS/5.")) + && (!serverHeaderField.contains("Netscape-Enterprise/3.")) + // check for HTTP/1.1 + && (reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1) + // check for not having connection close + && (!reply->d_func()->connectionCloseEnabled()) + // check if it is still connected + && (socket->state() == QAbstractSocket::ConnectedState) + ) { + pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported; + } else { + pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown; + } +} + +// called when the connection broke and we need to queue some pipelined requests again +void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests() +{ + for (int i = 0; i < alreadyPipelinedRequests.length(); i++) + connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i)); + alreadyPipelinedRequests.clear(); + + close(); + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); +} + +void QHttpNetworkConnectionChannel::eatWhitespace() +{ + char c; + while (socket->bytesAvailable()) { + if (socket->peek(&c, 1) != 1) + return; + + // read all whitespace and line endings + if (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31) { + socket->read(&c, 1); + continue; + } else { + break; + } + } +} + +void QHttpNetworkConnectionChannel::handleStatus() +{ + Q_ASSERT(socket); + Q_ASSERT(reply); + + int statusCode = reply->statusCode(); + bool resend = false; + + switch (statusCode) { + case 401: // auth required + case 407: // proxy auth required + if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) { + if (resend) { + QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); + if (uploadByteDevice) { + if (uploadByteDevice->reset()) { + written = 0; + } else { + connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError); + break; + } + } + + reply->d_func()->eraseData(); + + if (alreadyPipelinedRequests.isEmpty()) { + // this does a re-send without closing the connection + resendCurrent = true; + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } else { + // we had requests pipelined.. better close the connection in closeAndResendCurrentRequest + closeAndResendCurrentRequest(); + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } + } + } else { + emit reply->headerChanged(); + emit reply->readyRead(); + QNetworkReply::NetworkError errorCode = (statusCode == 407) + ? QNetworkReply::ProxyAuthenticationRequiredError + : QNetworkReply::AuthenticationRequiredError; + reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket); + emit connection->error(errorCode, reply->d_func()->errorString); + emit reply->finished(); + } + break; + default: + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); + } +} + +void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair) +{ + // this is only called for simple GET + + QHttpNetworkRequest &request = pair.first; + QHttpNetworkReply *reply = pair.second; + if (reply) { + reply->d_func()->clear(); + reply->d_func()->connection = connection; + reply->d_func()->autoDecompress = request.d->autoDecompress; + reply->d_func()->pipeliningUsed = true; + } + +#ifndef QT_NO_NETWORKPROXY + QByteArray header = QHttpNetworkRequestPrivate::header(request, + (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)); +#else + QByteArray header = QHttpNetworkRequestPrivate::header(request, false); +#endif + socket->write(header); + + alreadyPipelinedRequests.append(pair); +} + +void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest() +{ + requeueCurrentlyPipelinedRequests(); + close(); + resendCurrent = true; + QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); +} + //private slots void QHttpNetworkConnectionChannel::_q_readyRead() { - if (!socket) - return; // ### error if (connection->d_func()->isSocketWaiting(socket) || connection->d_func()->isSocketReading(socket)) { state = QHttpNetworkConnectionChannel::ReadingState; if (reply) - connection->d_func()->receiveReply(socket, reply); + receiveReply(); } - // ### error } void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes) { Q_UNUSED(bytes); - if (!socket) - return; // ### error // bytes have been written to the socket. write even more of them :) if (connection->d_func()->isSocketWriting(socket)) - connection->d_func()->sendRequest(socket); + sendRequest(); // otherwise we do nothing } void QHttpNetworkConnectionChannel::_q_disconnected() { - if (!socket) - return; // ### error // read the available data before closing if (connection->d_func()->isSocketWaiting(socket) || connection->d_func()->isSocketReading(socket)) { state = QHttpNetworkConnectionChannel::ReadingState; if (reply) - connection->d_func()->receiveReply(socket, reply); + receiveReply(); } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) { // re-sending request because the socket was in ClosingState QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } state = QHttpNetworkConnectionChannel::IdleState; + + requeueCurrentlyPipelinedRequests(); } void QHttpNetworkConnectionChannel::_q_connected() { - if (!socket) - return; // ### error - // improve performance since we get the request sent by the kernel ASAP socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); + pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown; + // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again! //channels[i].reconnectAttempts = 2; if (!pendingEncrypt) { state = QHttpNetworkConnectionChannel::IdleState; if (reply) - connection->d_func()->sendRequest(socket); + sendRequest(); else close(); } @@ -193,7 +779,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket // while "Reading" the _q_disconnected() will handle this. if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) { if (reconnectAttempts-- > 0) { - connection->d_func()->resendCurrentRequest(socket); + closeAndResendCurrentRequest(); return; } else { send2Reply = true; @@ -206,7 +792,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket case QAbstractSocket::SocketTimeoutError: // try to reconnect/resend before sending an error. if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) { - connection->d_func()->resendCurrentRequest(socket); + closeAndResendCurrentRequest(); return; } send2Reply = true; @@ -244,13 +830,13 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket #ifndef QT_NO_NETWORKPROXY void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth) { - emit connection->proxyAuthenticationRequired(proxy, auth, connection); + connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth); } #endif void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead() { - connection->d_func()->sendRequest(socket); + sendRequest(); } #ifndef QT_NO_OPENSSL @@ -259,7 +845,7 @@ void QHttpNetworkConnectionChannel::_q_encrypted() if (!socket) return; // ### error state = QHttpNetworkConnectionChannel::IdleState; - connection->d_func()->sendRequest(socket); + sendRequest(); } void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors) diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index f937669..220b72c 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -82,10 +82,14 @@ class QHttpNetworkReply; class QByteArray; class QHttpNetworkConnection; +#ifndef HttpMessagePair +typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair; +#endif + class QHttpNetworkConnectionChannel : public QObject { Q_OBJECT public: - enum ChannelState { + enum ChannelState { IdleState = 0, // ready to send request ConnectingState = 1, // connecting to host WritingState = 2, // writing the data @@ -112,12 +116,24 @@ public: bool ignoreAllSslErrors; QList<QSslError> ignoreSslErrorsList; #endif + + // HTTP pipelining -> http://en.wikipedia.org/wiki/Http_pipelining + enum PipeliningSupport { + PipeliningSupportUnknown, // default for a new connection + PipeliningProbablySupported, // after having received a server response that indicates support + PipeliningNotSupported // currently not used + }; + PipeliningSupport pipeliningSupported; + QList<HttpMessagePair> alreadyPipelinedRequests; + + QHttpNetworkConnectionChannel() : socket(0), state(IdleState), reply(0), written(0), bytesTotal(0), resendCurrent(false), lastStatus(0), pendingEncrypt(false), reconnectAttempts(2), authMehtod(QAuthenticatorPrivate::None), proxyAuthMehtod(QAuthenticatorPrivate::None) #ifndef QT_NO_OPENSSL , ignoreAllSslErrors(false) #endif + , pipeliningSupported(PipeliningSupportUnknown) , connection(0) {} @@ -127,6 +143,23 @@ public: void init(); void close(); + bool sendRequest(); + void receiveReply(); + + bool ensureConnection(); + + bool expand(bool dataComplete); + void allDone(); // reply header + body have been read + void handleStatus(); // called from allDone() + + void pipelineInto(HttpMessagePair &pair); + void requeueCurrentlyPipelinedRequests(); + void detectPipeliningSupport(); + + void closeAndResendCurrentRequest(); + + void eatWhitespace(); + protected slots: void _q_bytesWritten(qint64 bytes); // proceed sending void _q_readyRead(); // pending data to read diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index 9dbc11d..ba429fd 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -187,6 +187,10 @@ bool QHttpNetworkReply::isFinished() const return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; } +bool QHttpNetworkReply::isPipeliningUsed() const +{ + return d_func()->pipeliningUsed; +} QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) @@ -195,6 +199,7 @@ QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) chunkedTransferEncoding(0), currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false), autoDecompress(false), responseData(), requestIsPrepared(false) + ,pipeliningUsed(false) { } @@ -418,20 +423,26 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) } bool ok = parseStatus(fragment); state = ReadingHeaderState; - fragment.clear(); // next fragment - - if (!ok) + fragment.clear(); + if (!ok) { return -1; + } break; } else { c = 0; - bytes += socket->read(&c, 1); + int haveRead = socket->read(&c, 1); + if (haveRead == -1) + return -1; + bytes += haveRead; fragment.append(c); } // is this a valid reply? if (fragment.length() >= 5 && !fragment.startsWith("HTTP/")) + { + fragment.clear(); return -1; + } } @@ -566,7 +577,6 @@ qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteData if (contentRead + haveRead == bodyLength) { state = AllDoneState; - socket->readAll(); // Read the rest to clean (CRLF) ### will break pipelining } contentRead += haveRead; @@ -586,8 +596,6 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff } else { bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable()); } - if (state == AllDoneState) - socket->readAll(); // Read the rest to clean (CRLF) ### will break pipelining contentRead += bytes; return bytes; } @@ -715,6 +723,33 @@ void QHttpNetworkReplyPrivate::appendCompressedReplyData(QByteDataBuffer &data) } +bool QHttpNetworkReplyPrivate::shouldEmitSignals() +{ + // for 401 & 407 don't emit the data signals. Content along with these + // responses are send only if the authentication fails. + return (statusCode != 401 && statusCode != 407); +} + +bool QHttpNetworkReplyPrivate::expectContent() +{ + // check whether we can expect content after the headers (rfc 2616, sec4.4) + if ((statusCode >= 100 && statusCode < 200) + || statusCode == 204 || statusCode == 304) + return false; + if (request.operation() == QHttpNetworkRequest::Head) + return !shouldEmitSignals(); + if (contentLength() == 0) + return false; + return true; +} + +void QHttpNetworkReplyPrivate::eraseData() +{ + compressedData.clear(); + responseData.clear(); +} + + // SSL support below #ifndef QT_NO_OPENSSL diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h index 5aee067..8d4d724 100644 --- a/src/network/access/qhttpnetworkreply_p.h +++ b/src/network/access/qhttpnetworkreply_p.h @@ -128,6 +128,8 @@ public: bool isFinished() const; + bool isPipeliningUsed() const; + #ifndef QT_NO_OPENSSL QSslConfiguration sslConfiguration() const; void setSslConfiguration(const QSslConfiguration &config); @@ -177,6 +179,10 @@ public: void appendUncompressedReplyData(QByteDataBuffer &data); void appendCompressedReplyData(QByteDataBuffer &data); + bool shouldEmitSignals(); + bool expectContent(); + void eraseData(); + qint64 bytesAvailable() const; bool isChunked(); bool connectionCloseEnabled(); @@ -219,6 +225,8 @@ public: QByteDataBuffer responseData; // uncompressed body QByteArray compressedData; // compressed body (temporary) bool requestIsPrepared; + + bool pipeliningUsed; }; diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 693e1f4..8b2c4e9 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op, QHttpNetworkRequest::Priority pri, const QUrl &newUrl) : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0), - autoDecompress(false) + autoDecompress(false), pipeliningAllowed(false) { } @@ -60,6 +60,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest priority = other.priority; uploadByteDevice = other.uploadByteDevice; autoDecompress = other.autoDecompress; + pipeliningAllowed = other.pipeliningAllowed; } QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate() @@ -239,6 +240,16 @@ void QHttpNetworkRequest::setPriority(Priority priority) d->priority = priority; } +bool QHttpNetworkRequest::isPipeliningAllowed() const +{ + return d->pipeliningAllowed; +} + +void QHttpNetworkRequest::setPipeliningAllowed(bool b) +{ + d->pipeliningAllowed = b; +} + void QHttpNetworkRequest::setUploadByteDevice(QNonContiguousByteDevice *bd) { d->uploadByteDevice = bd; diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index ffacb14..2468be9 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -106,6 +106,9 @@ public: Priority priority() const; void setPriority(Priority priority); + bool isPipeliningAllowed() const; + void setPipeliningAllowed(bool b); + void setUploadByteDevice(QNonContiguousByteDevice *bd); QNonContiguousByteDevice* uploadByteDevice() const; @@ -113,6 +116,7 @@ private: QSharedDataPointer<QHttpNetworkRequestPrivate> d; friend class QHttpNetworkRequestPrivate; friend class QHttpNetworkConnectionPrivate; + friend class QHttpNetworkConnectionChannel; }; class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate @@ -132,6 +136,7 @@ public: QHttpNetworkRequest::Priority priority; mutable QNonContiguousByteDevice* uploadByteDevice; bool autoDecompress; + bool pipeliningAllowed; }; diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp index 0ca00fe..30f16da 100644 --- a/src/network/access/qnetworkaccesshttpbackend.cpp +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -516,6 +516,9 @@ void QNetworkAccessHttpBackend::postRequest() return; // no need to send the request! :) } + if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true) + httpRequest.setPipeliningAllowed(true); + httpReply = http->sendRequest(httpRequest); httpReply->setParent(this); #ifndef QT_NO_OPENSSL @@ -718,6 +721,8 @@ void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode) void QNetworkAccessHttpBackend::replyHeaderChanged() { + setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, httpReply->isPipeliningUsed()); + // reconstruct the HTTP header QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header(); QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(), diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index fb86e6a..70f8de6 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -804,7 +804,13 @@ void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBac QAuthenticator *authenticator) { Q_Q(QNetworkAccessManager); - + // ### FIXME Tracking of successful authentications + // This code is a bit broken right now for SOCKS authentication + // first request: proxyAuthenticationRequired gets emitted, credentials gets saved + // second request: (proxy != backend->reply->lastProxyAuthentication) does not evaluate to true, + // proxyAuthenticationRequired gets emitted again + // possible solution: some tracking inside the authenticator + // or a new function proxyAuthenticationSucceeded(true|false) if (proxy != backend->reply->lastProxyAuthentication) { QNetworkAuthenticationCredential *cred = fetchCachedCredentials(proxy); if (cred) { diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 9f6f224..691423f 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -167,6 +167,16 @@ QT_BEGIN_NAMESPACE When using this flag with sequential upload data, the ContentLengthHeader header must be set. + \value HttpPipeliningAllowedAttribute + Requests only, type: QVariant::Bool (default: false) + Indicates whether the QNetworkAccessManager code is + allowed to use HTTP pipelining with this request. + + \value HttpPipeliningWasUsedAttribute + Replies only, type: QVariant::Bool + Indicates whether the HTTP pipelining was used for receiving + this reply. + \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 aaeed48..2c26038 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -76,6 +76,8 @@ public: CacheSaveControlAttribute, SourceIsFromCacheAttribute, DoNotBufferUploadDataAttribute, + HttpPipeliningAllowedAttribute, + HttpPipeliningWasUsedAttribute, User = 1000, UserMax = 32767 |