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:
parent
57f209497c
commit
5b14bf342f
@ -7,6 +7,8 @@ HEADERS += \
|
|||||||
access/qhttpnetworkreply_p.h \
|
access/qhttpnetworkreply_p.h \
|
||||||
access/qhttpnetworkconnection_p.h \
|
access/qhttpnetworkconnection_p.h \
|
||||||
access/qhttpnetworkconnectionchannel_p.h \
|
access/qhttpnetworkconnectionchannel_p.h \
|
||||||
|
access/qabstractprotocolhandler_p.h \
|
||||||
|
access/qhttpprotocolhandler_p.h \
|
||||||
access/qnetworkaccessauthenticationmanager_p.h \
|
access/qnetworkaccessauthenticationmanager_p.h \
|
||||||
access/qnetworkaccessmanager.h \
|
access/qnetworkaccessmanager.h \
|
||||||
access/qnetworkaccessmanager_p.h \
|
access/qnetworkaccessmanager_p.h \
|
||||||
@ -43,6 +45,8 @@ SOURCES += \
|
|||||||
access/qhttpnetworkreply.cpp \
|
access/qhttpnetworkreply.cpp \
|
||||||
access/qhttpnetworkconnection.cpp \
|
access/qhttpnetworkconnection.cpp \
|
||||||
access/qhttpnetworkconnectionchannel.cpp \
|
access/qhttpnetworkconnectionchannel.cpp \
|
||||||
|
access/qabstractprotocolhandler.cpp \
|
||||||
|
access/qhttpprotocolhandler.cpp \
|
||||||
access/qnetworkaccessauthenticationmanager.cpp \
|
access/qnetworkaccessauthenticationmanager.cpp \
|
||||||
access/qnetworkaccessmanager.cpp \
|
access/qnetworkaccessmanager.cpp \
|
||||||
access/qnetworkaccesscache.cpp \
|
access/qnetworkaccesscache.cpp \
|
||||||
|
68
src/network/access/qabstractprotocolhandler.cpp
Normal file
68
src/network/access/qabstractprotocolhandler.cpp
Normal 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
|
88
src/network/access/qabstractprotocolhandler_p.h
Normal file
88
src/network/access/qabstractprotocolhandler_p.h
Normal 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
|
@ -139,6 +139,7 @@ private:
|
|||||||
friend class QHttpNetworkReply;
|
friend class QHttpNetworkReply;
|
||||||
friend class QHttpNetworkReplyPrivate;
|
friend class QHttpNetworkReplyPrivate;
|
||||||
friend class QHttpNetworkConnectionChannel;
|
friend class QHttpNetworkConnectionChannel;
|
||||||
|
friend class QHttpProtocolHandler;
|
||||||
|
|
||||||
Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest())
|
Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest())
|
||||||
Q_PRIVATE_SLOT(d_func(), void _q_hostLookupFinished(QHostInfo))
|
Q_PRIVATE_SLOT(d_func(), void _q_hostLookupFinished(QHostInfo))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
** 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
|
** Contact: http://www.qt-project.org/legal
|
||||||
**
|
**
|
||||||
** This file is part of the QtNetwork module of the Qt Toolkit.
|
** This file is part of the QtNetwork module of the Qt Toolkit.
|
||||||
@ -48,6 +49,8 @@
|
|||||||
|
|
||||||
#ifndef QT_NO_HTTP
|
#ifndef QT_NO_HTTP
|
||||||
|
|
||||||
|
#include <private/qhttpprotocolhandler_p.h>
|
||||||
|
|
||||||
#ifndef QT_NO_SSL
|
#ifndef QT_NO_SSL
|
||||||
# include <QtNetwork/qsslkey.h>
|
# include <QtNetwork/qsslkey.h>
|
||||||
# include <QtNetwork/qsslcipher.h>
|
# include <QtNetwork/qsslcipher.h>
|
||||||
@ -78,6 +81,7 @@ QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
|
|||||||
, proxyAuthMethod(QAuthenticatorPrivate::None)
|
, proxyAuthMethod(QAuthenticatorPrivate::None)
|
||||||
, authenticationCredentialsSent(false)
|
, authenticationCredentialsSent(false)
|
||||||
, proxyCredentialsSent(false)
|
, proxyCredentialsSent(false)
|
||||||
|
, protocolHandler(0)
|
||||||
#ifndef QT_NO_SSL
|
#ifndef QT_NO_SSL
|
||||||
, ignoreAllSslErrors(false)
|
, ignoreAllSslErrors(false)
|
||||||
#endif
|
#endif
|
||||||
@ -163,8 +167,8 @@ void QHttpNetworkConnectionChannel::init()
|
|||||||
if (!sslConfiguration.isNull())
|
if (!sslConfiguration.isNull())
|
||||||
sslSocket->setSslConfiguration(sslConfiguration);
|
sslSocket->setSslConfiguration(sslConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
protocolHandler.reset(new QHttpProtocolHandler(this));
|
||||||
|
|
||||||
#ifndef QT_NO_NETWORKPROXY
|
#ifndef QT_NO_NETWORKPROXY
|
||||||
if (proxy.type() != QNetworkProxy::NoProxy)
|
if (proxy.type() != QNetworkProxy::NoProxy)
|
||||||
@ -193,349 +197,21 @@ void QHttpNetworkConnectionChannel::close()
|
|||||||
|
|
||||||
bool QHttpNetworkConnectionChannel::sendRequest()
|
bool QHttpNetworkConnectionChannel::sendRequest()
|
||||||
{
|
{
|
||||||
if (!reply) {
|
Q_ASSERT(!protocolHandler.isNull());
|
||||||
// heh, how should that happen!
|
return protocolHandler->sendRequest();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void QHttpNetworkConnectionChannel::_q_receiveReply()
|
void QHttpNetworkConnectionChannel::_q_receiveReply()
|
||||||
{
|
{
|
||||||
Q_ASSERT(socket);
|
Q_ASSERT(!protocolHandler.isNull());
|
||||||
|
protocolHandler->_q_receiveReply();
|
||||||
|
}
|
||||||
|
|
||||||
if (!reply) {
|
void QHttpNetworkConnectionChannel::_q_readyRead()
|
||||||
if (socket->bytesAvailable() > 0)
|
{
|
||||||
qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply,"
|
Q_ASSERT(!protocolHandler.isNull());
|
||||||
<< socket->bytesAvailable() << "bytes on socket.";
|
protocolHandler->_q_readyRead();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// called when unexpectedly reading a -1 or when data is expected but socket is closed
|
// called when unexpectedly reading a -1 or when data is expected but socket is closed
|
||||||
@ -724,6 +400,7 @@ void QHttpNetworkConnectionChannel::allDone()
|
|||||||
if (!resendCurrent) {
|
if (!resendCurrent) {
|
||||||
request = QHttpNetworkRequest();
|
request = QHttpNetworkRequest();
|
||||||
reply = 0;
|
reply = 0;
|
||||||
|
protocolHandler->setReply(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// move next from pipeline to current request
|
// move next from pipeline to current request
|
||||||
@ -738,6 +415,7 @@ void QHttpNetworkConnectionChannel::allDone()
|
|||||||
|
|
||||||
request = messagePair.first;
|
request = messagePair.first;
|
||||||
reply = messagePair.second;
|
reply = messagePair.second;
|
||||||
|
protocolHandler->setReply(messagePair.second);
|
||||||
state = QHttpNetworkConnectionChannel::ReadingState;
|
state = QHttpNetworkConnectionChannel::ReadingState;
|
||||||
resendCurrent = false;
|
resendCurrent = false;
|
||||||
|
|
||||||
@ -982,32 +660,6 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const
|
|||||||
return (state & QHttpNetworkConnectionChannel::ReadingState);
|
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)
|
void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
|
||||||
{
|
{
|
||||||
Q_UNUSED(bytes);
|
Q_UNUSED(bytes);
|
||||||
@ -1224,6 +876,8 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
|||||||
reply->d_func()->errorString = errorString;
|
reply->d_func()->errorString = errorString;
|
||||||
emit reply->finishedWithError(errorCode, errorString);
|
emit reply->finishedWithError(errorCode, errorString);
|
||||||
reply = 0;
|
reply = 0;
|
||||||
|
if (protocolHandler)
|
||||||
|
protocolHandler->setReply(0);
|
||||||
}
|
}
|
||||||
// send the next request
|
// send the next request
|
||||||
QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
#include <private/qhttpnetworkreply_p.h>
|
#include <private/qhttpnetworkreply_p.h>
|
||||||
|
|
||||||
#include <private/qhttpnetworkconnection_p.h>
|
#include <private/qhttpnetworkconnection_p.h>
|
||||||
|
#include <private/qabstractprotocolhandler_p.h>
|
||||||
|
|
||||||
#ifndef QT_NO_HTTP
|
#ifndef QT_NO_HTTP
|
||||||
|
|
||||||
@ -117,6 +118,7 @@ public:
|
|||||||
QAuthenticator proxyAuthenticator;
|
QAuthenticator proxyAuthenticator;
|
||||||
bool authenticationCredentialsSent;
|
bool authenticationCredentialsSent;
|
||||||
bool proxyCredentialsSent;
|
bool proxyCredentialsSent;
|
||||||
|
QScopedPointer<QAbstractProtocolHandler> protocolHandler;
|
||||||
#ifndef QT_NO_SSL
|
#ifndef QT_NO_SSL
|
||||||
bool ignoreAllSslErrors;
|
bool ignoreAllSslErrors;
|
||||||
QList<QSslError> ignoreSslErrorsList;
|
QList<QSslError> ignoreSslErrorsList;
|
||||||
@ -193,6 +195,8 @@ public:
|
|||||||
void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket
|
void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket
|
||||||
void _q_encryptedBytesWritten(qint64 bytes); // proceed sending
|
void _q_encryptedBytesWritten(qint64 bytes); // proceed sending
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
friend class QHttpProtocolHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -164,6 +164,7 @@ private:
|
|||||||
friend class QHttpNetworkConnection;
|
friend class QHttpNetworkConnection;
|
||||||
friend class QHttpNetworkConnectionPrivate;
|
friend class QHttpNetworkConnectionPrivate;
|
||||||
friend class QHttpNetworkConnectionChannel;
|
friend class QHttpNetworkConnectionChannel;
|
||||||
|
friend class QHttpProtocolHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
431
src/network/access/qhttpprotocolhandler.cpp
Normal file
431
src/network/access/qhttpprotocolhandler.cpp
Normal 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
|
77
src/network/access/qhttpprotocolhandler_p.h
Normal file
77
src/network/access/qhttpprotocolhandler_p.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user