src/network/access/qhttpnetworkconnectionchannel.cpp
branchRCL_3
changeset 7 3f74d0d4af4c
parent 5 d3bac044e0f0
child 13 c0432d11811c
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp	Mon Mar 15 12:43:09 2010 +0200
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp	Thu Apr 08 14:19:33 2010 +0300
@@ -58,6 +58,28 @@
 
 // TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
 
+QHttpNetworkConnectionChannel::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)
+{
+    // Inlining this function in the header leads to compiler error on
+    // release-armv5, on at least timebox 9.2 and 10.1.
+}
+
 void QHttpNetworkConnectionChannel::init()
 {
 #ifndef QT_NO_OPENSSL
@@ -124,6 +146,13 @@
 
 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()) {
@@ -134,13 +163,13 @@
         }
         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;
+
+        reply->d_func()->clear();
+        reply->d_func()->connection = connection;
+        reply->d_func()->connectionChannel = this;
+        reply->d_func()->autoDecompress = request.d->autoDecompress;
+        reply->d_func()->pipeliningUsed = false;
+
         pendingEncrypt = false;
         // if the url contains authentication parameters, use the new ones
         // both channels will use the new authentication parameters
@@ -174,13 +203,15 @@
             QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead()));
 
             bytesTotal = request.contentLength();
+
+            state = QHttpNetworkConnectionChannel::WritingState; // start writing data
+            sendRequest(); //recurse
         } else {
-            state = QHttpNetworkConnectionChannel::WaitingState;
-            sendRequest();
-            break;
+            state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
+            sendRequest(); //recurse
         }
-        // write the initial chunk together with the headers
-        // fall through
+
+        break;
     }
     case QHttpNetworkConnectionChannel::WritingState:
     {
@@ -190,7 +221,7 @@
             if (uploadByteDevice)
                 emit reply->dataSendProgress(written, bytesTotal);
             state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
-            sendRequest();
+            sendRequest(); // recurse
             break;
         }
 
@@ -278,49 +309,50 @@
 {
     Q_ASSERT(socket);
 
+    if (!reply) {
+        // heh, how should that happen!
+        qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply,"
+                << socket->bytesAvailable() << "bytes on socket.";
+        close();
+        return;
+    }
+
     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) {
+        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;
-                this->state = QHttpNetworkConnectionChannel::IdleState;
                 allDone();
+                return;
             } 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);
-                }
+                handleUnexpectedEOF();
+                return;
             }
+        } else {
+            // socket not connected but still bytes for reading.. just continue in this function
         }
     }
 
     // read loop for the response
     while (socket->bytesAvailable()) {
-        QHttpNetworkReplyPrivate::ReplyState state = reply ? reply->d_func()->state : QHttpNetworkReplyPrivate::AllDoneState;
+        QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state;
         switch (state) {
-        case QHttpNetworkReplyPrivate::NothingDoneState:
+        case QHttpNetworkReplyPrivate::NothingDoneState: {
+            // only eat whitespace on the first call
+            eatWhitespace();
+            state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
+            // fallthrough
+        }
         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;
+            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;
@@ -328,7 +360,13 @@
         }
         case QHttpNetworkReplyPrivate::ReadingHeaderState: {
             QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
-            bytes += replyPrivate->readHeader(socket);
+            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 (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) {
                 if (replyPrivate->isGzipped() && replyPrivate->autoDecompress) {
                     // remove the Content-Length from header
@@ -337,6 +375,7 @@
                     replyPrivate->autoDecompress = false;
                 }
                 if (replyPrivate->statusCode == 100) {
+                    replyPrivate->clearHttpLayerInformation();
                     replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
                     break; // ignore
                 }
@@ -344,7 +383,6 @@
                     emit reply->headerChanged();
                 if (!replyPrivate->expectContent()) {
                     replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState;
-                    this->state = QHttpNetworkConnectionChannel::IdleState;
                     allDone();
                     return;
                 }
@@ -416,12 +454,13 @@
 #endif
                 }
             }
+            // still in ReadingDataState? This function will be called again by the socket's readyRead
             if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState)
                 break;
+
             // everything done, fall through
             }
       case QHttpNetworkReplyPrivate::AllDoneState:
-            this->state = QHttpNetworkConnectionChannel::IdleState;
             allDone();
             break;
         default:
@@ -430,6 +469,25 @@
     }
 }
 
