QHttpNetworkRequest QHttpNetworkConnectionPrivate::predictNextRequest() { if (!highPriorityQueue.isEmpty()) return highPriorityQueue.last().first; if (!lowPriorityQueue.isEmpty()) return lowPriorityQueue.last().first; return QHttpNetworkRequest(); }
void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode) { Q_Q(QHttpNetworkConnection); if (socket && reply) { // this error matters only to this reply reply->d_func()->errorString = errorDetail(errorCode, socket); emit reply->finishedWithError(errorCode, reply->d_func()->errorString); int i = indexOf(socket); // remove the corrupt data if any reply->d_func()->eraseData(); // Clean the channel channels[i].close(); channels[i].reply = 0; channels[i].request = QHttpNetworkRequest(); channels[i].requeueCurrentlyPipelinedRequests(); // send the next request QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); } }
void QHttpNetworkConnectionChannel::allDone() { Q_ASSERT(reply); if (!reply) { qWarning() << "QHttpNetworkConnectionChannel::allDone() called without reply. Please report at http://bugreports.qt.nokia.com/"; return; } // while handling 401 & 407, we might reset the status code, so save this. bool emitFinished = reply->d_func()->shouldEmitSignals(); bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled(); detectPipeliningSupport(); handleStatus(); // handleStatus() might have removed the reply because it already called connection->emitReplyError() // 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 (reply && 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; // now the channel can be seen as free/idle again, all signal emissions for the reply have been done if (state != QHttpNetworkConnectionChannel::ClosingState) 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) { request = QHttpNetworkRequest(); reply = 0; } // move next from pipeline to current request if (!alreadyPipelinedRequests.isEmpty()) { if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) { // move the pipelined ones back to the main queue requeueCurrentlyPipelinedRequests(); close(); } 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 //_q_receiveReply(); // this was wrong, allDone gets called from that function anyway. } } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) { // this is weird. we had nothing pipelined but still bytes available. better close it. //if (socket->bytesAvailable() > 0) // close(); // // FIXME // We do not close it anymore now, but should introduce this again after having fixed // the chunked decoder in QHttpNetworkReply to read the whitespace after the last chunk. // (Currently this is worked around by readStatus in the QHttpNetworkReply ignoring // leading whitespace. QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } else if (alreadyPipelinedRequests.isEmpty()) { if (connectionCloseEnabled) if (socket->state() != QAbstractSocket::UnconnectedState) close(); if (qobject_cast<QHttpNetworkConnection*>(connection)) QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } }
// this is called from the destructor of QHttpNetworkReply. It is called when // the reply was finished correctly or when it was aborted. void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) { Q_Q(QHttpNetworkConnection); // check if the reply is currently being processed or it is pipelined in for (int i = 0; i < channelCount; ++i) { // is the reply associated the currently processing of this channel? if (channels[i].reply == reply) { channels[i].reply = 0; channels[i].request = QHttpNetworkRequest(); channels[i].resendCurrent = false; if (!reply->isFinished() && !channels[i].alreadyPipelinedRequests.isEmpty()) { // the reply had to be prematurely removed, e.g. it was not finished // therefore we have to requeue the already pipelined requests. channels[i].requeueCurrentlyPipelinedRequests(); } // if HTTP mandates we should close // or the reply is not finished yet, e.g. it was aborted // we have to close that connection if (reply->d_func()->isConnectionCloseEnabled() || !reply->isFinished()) channels[i].close(); QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); return; } // is the reply inside the pipeline of this channel already? for (int j = 0; j < channels[i].alreadyPipelinedRequests.length(); j++) { if (channels[i].alreadyPipelinedRequests.at(j).second == reply) { // Remove that HttpMessagePair channels[i].alreadyPipelinedRequests.removeAt(j); channels[i].requeueCurrentlyPipelinedRequests(); // Since some requests had already been pipelined, but we removed // one and re-queued the others // we must force a connection close after the request that is // currently in processing has been finished. if (channels[i].reply) channels[i].reply->d_func()->forceConnectionCloseEnabled = true; QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); return; } } } // remove from the high priority queue if (!highPriorityQueue.isEmpty()) { for (int j = highPriorityQueue.count() - 1; j >= 0; --j) { HttpMessagePair messagePair = highPriorityQueue.at(j); if (messagePair.second == reply) { highPriorityQueue.removeAt(j); QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); return; } } } // remove from the low priority queue if (!lowPriorityQueue.isEmpty()) { for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) { HttpMessagePair messagePair = lowPriorityQueue.at(j); if (messagePair.second == reply) { lowPriorityQueue.removeAt(j); QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); return; } } } }
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 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 // 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(); // 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) { request = QHttpNetworkRequest(); reply = 0; } // move next from pipeline to current request if (!alreadyPipelinedRequests.isEmpty()) { if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) { // move the pipelined ones back to the main queue requeueCurrentlyPipelinedRequests(); close(); } 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 //_q_receiveReply(); // this was wrong, allDone gets called from that function anyway. } } 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()) { if (qobject_cast<QHttpNetworkConnection*>(connection)) QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } }