HTTP internals: introduce protocol handlers

... to defer the decision which protocol will be used on a specific
channel. This is to allow using the SPDY protocol instead of HTTP (to
be implemented in a later commit); which protocol will be used can
only be decided after the SSL handshake.

Change-Id: I6b538320668fe4994438f0095ecdc445677cf0a6
Reviewed-by: Peter Hartmann <phartmann@blackberry.com>
This commit is contained in:
Peter Hartmann 2014-01-21 16:28:01 +01:00 committed by The Qt Project
parent 57f209497c
commit 5b14bf342f
9 changed files with 692 additions and 364 deletions

View File

@ -7,6 +7,8 @@ HEADERS += \
access/qhttpnetworkreply_p.h \
access/qhttpnetworkconnection_p.h \
access/qhttpnetworkconnectionchannel_p.h \
access/qabstractprotocolhandler_p.h \
access/qhttpprotocolhandler_p.h \
access/qnetworkaccessauthenticationmanager_p.h \
access/qnetworkaccessmanager.h \
access/qnetworkaccessmanager_p.h \
@ -43,6 +45,8 @@ SOURCES += \
access/qhttpnetworkreply.cpp \
access/qhttpnetworkconnection.cpp \
access/qhttpnetworkconnectionchannel.cpp \
access/qabstractprotocolhandler.cpp \
access/qhttpprotocolhandler.cpp \
access/qnetworkaccessauthenticationmanager.cpp \
access/qnetworkaccessmanager.cpp \
access/qnetworkaccesscache.cpp \

View File

