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/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 \
|
||||
|
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 QHttpNetworkReplyPrivate;
|
||||
friend class QHttpNetworkConnectionChannel;
|
||||
friend class QHttpProtocolHandler;
|
||||
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest())
|
||||
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) 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);
|
||||
|
@ -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
|
||||
|
@ -164,6 +164,7 @@ private:
|
||||
friend class QHttpNetworkConnection;
|
||||
friend class QHttpNetworkConnectionPrivate;
|
||||
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