summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp695
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h30
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp624
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h35
-rw-r--r--src/network/access/qhttpnetworkreply.cpp49
-rw-r--r--src/network/access/qhttpnetworkreply_p.h8
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp13
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h5
-rw-r--r--src/network/access/qnetworkaccesshttpbackend.cpp5
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp8
-rw-r--r--src/network/access/qnetworkrequest.cpp10
-rw-r--r--src/network/access/qnetworkrequest.h2
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