+// called when unexpectedly reading a -1 or when data is expected but socket is closed
+void QHttpNetworkConnectionChannel::handleUnexpectedEOF()
+{
+    if (reconnectAttempts <= 0) {
+        // too many errors reading/receiving/parsing the status, close the socket and emit error
+        requeueCurrentlyPipelinedRequests();
+        close();
+        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);
+    } else {
+        reconnectAttempts--;
+        reply->d_func()->clear();
+        reply->d_func()->connection = connection;
+        reply->d_func()->connectionChannel = this;
+        closeAndResendCurrentRequest();
+    }
+}
+
 bool QHttpNetworkConnectionChannel::ensureConnection()
 {
     QAbstractSocket::SocketState socketState = socket->state();
@@ -556,7 +614,8 @@
     handleStatus();
     // ### at this point there should be no more data on the socket
     // close if server requested
-    if (reply->d_func()->isConnectionCloseEnabled())
+    bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
+    if (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
@@ -569,9 +628,20 @@
 
     detectPipeliningSupport();
 
+    // now the channel can be seen as free/idle again, all signal emissions for the reply have been done
+    this->state = QHttpNetworkConnectionChannel::IdleState;
+
+    // if it does not need to be sent again we can set it to 0
+    // the previous code did not do that and we had problems with accidental re-sending of a
+    // finished request.
+    // Note that this may trigger a segfault at some other point. But then we can fix the underlying
+    // problem.
+    if (!resendCurrent)
+        reply = 0;
+
     // move next from pipeline to current request
     if (!alreadyPipelinedRequests.isEmpty()) {
-        if (resendCurrent || reply->d_func()->isConnectionCloseEnabled() || socket->state() != QAbstractSocket::ConnectedState) {
+        if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
             // move the pipelined ones back to the main queue
             requeueCurrentlyPipelinedRequests();
             close();
@@ -607,19 +677,19 @@
 void QHttpNetworkConnectionChannel::detectPipeliningSupport()
 {
     // detect HTTP Pipelining support
-    QByteArray serverHeaderField = reply->headerField("Server");
+    QByteArray serverHeaderField;
     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)
+            (reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1)
             // check for not having connection close
             && (!reply->d_func()->isConnectionCloseEnabled())
             // check if it is still connected
             && (socket->state() == QAbstractSocket::ConnectedState)
+            // check for broken servers in server reply header
+            // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
+            && (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
+            && (!serverHeaderField.contains("Microsoft-IIS/5."))
+            && (!serverHeaderField.contains("Netscape-Enterprise/3."))
             ) {
         pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported;
     } else {
@@ -634,7 +704,11 @@
         connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i));
     alreadyPipelinedRequests.clear();
 
-    QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+    // 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))
+        QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
 }
 
 void QHttpNetworkConnectionChannel::eatWhitespace()
@@ -727,12 +801,11 @@
 
     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;
-    }
+    reply->d_func()->clear();
+    reply->d_func()->connection = connection;
+    reply->d_func()->connectionChannel = this;
+    reply->d_func()->autoDecompress = request.d->autoDecompress;
+    reply->d_func()->pipeliningUsed = true;
 
 #ifndef QT_NO_NETWORKPROXY
     QByteArray header = QHttpNetworkRequestPrivate::header(request,
@@ -796,9 +869,10 @@
 {
     // read the available data before closing
     if (isSocketWaiting() || isSocketReading()) {
-        state = QHttpNetworkConnectionChannel::ReadingState;
-        if (reply)
+        if (reply) {
+            state = QHttpNetworkConnectionChannel::ReadingState;
             _q_receiveReply();
+        }
     } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) {
         // re-sending request because the socket was in ClosingState
         QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
@@ -813,7 +887,14 @@
 void QHttpNetworkConnectionChannel::_q_connected()
 {
     // improve performance since we get the request sent by the kernel ASAP
-    socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
+    //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
+    // We have this commented out now. It did not have the effect we wanted. If we want to
+    // do this properly, Qt has to combine multiple HTTP requests into one buffer
+    // and send this to the kernel in one syscall and then the kernel immediately sends
+    // it as one TCP packet because of TCP_NODELAY.
+    // However, this code is currently not in Qt, so we rely on the kernel combining
+    // the requests into one TCP packet.
+
     // not sure yet if it helps, but it makes sense
     socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
 
@@ -880,7 +961,7 @@
         errorCode = QNetworkReply::UnknownNetworkError;
         break;
     }
-    QPointer<QObject> that = connection;
+    QPointer<QHttpNetworkConnection> that = connection;
     QString errorString = connection->d_func()->errorDetail(errorCode, socket, socket->errorString());
     if (send2Reply) {
         if (reply) {
@@ -935,8 +1016,16 @@
         sendRequest();
     // otherwise we do nothing
 }
+
 #endif
 
+void QHttpNetworkConnectionChannel::setConnection(QHttpNetworkConnection *c)
+{
+    // Inlining this function in the header leads to compiler error on
+    // release-armv5, on at least timebox 9.2 and 10.1.
+    connection = c;
+}
+
 QT_END_NAMESPACE
 
 #include "moc_qhttpnetworkconnectionchannel_p.cpp"