diff options
Diffstat (limited to 'src/network/access/qhttpnetworkconnectionchannel.cpp')
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 382 |
1 files changed, 18 insertions, 364 deletions
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 6e786893ed..6f06c18732 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -48,6 +49,8 @@ #ifndef QT_NO_HTTP +#include <private/qhttpprotocolhandler_p.h> + #ifndef QT_NO_SSL # include <QtNetwork/qsslkey.h> # include <QtNetwork/qsslcipher.h> @@ -78,6 +81,7 @@ QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel() , proxyAuthMethod(QAuthenticatorPrivate::None) , authenticationCredentialsSent(false) , proxyCredentialsSent(false) + , protocolHandler(0) #ifndef QT_NO_SSL , ignoreAllSslErrors(false) #endif @@ -163,8 +167,8 @@ void QHttpNetworkConnectionChannel::init() if (!sslConfiguration.isNull()) sslSocket->setSslConfiguration(sslConfiguration); } - #endif + protocolHandler.reset(new QHttpProtocolHandler(this)); #ifndef QT_NO_NETWORKPROXY if (proxy.type() != QNetworkProxy::NoProxy) @@ -193,349 +197,21 @@ void QHttpNetworkConnectionChannel::close() bool QHttpNetworkConnectionChannel::sendRequest() { - if (!reply) { - // heh, how should that happen! - qWarning() << "QHttpNetworkConnectionChannel::sendRequest() called without QHttpNetworkReply"; - state = QHttpNetworkConnectionChannel::IdleState; - return false; - } - - 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; - } - QString scheme = request.url().scheme(); - if (scheme == QLatin1String("preconnect-http") - || scheme == QLatin1String("preconnect-https")) { - state = QHttpNetworkConnectionChannel::IdleState; - reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; - allDone(); - connection->preConnectFinished(); // will only decrease the counter - reply = 0; // so we can reuse this channel - return true; // we have a working connection and are done - } - - written = 0; // excluding the header - bytesTotal = 0; - - QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); - replyPrivate->clear(); - replyPrivate->connection = connection; - replyPrivate->connectionChannel = this; - replyPrivate->autoDecompress = request.d->autoDecompress; - replyPrivate->pipeliningUsed = false; - - // if the url contains authentication parameters, use the new ones - // both channels will use the new authentication parameters - if (!request.url().userInfo().isEmpty() && request.withCredentials()) { - QUrl url = request.url(); - QAuthenticator &auth = authenticator; - if (url.userName() != auth.user() - || (!url.password().isEmpty() && url.password() != auth.password())) { - auth.setUser(url.userName(QUrl::FullyDecoded)); - auth.setPassword(url.password(QUrl::FullyDecoded)); - 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); - } - // Will only be false if Qt WebKit is performing a cross-origin XMLHttpRequest - // and withCredentials has not been set to true. - if (request.withCredentials()) - 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); - // flushing is dangerous (QSslSocket calls transmit which might read or error) -// socket->flush(); - 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(); - - state = QHttpNetworkConnectionChannel::WritingState; // start writing data - sendRequest(); //recurse - } else { - state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response - sendRequest(); //recurse - } - - break; - } - 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(); // recurse - 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_SSL - 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 = 0; - 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; - } 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 - if (socket->bytesAvailable()) - QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection); - break; - } - case QHttpNetworkConnectionChannel::ReadingState: - // ignore _q_bytesWritten in these states - // fall through - default: - break; - } - return true; + Q_ASSERT(!protocolHandler.isNull()); + return protocolHandler->sendRequest(); } void QHttpNetworkConnectionChannel::_q_receiveReply() { - Q_ASSERT(socket); - - if (!reply) { - if (socket->bytesAvailable() > 0) - qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply," - << socket->bytesAvailable() << "bytes on socket."; - close(); - return; - } - - // only run when the QHttpNetworkConnection is not currently being destructed, e.g. - // this function is called from _q_disconnected which is called because - // of ~QHttpNetworkConnectionPrivate - if (!qobject_cast<QHttpNetworkConnection*>(connection)) { - return; - } - - QAbstractSocket::SocketState socketState = socket->state(); - - // connection might be closed to signal the end of data - if (socketState == QAbstractSocket::UnconnectedState) { - if (socket->bytesAvailable() <= 0) { - if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { - // finish this reply. this case happens when the server did not send a content length - reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; - allDone(); - return; - } else { - handleUnexpectedEOF(); - return; - } - } else { - // socket not connected but still bytes for reading.. just continue in this function - } - } - - // read loop for the response - qint64 bytes = 0; - qint64 lastBytes = bytes; - do { - lastBytes = bytes; - - QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state; - switch (state) { - case QHttpNetworkReplyPrivate::NothingDoneState: { - state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; - // fallthrough - } - case QHttpNetworkReplyPrivate::ReadingStatusState: { - qint64 statusBytes = reply->d_func()->readStatus(socket); - if (statusBytes == -1) { - // connection broke while reading status. also handled if later _q_disconnected is called - handleUnexpectedEOF(); - return; - } - bytes += statusBytes; - lastStatus = reply->d_func()->statusCode; - break; - } - case QHttpNetworkReplyPrivate::ReadingHeaderState: { - QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); - qint64 headerBytes = replyPrivate->readHeader(socket); - if (headerBytes == -1) { - // connection broke while reading headers. also handled if later _q_disconnected is called - handleUnexpectedEOF(); - return; - } - bytes += headerBytes; - // If headers were parsed successfully now it is the ReadingDataState - if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) { - if (replyPrivate->isCompressed() && replyPrivate->autoDecompress) { - // remove the Content-Length from header - replyPrivate->removeAutoDecompressHeader(); - } else { - replyPrivate->autoDecompress = false; - } - if (replyPrivate->statusCode == 100) { - replyPrivate->clearHttpLayerInformation(); - replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState; - break; // ignore - } - if (replyPrivate->shouldEmitSignals()) - emit reply->headerChanged(); - // After headerChanged had been emitted - // we can suddenly have a replyPrivate->userProvidedDownloadBuffer - // this is handled in the ReadingDataState however - - if (!replyPrivate->expectContent()) { - replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState; - allDone(); - break; - } - } - break; - } - case QHttpNetworkReplyPrivate::ReadingDataState: { - QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); - if (socket->state() == QAbstractSocket::ConnectedState && - replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) { - // (only do the following when still connected, not when we have already been disconnected and there is still data) - // We already have some HTTP body data. We don't read more from the socket until - // this is fetched by QHttpNetworkAccessHttpBackend. If we would read more, - // we could not limit our read buffer usage. - // We only do this when shouldEmitSignals==true because our HTTP parsing - // always needs to parse the 401/407 replies. Therefore they don't really obey - // to the read buffer maximum size, but we don't care since they should be small. - return; - } - - if (replyPrivate->userProvidedDownloadBuffer) { - // the user provided a direct buffer where we should put all our data in. - // this only works when we can tell the user the content length and he/she can allocate - // the buffer in that size. - // note that this call will read only from the still buffered data - qint64 haveRead = replyPrivate->readBodyVeryFast(socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress); - if (haveRead > 0) { - bytes += haveRead; - replyPrivate->totalProgress += haveRead; - // the user will get notified of it via progress signal - emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); - } else if (haveRead == 0) { - // Happens since this called in a loop. Currently no bytes available. - } else if (haveRead < 0) { - connection->d_func()->emitReplyError(socket, reply, QNetworkReply::RemoteHostClosedError); - break; - } - } else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress - && replyPrivate->bodyLength > 0) { - // bulk files like images should fulfill these properties and - // we can therefore save on memory copying - qint64 haveRead = replyPrivate->readBodyFast(socket, &replyPrivate->responseData); - bytes += haveRead; - replyPrivate->totalProgress += haveRead; - if (replyPrivate->shouldEmitSignals()) { - emit reply->readyRead(); - emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); - } - } - else - { - // use the traditional slower reading (for compressed encoding, chunked encoding, - // no content-length etc) - qint64 haveRead = replyPrivate->readBody(socket, &replyPrivate->responseData); - if (haveRead > 0) { - bytes += haveRead; - replyPrivate->totalProgress += haveRead; - if (replyPrivate->shouldEmitSignals()) { - emit reply->readyRead(); - emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); - } - } else if (haveRead == -1) { - // Some error occurred - connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure); - break; - } - } - // still in ReadingDataState? This function will be called again by the socket's readyRead - if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) - break; + Q_ASSERT(!protocolHandler.isNull()); + protocolHandler->_q_receiveReply(); +} - // everything done, fall through - } - case QHttpNetworkReplyPrivate::AllDoneState: - allDone(); - break; - default: - break; - } - } while (bytes != lastBytes && reply); +void QHttpNetworkConnectionChannel::_q_readyRead() +{ + Q_ASSERT(!protocolHandler.isNull()); + protocolHandler->_q_readyRead(); } // called when unexpectedly reading a -1 or when data is expected but socket is closed @@ -724,6 +400,7 @@ void QHttpNetworkConnectionChannel::allDone() if (!resendCurrent) { request = QHttpNetworkRequest(); reply = 0; + protocolHandler->setReply(0); } // move next from pipeline to current request @@ -738,6 +415,7 @@ void QHttpNetworkConnectionChannel::allDone() request = messagePair.first; reply = messagePair.second; + protocolHandler->setReply(messagePair.second); state = QHttpNetworkConnectionChannel::ReadingState; resendCurrent = false; @@ -982,32 +660,6 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const return (state & QHttpNetworkConnectionChannel::ReadingState); } -//private slots -void QHttpNetworkConnectionChannel::_q_readyRead() -{ - if (socket->state() == QAbstractSocket::ConnectedState && socket->bytesAvailable() == 0) { - // We got a readyRead but no bytes are available.. - // This happens for the Unbuffered QTcpSocket - // Also check if socket is in ConnectedState since - // this function may also be invoked via the event loop. - char c; - qint64 ret = socket->peek(&c, 1); - if (ret < 0) { - _q_error(socket->error()); - // We still need to handle the reply so it emits its signals etc. - if (reply) - _q_receiveReply(); - return; - } - } - - if (isSocketWaiting() || isSocketReading()) { - state = QHttpNetworkConnectionChannel::ReadingState; - if (reply) - _q_receiveReply(); - } -} - void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes) { Q_UNUSED(bytes); @@ -1224,6 +876,8 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket reply->d_func()->errorString = errorString; emit reply->finishedWithError(errorCode, errorString); reply = 0; + if (protocolHandler) + protocolHandler->setReply(0); } // send the next request QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection); |