@ -0,0 +1,68 @@
/****************************************************************************
**
** 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.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <private/qabstractprotocolhandler_p.h>
#include <private/qhttpnetworkconnectionchannel_p.h>
#ifndef QT_NO_HTTP
QT_BEGIN_NAMESPACE
QAbstractProtocolHandler::QAbstractProtocolHandler(QHttpNetworkConnectionChannel *channel)
: m_channel(channel), m_reply(0), m_socket(m_channel->socket), m_connection(m_channel->connection)
{
Q_ASSERT(m_channel);
Q_ASSERT(m_socket);
Q_ASSERT(m_connection);
}
QAbstractProtocolHandler::~QAbstractProtocolHandler()
{
}
void QAbstractProtocolHandler::setReply(QHttpNetworkReply *reply)
{
m_reply = reply;
}
QT_END_NAMESPACE
#endif // QT_NO_HTTP

View File

@ -0,0 +1,88 @@
/****************************************************************************
**
** 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.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QABSTRACTPROTOCOLHANDLER_H
#define QABSTRACTPROTOCOLHANDLER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of the Network Access API. This header file may change from
// version to version without notice, or even be removed.
//
// We mean it.
//
#ifndef QT_NO_HTTP
#include <QtCore/qglobal.h>
QT_BEGIN_NAMESPACE
class QHttpNetworkConnectionChannel;
class QHttpNetworkReply;
class QAbstractSocket;
class QHttpNetworkConnection;
class QAbstractProtocolHandler {
public:
QAbstractProtocolHandler(QHttpNetworkConnectionChannel *channel);
virtual ~QAbstractProtocolHandler();
virtual void _q_receiveReply() = 0;
virtual void _q_readyRead() = 0;
virtual bool sendRequest() = 0;
void setReply(QHttpNetworkReply *reply);
protected:
QHttpNetworkConnectionChannel *m_channel;
QHttpNetworkReply *m_reply;
QAbstractSocket *m_socket;
QHttpNetworkConnection *m_connection;
};
QT_END_NAMESPACE
#endif // QT_NO_HTTP
#endif // QABSTRACTPROTOCOLHANDLER_H

View File

@ -139,6 +139,7 @@ private:
friend class QHttpNetworkReply;
friend class QHttpNetworkReplyPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest())
Q_PRIVATE_SLOT(d_func(), void _q_hostLookupFinished(QHostInfo))

View File

@ -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);
Q_ASSERT(!protocolHandler.isNull());
protocolHandler->_q_receiveReply();
}
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;
// 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);

View File

@ -66,6 +66,7 @@
#include <private/qhttpnetworkreply_p.h>
#include <private/qhttpnetworkconnection_p.h>
#include <private/qabstractprotocolhandler_p.h>
#ifndef QT_NO_HTTP
@ -117,6 +118,7 @@ public:
QAuthenticator proxyAuthenticator;
bool authenticationCredentialsSent;
bool proxyCredentialsSent;
QScopedPointer<QAbstractProtocolHandler> protocolHandler;
#ifndef QT_NO_SSL
bool ignoreAllSslErrors;
QList<QSslError> ignoreSslErrorsList;
@ -193,6 +195,8 @@ public:
void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket
void _q_encryptedBytesWritten(qint64 bytes); // proceed sending
#endif
friend class QHttpProtocolHandler;
};
QT_END_NAMESPACE

View File

@ -164,6 +164,7 @@ private:
friend class QHttpNetworkConnection;
friend class QHttpNetworkConnectionPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
};

View File

@ -0,0 +1,431 @@
/****************************************************************************
**
** 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.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <private/qhttpprotocolhandler_p.h>
#include <private/qnoncontiguousbytedevice_p.h>
#include <private/qhttpnetworkconnectionchannel_p.h>
#ifndef QT_NO_HTTP
QT_BEGIN_NAMESPACE
QHttpProtocolHandler::QHttpProtocolHandler(QHttpNetworkConnectionChannel *channel)
: QAbstractProtocolHandler(channel)
{
}
void QHttpProtocolHandler::_q_receiveReply()
{
Q_ASSERT(m_socket);
if (!m_reply) {
if (m_socket->bytesAvailable() > 0)
qWarning() << "QAbstractProtocolHandler::_q_receiveReply() called without QHttpNetworkReply,"
<< m_socket->bytesAvailable() << "bytes on socket.";
m_channel->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*>(m_connection)) {
return;
}
QAbstractSocket::SocketState socketState = m_socket->state();
// connection might be closed to signal the end of data
if (socketState == QAbstractSocket::UnconnectedState) {
if (m_socket->bytesAvailable() <= 0) {
if (m_reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
// finish this reply. this case happens when the server did not send a content length
m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
m_channel->allDone();
return;
} else {
m_channel->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 = m_reply->d_func()->state;
switch (state) {
case QHttpNetworkReplyPrivate::NothingDoneState: {
m_reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
// fallthrough
}
case QHttpNetworkReplyPrivate::ReadingStatusState: {
qint64 statusBytes = m_reply->d_func()->readStatus(m_socket);
if (statusBytes == -1) {
// connection broke while reading status. also handled if later _q_disconnected is called
m_channel->handleUnexpectedEOF();
return;
}
bytes += statusBytes;
m_channel->lastStatus = m_reply->d_func()->statusCode;
break;
}
case QHttpNetworkReplyPrivate::ReadingHeaderState: {
QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
qint64 headerBytes = replyPrivate->readHeader(m_socket);
if (headerBytes == -1) {
// connection broke while reading headers. also handled if later _q_disconnected is called
m_channel->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 m_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;
m_channel->allDone();
break;
}
}
break;
}
case QHttpNetworkReplyPrivate::ReadingDataState: {
QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
if (m_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(m_socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress);
if (haveRead > 0) {
bytes += haveRead;
replyPrivate->totalProgress += haveRead;
// the user will get notified of it via progress signal
emit m_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) {
m_connection->d_func()->emitReplyError(m_socket, m_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(m_socket, &replyPrivate->responseData);
bytes += haveRead;
replyPrivate->totalProgress += haveRead;
if (replyPrivate->shouldEmitSignals()) {
emit m_reply->readyRead();
emit m_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(m_socket, &replyPrivate->responseData);
if (haveRead > 0) {
bytes += haveRead;
replyPrivate->totalProgress += haveRead;
if (replyPrivate->shouldEmitSignals()) {
emit m_reply->readyRead();
emit m_reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
}
} else if (haveRead == -1) {
// Some error occurred
m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
break;
}
}
// 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:
m_channel->allDone();
break;
default:
break;
}
} while (bytes != lastBytes && m_reply);
}
void QHttpProtocolHandler::_q_readyRead()
{
if (m_socket->state() == QAbstractSocket::ConnectedState && m_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 = m_socket->peek(&c, 1);
if (ret < 0) {
m_channel->_q_error(m_socket->error());
// We still need to handle the reply so it emits its signals etc.
if (m_reply)
_q_receiveReply();
return;
}
}
if (m_channel->isSocketWaiting() || m_channel->isSocketReading()) {
m_channel->state = QHttpNetworkConnectionChannel::ReadingState;
if (m_reply)
_q_receiveReply();
}
}
bool QHttpProtocolHandler::sendRequest()
{
m_reply = m_channel->reply;
if (!m_reply) {
// heh, how should that happen!
qWarning() << "QAbstractProtocolHandler::sendRequest() called without QHttpNetworkReply";
m_channel->state = QHttpNetworkConnectionChannel::IdleState;
return false;
}
switch (m_channel->state) {
case QHttpNetworkConnectionChannel::IdleState: { // write the header
if (!m_channel->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 = m_channel->request.url().scheme();
if (scheme == QLatin1String("preconnect-http")
|| scheme == QLatin1String("preconnect-https")) {
m_channel->state = QHttpNetworkConnectionChannel::IdleState;
m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
m_channel->allDone();
m_connection->preConnectFinished(); // will only decrease the counter
m_reply = 0; // so we can reuse this channel
return true; // we have a working connection and are done
}
m_channel->written = 0; // excluding the header
m_channel->bytesTotal = 0;
QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
replyPrivate->clear();
replyPrivate->connection = m_connection;
replyPrivate->connectionChannel = m_channel;
replyPrivate->autoDecompress = m_channel->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 (!m_channel->request.url().userInfo().isEmpty() && m_channel->request.withCredentials()) {
QUrl url = m_channel->request.url();
QAuthenticator &auth = m_channel->authenticator;
if (url.userName() != auth.user()
|| (!url.password().isEmpty() && url.password() != auth.password())) {
auth.setUser(url.userName());
auth.setPassword(url.password());
m_connection->d_func()->copyCredentials(m_connection->d_func()->indexOf(m_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());
m_channel->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 (m_channel->request.withCredentials())
m_connection->d_func()->createAuthorization(m_socket, m_channel->request);
#ifndef QT_NO_NETWORKPROXY
QByteArray header = QHttpNetworkRequestPrivate::header(m_channel->request,
(m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
#else
QByteArray header = QHttpNetworkRequestPrivate::header(m_channel->request, false);
#endif
m_socket->write(header);
// flushing is dangerous (QSslSocket calls transmit which might read or error)
// m_socket->flush();
QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice();
if (uploadByteDevice) {
// connect the signals so this function gets called again
QObject::connect(uploadByteDevice, SIGNAL(readyRead()), m_channel, SLOT(_q_uploadDataReadyRead()));
m_channel->bytesTotal = m_channel->request.contentLength();
m_channel->state = QHttpNetworkConnectionChannel::WritingState; // start writing data
sendRequest(); //recurse
} else {
m_channel->state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
sendRequest(); //recurse
}
break;
}
case QHttpNetworkConnectionChannel::WritingState:
{
// write the data
QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice();
if (!uploadByteDevice || m_channel->bytesTotal == m_channel->written) {
if (uploadByteDevice)
emit m_reply->dataSendProgress(m_channel->written, m_channel->bytesTotal);
m_channel->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*>(m_socket);
// if it is really an ssl socket, check more than just bytesToWrite()
while ((m_socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0))
<= socketBufferFill && m_channel->bytesTotal != m_channel->written)
#else
while (m_socket->bytesToWrite() <= socketBufferFill
&& m_channel->bytesTotal != m_channel->written)
#endif
{
// get pointer to upload data
qint64 currentReadSize = 0;
qint64 desiredReadSize = qMin(socketWriteMaxSize, m_channel->bytesTotal - m_channel->written);
const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
if (currentReadSize == -1) {
// premature eof happened
m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::UnknownNetworkError);
return false;
} else if (readPointer == 0 || currentReadSize == 0) {
// nothing to read currently, break the loop
break;
} else {
qint64 currentWriteSize = m_socket->write(readPointer, currentReadSize);
if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
// socket broke down
m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::UnknownNetworkError);
return false;
} else {
m_channel->written += currentWriteSize;
uploadByteDevice->advanceReadPointer(currentWriteSize);
emit m_reply->dataSendProgress(m_channel->written, m_channel->bytesTotal);
if (m_channel->written == m_channel->bytesTotal) {
// make sure this function is called once again
m_channel->state = QHttpNetworkConnectionChannel::WaitingState;
sendRequest();
break;
}
}
}
}
break;
}
case QHttpNetworkConnectionChannel::WaitingState:
{
QNonContiguousByteDevice* uploadByteDevice = m_channel->request.uploadByteDevice();
if (uploadByteDevice) {
QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), m_channel, SLOT(_q_uploadDataReadyRead()));
}
// HTTP pipelining
//m_connection->d_func()->fillPipeline(m_socket);
//m_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 (m_socket->bytesAvailable())
QMetaObject::invokeMethod(m_channel, "_q_receiveReply", Qt::QueuedConnection);
break;
}
case QHttpNetworkConnectionChannel::ReadingState:
// ignore _q_bytesWritten in these states
// fall through
default:
break;
}
return true;
}
QT_END_NAMESPACE
#endif // QT_NO_HTTP

View File

@ -0,0 +1,77 @@
/****************************************************************************
**
** 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.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QHTTPPROTOCOLHANDLER_H
#define QHTTPPROTOCOLHANDLER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of the Network Access API. This header file may change from
// version to version without notice, or even be removed.
//
// We mean it.
//
#include <private/qabstractprotocolhandler_p.h>
#ifndef QT_NO_HTTP
QT_BEGIN_NAMESPACE
class QHttpProtocolHandler : public QAbstractProtocolHandler {
public:
QHttpProtocolHandler(QHttpNetworkConnectionChannel *channel);
private:
virtual void _q_receiveReply() Q_DECL_OVERRIDE;
virtual void _q_readyRead() Q_DECL_OVERRIDE;
virtual bool sendRequest() Q_DECL_OVERRIDE;
};
QT_END_NAMESPACE
#endif // QT_NO_HTTP
#endif