From a0beeac097db8ead6f2a56e21699f6007b9686d4 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Mon, 28 Mar 2011 14:56:26 +0200 Subject: [PATCH] QNAM: Start new HTTP backend architecture --- src/network/access/access.pri | 4 +- src/network/access/qnetworkaccessmanager.cpp | 32 +- src/network/access/qnetworkaccessmanager.h | 1 + src/network/access/qnetworkreplyhttpimpl.cpp | 1191 +++++++++++++++++++++----- src/network/access/qnetworkreplyhttpimpl_p.h | 247 ++++-- src/network/access/qnetworkreplyimpl.cpp | 9 +- 6 files changed, 1203 insertions(+), 281 deletions(-) diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 5ead3ad..0f901b8 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -14,7 +14,6 @@ HEADERS += \ access/qnetworkaccesscache_p.h \ access/qnetworkaccessbackend_p.h \ access/qnetworkaccessdebugpipebackend_p.h \ - access/qnetworkaccesshttpbackend_p.h \ access/qnetworkaccessfilebackend_p.h \ access/qnetworkaccesscachebackend_p.h \ access/qnetworkaccessftpbackend_p.h \ @@ -29,6 +28,7 @@ HEADERS += \ access/qnetworkreply_p.h \ access/qnetworkreplyimpl_p.h \ access/qnetworkreplydataimpl_p.h \ + access/qnetworkreplyhttpimpl_p.h \ access/qnetworkreplyfileimpl_p.h \ access/qabstractnetworkcache_p.h \ access/qabstractnetworkcache.h \ @@ -54,13 +54,13 @@ SOURCES += \ access/qnetworkaccessfilebackend.cpp \ access/qnetworkaccesscachebackend.cpp \ access/qnetworkaccessftpbackend.cpp \ - access/qnetworkaccesshttpbackend.cpp \ access/qnetworkcookie.cpp \ access/qnetworkcookiejar.cpp \ access/qnetworkrequest.cpp \ access/qnetworkreply.cpp \ access/qnetworkreplyimpl.cpp \ access/qnetworkreplydataimpl.cpp \ + access/qnetworkreplyhttpimpl.cpp \ access/qnetworkreplyfileimpl.cpp \ access/qabstractnetworkcache.cpp \ access/qnetworkdiskcache.cpp \ diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 34ac170..090a25c 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -67,13 +67,12 @@ #include "QtNetwork/qhttpmultipart.h" #include "qhttpmultipart_p.h" +#include "qnetworkreplyhttpimpl_p.h" + #include "qthread.h" QT_BEGIN_NAMESPACE -#ifndef QT_NO_HTTP -Q_GLOBAL_STATIC(QNetworkAccessHttpBackendFactory, httpBackend) -#endif // QT_NO_HTTP Q_GLOBAL_STATIC(QNetworkAccessFileBackendFactory, fileBackend) #ifndef QT_NO_FTP Q_GLOBAL_STATIC(QNetworkAccessFtpBackendFactory, ftpBackend) @@ -85,10 +84,6 @@ Q_GLOBAL_STATIC(QNetworkAccessDebugPipeBackendFactory, debugpipeBackend) static void ensureInitialized() { -#ifndef QT_NO_HTTP - (void) httpBackend(); -#endif // QT_NO_HTTP - #ifndef QT_NO_FTP (void) ftpBackend(); #endif @@ -356,6 +351,17 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent) ensureInitialized(); qRegisterMetaType("QNetworkReply::NetworkError"); +#ifndef QT_NO_NETWORKPROXY + qRegisterMetaType("QNetworkProxy"); +#endif +#ifndef QT_NO_OPENSSL + qRegisterMetaType >("QList"); + qRegisterMetaType("QSslConfiguration"); +#endif + qRegisterMetaType > >("QList >"); + qRegisterMetaType("QHttpNetworkRequest"); + qRegisterMetaType("QNetworkReply::NetworkError"); + qRegisterMetaType >("QSharedPointer"); } /*! @@ -967,6 +973,18 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera } } +#ifndef QT_NO_HTTP + // Since Qt 5 we use the new QNetworkReplyHttpImpl + if (scheme == QLatin1String("http") || scheme == QLatin1String("https") ) { + QNetworkReplyHttpImpl *reply = new QNetworkReplyHttpImpl(this, request, op, outgoingData); +#ifndef QT_NO_BEARERMANAGEMENT + connect(this, SIGNAL(networkSessionConnected()), + reply, SLOT(_q_networkSessionConnected())); +#endif + return reply; + } +#endif // QT_NO_HTTP + // first step: create the reply QUrl url = request.url(); QNetworkReplyImpl *reply = new QNetworkReplyImpl(this); diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index 47760b2..b22be91 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -160,6 +160,7 @@ protected: private: friend class QNetworkReplyImplPrivate; friend class QNetworkAccessHttpBackend; + friend class QNetworkReplyHttpImpl; Q_DECLARE_PRIVATE(QNetworkAccessManager) Q_PRIVATE_SLOT(d_func(), void _q_replyFinished()) diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index e75347f..d38850b 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -41,7 +41,7 @@ //#define QNETWORKACCESSHTTPBACKEND_DEBUG -#include "qnetworkaccesshttpbackend_p.h" +#include "qnetworkreplyhttpimpl_p.h" #include "qnetworkaccessmanager_p.h" #include "qnetworkaccesscache_p.h" #include "qabstractnetworkcache.h" @@ -55,6 +55,8 @@ #include "qhttpthreaddelegate_p.h" #include "qthread.h" +#include "qnetworkcookiejar.h" + #ifndef QT_NO_HTTP #include // for strchr @@ -162,55 +164,231 @@ static QHash parseHttpOptionHeader(const QByteArray &hea } } -QNetworkAccessBackend * -QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op, - const QNetworkRequest &request) const +QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manager, + const QNetworkRequest& request, + QNetworkAccessManager::Operation& operation, + QIODevice* outgoingData) + : QNetworkReply(*new QNetworkReplyHttpImplPrivate, manager) { - // check the operation - switch (op) { - case QNetworkAccessManager::GetOperation: - case QNetworkAccessManager::PostOperation: - case QNetworkAccessManager::HeadOperation: - case QNetworkAccessManager::PutOperation: - case QNetworkAccessManager::DeleteOperation: - case QNetworkAccessManager::CustomOperation: - break; + Q_D(QNetworkReplyHttpImpl); + d->manager = manager; + d->managerPrivate = manager->d_func(); + d->request = request; + d->operation = operation; + d->outgoingData = outgoingData; + d->url = request.url(); +#ifndef QT_NO_OPENSSL + d->sslConfiguration = request.sslConfiguration(); +#endif - default: - // no, we can't handle this request - return 0; + // FIXME Later maybe set to Unbuffered, especially if it is zerocopy or from cache? + QIODevice::open(QIODevice::ReadOnly); + + + // Internal code that does a HTTP reply for the synchronous Ajax + // in QtWebKit. + QVariant synchronousHttpAttribute = request.attribute( + static_cast(QNetworkRequest::SynchronousRequestAttribute)); + if (synchronousHttpAttribute.isValid()) { + d->synchronous = synchronousHttpAttribute.toBool(); + if (d->synchronous && outgoingData) { + // The synchronous HTTP is a corner case, we will put all upload data in one big QByteArray in the outgoingDataBuffer. + // Yes, this is not the most efficient thing to do, but on the other hand synchronous XHR needs to die anyway. + d->outgoingDataBuffer = QSharedPointer(new QRingBuffer()); + qint64 previousDataSize = 0; + do { + previousDataSize = d->outgoingDataBuffer->size(); + d->outgoingDataBuffer->append(d->outgoingData->readAll()); + } while (d->outgoingDataBuffer->size() != previousDataSize); + d->_q_startOperation(); + return; + } } - QUrl url = request.url(); - QString scheme = url.scheme().toLower(); - if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) - return new QNetworkAccessHttpBackend; - return 0; + if (outgoingData) { + // there is data to be uploaded, e.g. HTTP POST. + + if (!d->outgoingData->isSequential()) { + // fixed size non-sequential (random-access) + // just start the operation + QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection); + // FIXME make direct call? + } else { + bool bufferingDisallowed = + request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, + false).toBool(); + + if (bufferingDisallowed) { + // if a valid content-length header for the request was supplied, we can disable buffering + // if not, we will buffer anyway + if (request.header(QNetworkRequest::ContentLengthHeader).isValid()) { + QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection); + // FIXME make direct call? + } else { + d->state = d->Buffering; + QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection); + } + } else { + // _q_startOperation will be called when the buffering has finished. + d->state = d->Buffering; + QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection); + } + } + } else { + // No outgoing data (POST, ..) + d->_q_startOperation(); + } +} + +QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl() +{ +} + +void QNetworkReplyHttpImpl::close() +{ + Q_D(QNetworkReplyHttpImpl); + QNetworkReply::close(); + // FIXME +} + +void QNetworkReplyHttpImpl::abort() +{ + Q_D(QNetworkReplyHttpImpl); + QNetworkReply::close(); + // FIXME +} + +qint64 QNetworkReplyHttpImpl::bytesAvailable() const +{ + Q_D(const QNetworkReplyHttpImpl); + qDebug() << "QNetworkReplyHttpImpl::bytesAvailable()"; + + // FIXME cache device + if (d->cacheLoadDevice) { + return QNetworkReply::bytesAvailable() + d->cacheLoadDevice->bytesAvailable() + d->downloadMultiBuffer.byteAmount(); + } + + // FIXME 0-copy buffer + if (d->downloadZerocopyBuffer) { + return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition; + } + + // FIXME normal buffer + qDebug() << "QNetworkReplyHttpImpl::bytesAvailable() ==" << QNetworkReply::bytesAvailable() + d->downloadMultiBuffer.byteAmount(); + return QNetworkReply::bytesAvailable() + d->downloadMultiBuffer.byteAmount(); + + // FIXME } -QNetworkAccessHttpBackend::QNetworkAccessHttpBackend() - : QNetworkAccessBackend() +bool QNetworkReplyHttpImpl::isSequential () const +{ + // FIXME Maybe not for cache or 0-copy buffer + return true; +} + +qint64 QNetworkReplyHttpImpl::size() const +{ + Q_D(const QNetworkReplyHttpImpl); + //return -1; + return QNetworkReply::size(); +} + +qint64 QNetworkReplyHttpImpl::readData(char* data, qint64 maxlen) +{ + Q_D(QNetworkReplyHttpImpl); + qDebug() << "QNetworkReplyHttpImpl::readData()" << maxlen; + + // FIXME cacheload device + if (d->cacheLoadDevice) { + // FIXME bytesdownloaded, position etc? + + // There is something already in the buffer we buffered before because the user did not read() + // anything, so we read there first: + if (!d->downloadMultiBuffer.isEmpty()) { + return d->downloadMultiBuffer.read(data, maxlen); + } + + qint64 ret = d->cacheLoadDevice->read(data, maxlen); + return ret; + } + + // FIXME 0-copy buffer + if (d->downloadZerocopyBuffer) { + // bla + qint64 howMuch = qMin(maxlen, (d->downloadBufferCurrentSize - d->downloadBufferReadPosition)); + memcpy(data, d->downloadZerocopyBuffer + d->downloadBufferReadPosition, howMuch); + d->downloadBufferReadPosition += howMuch; + return howMuch; + + } + + // FIXME normal buffer + if (d->downloadMultiBuffer.isEmpty()) + return d->state == d->Finished ? -1 : 0; + // FIXME what about "Aborted" state? + + //d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); + if (maxlen == 1) { + // optimization for getChar() + *data = d->downloadMultiBuffer.getChar(); + return 1; + } + + maxlen = qMin(maxlen, d->downloadMultiBuffer.byteAmount()); + return d->downloadMultiBuffer.read(data, maxlen); +} + +void QNetworkReplyHttpImpl::setReadBufferSize(qint64 size) +{ + return; // FIXME, unsupported right now +} + +bool QNetworkReplyHttpImpl::canReadLine () const +{ + Q_D(const QNetworkReplyHttpImpl); + + if (QNetworkReply::canReadLine()) + return true; + + if (d->cacheLoadDevice) + return d->cacheLoadDevice->canReadLine() || d->downloadMultiBuffer.canReadLine(); + + return d->downloadMultiBuffer.canReadLine(); +} + +QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate() +// FIXME order etc + : QNetworkReplyPrivate() , statusCode(0) , pendingDownloadDataEmissions(new QAtomicInt()) , pendingDownloadProgressEmissions(new QAtomicInt()) , loadingFromCache(false) - , usingZerocopyDownloadBuffer(false) #ifndef QT_NO_OPENSSL - , pendingSslConfiguration(0), pendingIgnoreAllSslErrors(false) + , pendingIgnoreAllSslErrors(false) #endif , resumeOffset(0) + , outgoingData(0), + cacheLoadDevice(0), + cacheEnabled(false), cacheSaveDevice(0), + // notificationHandlingPaused(false), + bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), preMigrationDownloaded(-1), + //httpStatusCode(0), + state(Idle) + , downloadBufferReadPosition(0) + , downloadBufferCurrentSize(0) + , downloadBufferMaximumSize(0) + , downloadZerocopyBuffer(0) + , synchronous(false) + { } -QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend() +QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate() { + Q_Q(QNetworkReplyHttpImpl); // This will do nothing if the request was already finished or aborted - emit abortHttpRequest(); - -#ifndef QT_NO_OPENSSL - delete pendingSslConfiguration; -#endif + emit q->abortHttpRequest(); } /* @@ -219,14 +397,14 @@ QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend() 2) If we have a cache entry for this url populate headers so the server can return 304 3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true */ -void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache) +void QNetworkReplyHttpImplPrivate::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache) { QNetworkRequest::CacheLoadControl CacheLoadControlAttribute = - (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(); + (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(); if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) { // If the request does not already specify preferred cache-control // force reload from the network and tell any caching proxy servers to reload too - if (!request().rawHeaderList().contains("Cache-Control")) { + if (!request.rawHeaderList().contains("Cache-Control")) { httpRequest.setHeaderField("Cache-Control", "no-cache"); httpRequest.setHeaderField("Pragma", "no-cache"); } @@ -235,14 +413,14 @@ void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, // The disk cache API does not currently support partial content retrieval. // That is why we don't use the disk cache for any such requests. - if (request().hasRawHeader("Range")) + if (request.hasRawHeader("Range")) return; - QAbstractNetworkCache *nc = networkCache(); + QAbstractNetworkCache *nc = managerPrivate->networkCache; if (!nc) return; // no local cache - QNetworkCacheMetaData metaData = nc->metaData(url()); + QNetworkCacheMetaData metaData = nc->metaData(request.url()); if (!metaData.isValid()) return; // not in cache @@ -348,7 +526,7 @@ void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, loadedFromCache = false; } -static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio) +QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::convert(const QNetworkRequest::Priority& prio) { switch (prio) { case QNetworkRequest::LowPriority: @@ -361,50 +539,44 @@ static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& pr } } -void QNetworkAccessHttpBackend::postRequest() +void QNetworkReplyHttpImplPrivate::postRequest() { + Q_Q(QNetworkReplyHttpImpl); + QThread *thread = 0; - if (isSynchronous()) { + if (synchronous) { // A synchronous HTTP request uses its own thread + qDebug() << "sync!"; thread = new QThread(); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); - } else if (!manager->httpThread) { + } else if (!managerPrivate->httpThread) { // We use the manager-global thread. // At some point we could switch to having multiple threads if it makes sense. - manager->httpThread = new QThread(); - QObject::connect(manager->httpThread, SIGNAL(finished()), manager->httpThread, SLOT(deleteLater())); - manager->httpThread->start(); -#ifndef QT_NO_NETWORKPROXY - qRegisterMetaType("QNetworkProxy"); -#endif -#ifndef QT_NO_OPENSSL - qRegisterMetaType >("QList"); - qRegisterMetaType("QSslConfiguration"); -#endif - qRegisterMetaType > >("QList >"); - qRegisterMetaType("QHttpNetworkRequest"); - qRegisterMetaType("QNetworkReply::NetworkError"); - qRegisterMetaType >("QSharedPointer"); + managerPrivate->httpThread = new QThread(); + QObject::connect(managerPrivate->httpThread, SIGNAL(finished()), managerPrivate->httpThread, SLOT(deleteLater())); + managerPrivate->httpThread->start(); - thread = manager->httpThread; + thread = managerPrivate->httpThread; } else { // Asynchronous request, thread already exists - thread = manager->httpThread; + thread = managerPrivate->httpThread; } - QUrl url = request().url(); + QUrl url = request.url(); httpRequest.setUrl(url); bool ssl = url.scheme().toLower() == QLatin1String("https"); - setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl); + q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl); httpRequest.setSsl(ssl); #ifndef QT_NO_NETWORKPROXY QNetworkProxy transparentProxy, cacheProxy; - foreach (const QNetworkProxy &p, proxyList()) { + // FIXME the proxy stuff should be done in the HTTP thread + foreach (const QNetworkProxy &p, managerPrivate->queryProxy(QNetworkProxyQuery(request.url()))) { + //foreach (const QNetworkProxy &p, proxyList()) { // use the first proxy that works // for non-encrypted connections, any transparent or HTTP proxy // for encrypted, only transparent proxies @@ -427,19 +599,19 @@ void QNetworkAccessHttpBackend::postRequest() if (transparentProxy.type() == QNetworkProxy::DefaultProxy && cacheProxy.type() == QNetworkProxy::DefaultProxy) { // unsuitable proxies - QMetaObject::invokeMethod(this, "error", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection, + QMetaObject::invokeMethod(q, "error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError), - Q_ARG(QString, tr("No suitable proxy found"))); - QMetaObject::invokeMethod(this, "finished", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection); + Q_ARG(QString, q->tr("No suitable proxy found"))); + QMetaObject::invokeMethod(q, "finished", synchronous ? Qt::DirectConnection : Qt::QueuedConnection); return; } #endif bool loadedFromCache = false; - httpRequest.setPriority(convert(request().priority())); + httpRequest.setPriority(convert(request.priority())); - switch (operation()) { + switch (operation) { case QNetworkAccessManager::GetOperation: httpRequest.setOperation(QHttpNetworkRequest::Get); validateCache(httpRequest, loadedFromCache); @@ -471,7 +643,7 @@ void QNetworkAccessHttpBackend::postRequest() invalidateCache(); // for safety reasons, we don't know what the operation does httpRequest.setOperation(QHttpNetworkRequest::Custom); createUploadByteDevice(); - httpRequest.setCustomVerb(request().attribute( + httpRequest.setCustomVerb(request.attribute( QNetworkRequest::CustomVerbAttribute).toByteArray()); break; @@ -479,7 +651,7 @@ void QNetworkAccessHttpBackend::postRequest() break; // can't happen } - QList headers = request().rawHeaderList(); + QList headers = request.rawHeaderList(); if (resumeOffset != 0) { if (headers.contains("Range")) { // Need to adjust resume offset for user specified range @@ -487,7 +659,7 @@ void QNetworkAccessHttpBackend::postRequest() headers.removeOne("Range"); // We've already verified that requestRange starts with "bytes=", see canResume. - QByteArray requestRange = request().rawHeader("Range").mid(6); + QByteArray requestRange = request.rawHeader("Range").mid(6); int index = requestRange.indexOf('-'); @@ -504,20 +676,17 @@ void QNetworkAccessHttpBackend::postRequest() } foreach (const QByteArray &header, headers) - httpRequest.setHeaderField(header, request().rawHeader(header)); + httpRequest.setHeaderField(header, request.rawHeader(header)); if (loadedFromCache) { - // commented this out since it will be called later anyway - // by copyFinished() - //QNetworkAccessBackend::finished(); return; // no need to send the request! :) } - if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true) + if (request.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true) httpRequest.setPipeliningAllowed(true); if (static_cast - (request().attribute(QNetworkRequest::AuthenticationReuseAttribute, + (request.attribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual) httpRequest.setWithCredentials(false); @@ -527,7 +696,7 @@ void QNetworkAccessHttpBackend::postRequest() // For the synchronous HTTP, this is the normal way the delegate gets deleted // For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished - connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater())); + QObject::connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater())); // Set the properties it needs delegate->httpRequest = httpRequest; @@ -538,63 +707,63 @@ void QNetworkAccessHttpBackend::postRequest() delegate->ssl = ssl; #ifndef QT_NO_OPENSSL if (ssl) - delegate->incomingSslConfiguration = request().sslConfiguration(); + delegate->incomingSslConfiguration = request.sslConfiguration(); #endif // Do we use synchronous HTTP? - delegate->synchronous = isSynchronous(); + delegate->synchronous = synchronous; // The authentication manager is used to avoid the BlockingQueuedConnection communication // from HTTP thread to user thread in some cases. - delegate->authenticationManager = manager->authenticationManager; + delegate->authenticationManager = managerPrivate->authenticationManager; - if (!isSynchronous()) { + if (!synchronous) { // Tell our zerocopy policy to the delegate delegate->downloadBufferMaximumSize = - request().attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute).toLongLong(); + request.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute).toLongLong(); // These atomic integers are used for signal compression delegate->pendingDownloadData = pendingDownloadDataEmissions; delegate->pendingDownloadProgress = pendingDownloadProgressEmissions; // Connect the signals of the delegate to us - connect(delegate, SIGNAL(downloadData(QByteArray)), - this, SLOT(replyDownloadData(QByteArray)), + QObject::connect(delegate, SIGNAL(downloadData(QByteArray)), + q, SLOT(replyDownloadData(QByteArray)), Qt::QueuedConnection); - connect(delegate, SIGNAL(downloadFinished()), - this, SLOT(replyFinished()), + QObject::connect(delegate, SIGNAL(downloadFinished()), + q, SLOT(replyFinished()), Qt::QueuedConnection); - connect(delegate, SIGNAL(downloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)), - this, SLOT(replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)), + QObject::connect(delegate, SIGNAL(downloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)), + q, SLOT(replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)), Qt::QueuedConnection); - connect(delegate, SIGNAL(downloadProgress(qint64,qint64)), - this, SLOT(replyDownloadProgressSlot(qint64,qint64)), + QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)), + q, SLOT(replyDownloadProgressSlot(qint64,qint64)), Qt::QueuedConnection); - connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)), - this, SLOT(httpError(QNetworkReply::NetworkError, const QString)), + QObject::connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)), + q, SLOT(httpError(QNetworkReply::NetworkError, const QString)), Qt::QueuedConnection); #ifndef QT_NO_OPENSSL - connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)), - this, SLOT(replySslConfigurationChanged(QSslConfiguration)), + QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)), + q, SLOT(replySslConfigurationChanged(QSslConfiguration)), Qt::QueuedConnection); #endif // Those need to report back, therefire BlockingQueuedConnection - connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)), - this, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)), + QObject::connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)), + q, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)), Qt::BlockingQueuedConnection); #ifndef QT_NO_NETWORKPROXY - connect (delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), - this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + QObject::connect(delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + q, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), Qt::BlockingQueuedConnection); #endif #ifndef QT_NO_OPENSSL - connect(delegate, SIGNAL(sslErrors(QList,bool*,QList*)), - this, SLOT(replySslErrors(const QList &, bool *, QList *)), + QObject::connect(delegate, SIGNAL(sslErrors(QList,bool*,QList*)), + q, SLOT(replySslErrors(const QList &, bool *, QList *)), Qt::BlockingQueuedConnection); #endif // This signal we will use to start the request. - connect(this, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest())); - connect(this, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest())); + QObject::connect(q, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest())); + QObject::connect(q, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest())); if (uploadByteDevice) { QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice = @@ -605,7 +774,7 @@ void QNetworkAccessHttpBackend::postRequest() delegate->httpRequest.setUploadByteDevice(forwardUploadDevice); // From main thread to user thread: - QObject::connect(this, SIGNAL(haveUploadData(QByteArray, bool, qint64)), + QObject::connect(q, SIGNAL(haveUploadData(QByteArray, bool, qint64)), forwardUploadDevice, SLOT(haveDataSlot(QByteArray, bool, qint64)), Qt::QueuedConnection); QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()), forwardUploadDevice, SIGNAL(readyRead()), @@ -613,22 +782,22 @@ void QNetworkAccessHttpBackend::postRequest() // From http thread to user thread: QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)), - this, SLOT(wantUploadDataSlot(qint64))); + q, SLOT(wantUploadDataSlot(qint64))); QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)), - this, SLOT(sentUploadDataSlot(qint64))); - connect(forwardUploadDevice, SIGNAL(resetData(bool*)), - this, SLOT(resetUploadDataSlot(bool*)), + q, SLOT(sentUploadDataSlot(qint64))); + QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)), + q, SLOT(resetUploadDataSlot(bool*)), Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued! } - } else if (isSynchronous()) { - connect(this, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection); + } else if (synchronous) { + QObject::connect(q, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection); if (uploadByteDevice) { // For the synchronous HTTP use case the use thread (this one here) is blocked // so we cannot use the asynchronous upload architecture. // We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly // use the uploadByteDevice provided to us by the QNetworkReplyImpl. - // The code that is in QNetworkReplyImplPrivate::setup() makes sure it is safe to use from a thread + // The code that is in start() makes sure it is safe to use from a thread // since it only wraps a QRingBuffer delegate->httpRequest.setUploadByteDevice(uploadByteDevice.data()); } @@ -640,8 +809,8 @@ void QNetworkAccessHttpBackend::postRequest() // This call automatically moves the uploadDevice too for the asynchronous case. // Send an signal to the delegate so it starts working in the other thread - if (isSynchronous()) { - emit startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done + if (synchronous) { + emit q->startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done if (delegate->incomingErrorCode != QNetworkReply::NoError) { replyDownloadMetaData @@ -668,41 +837,61 @@ void QNetworkAccessHttpBackend::postRequest() finished(); } else { - emit startHttpRequest(); // Signal to the HTTP thread and go back to user. + emit q->startHttpRequest(); // Signal to the HTTP thread and go back to user. } } -void QNetworkAccessHttpBackend::invalidateCache() +void QNetworkReplyHttpImplPrivate::invalidateCache() { - QAbstractNetworkCache *nc = networkCache(); + QAbstractNetworkCache *nc = managerPrivate->networkCache; if (nc) - nc->remove(url()); + nc->remove(request.url()); } -void QNetworkAccessHttpBackend::open() +void QNetworkReplyHttpImplPrivate::initCacheSaveDevice() { - postRequest(); -} + Q_Q(QNetworkReplyHttpImpl); -void QNetworkAccessHttpBackend::closeDownstreamChannel() -{ - // FIXME Maybe we can get rid of this whole architecture part -} + // The disk cache does not support partial content, so don't even try to + // save any such content into the cache. + if (q->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) { + cacheEnabled = false; + return; + } -void QNetworkAccessHttpBackend::downstreamReadyWrite() -{ - // FIXME Maybe we can get rid of this whole architecture part -} + // save the meta data + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + metaData = fetchCacheMetaData(metaData); + + // save the redirect request also in the cache + QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (redirectionTarget.isValid()) { + QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes(); + attributes.insert(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget); + metaData.setAttributes(attributes); + } -void QNetworkAccessHttpBackend::setDownstreamLimited(bool b) -{ - Q_UNUSED(b); - // We know that readBuffer maximum size limiting is broken since quite a while. - // The task to fix this is QTBUG-15065 + cacheSaveDevice = managerPrivate->networkCache->prepare(metaData); + + if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) { + if (cacheSaveDevice && !cacheSaveDevice->isOpen()) + qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- " + "class %s probably needs to be fixed", + managerPrivate->networkCache->metaObject()->className()); + + managerPrivate->networkCache->remove(url); + cacheSaveDevice = 0; + cacheEnabled = false; + } } -void QNetworkAccessHttpBackend::replyDownloadData(QByteArray d) +void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d) { + Q_Q(QNetworkReplyHttpImpl); + + qDebug() << "QNetworkReplyHttpImplPrivate::replyDownloadData" << d.size(); + int pendingSignals = (int)pendingDownloadDataEmissions->fetchAndAddAcquire(-1) - 1; if (pendingSignals > 0) { @@ -722,10 +911,54 @@ void QNetworkAccessHttpBackend::replyDownloadData(QByteArray d) // processEvents() or spin an event loop when this occur. QByteDataBuffer pendingDownloadDataCopy = pendingDownloadData; pendingDownloadData.clear(); - writeDownstreamData(pendingDownloadDataCopy); + + // FIXME + //writeDownstreamData(pendingDownloadDataCopy); + // instead we do: + + // We could be closed + if (!q->isOpen()) + return; + + if (cacheEnabled && !cacheSaveDevice) { + initCacheSaveDevice(); + } + + qint64 bytesWritten = 0; + for (int i = 0; i < pendingDownloadDataCopy.bufferCount(); i++) { + QByteArray const &item = pendingDownloadDataCopy[i]; + + if (cacheSaveDevice) + cacheSaveDevice->write(item.constData(), item.size()); + downloadMultiBuffer.append(item); + + bytesWritten += item.size(); + } + pendingDownloadDataCopy.clear(); + + bytesDownloaded += bytesWritten; + lastBytesDownloaded = bytesDownloaded; + + //appendDownstreamDataSignalEmissions(); + // instead: + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (preMigrationDownloaded != Q_INT64_C(-1)) + totalSize = totalSize.toLongLong() + preMigrationDownloaded; +// pauseNotificationHandling(); + // important: At the point of this readyRead(), the data parameter list must be empty, + // else implicit sharing will trigger memcpy when the user is reading data! + emit q->readyRead(); + // emit readyRead before downloadProgress incase this will cause events to be + // processed and we get into a recursive call (as in QProgressDialog). + emit q->downloadProgress(bytesDownloaded, + totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + +// resumeNotificationHandling(); + + } -void QNetworkAccessHttpBackend::replyFinished() +void QNetworkReplyHttpImplPrivate::replyFinished() { // We are already loading from cache, we still however // got this signal because it was posted already @@ -735,8 +968,9 @@ void QNetworkAccessHttpBackend::replyFinished() finished(); } -void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode) +void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode) { + Q_Q(QNetworkReplyHttpImpl); switch (statusCode) { case 301: // Moved Permanently case 302: // Found @@ -745,32 +979,38 @@ void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode) // What do we do about the caching of the HTML note? // The response to a 303 MUST NOT be cached, while the response to // all of the others is cacheable if the headers indicate it to be - QByteArray header = rawHeader("location"); + QByteArray header = q->rawHeader("location"); QUrl url = QUrl::fromEncoded(header); if (!url.isValid()) url = QUrl(QLatin1String(header)); - redirectionRequested(url); + //redirectionRequested(url); + q->setAttribute(QNetworkRequest::RedirectionTargetAttribute, url); } } -void QNetworkAccessHttpBackend::replyDownloadMetaData +void QNetworkReplyHttpImplPrivate::replyDownloadMetaData (QList > hm, int sc,QString rp,bool pu, QSharedPointer db, qint64 contentLength) { + qDebug() << "QNetworkReplyHttpImplPrivate::replyDownloadMetaData" << contentLength << sc; + Q_Q(QNetworkReplyHttpImpl); + statusCode = sc; reasonPhrase = rp; // Download buffer if (!db.isNull()) { - reply->setDownloadBuffer(db, contentLength); - usingZerocopyDownloadBuffer = true; - } else { - usingZerocopyDownloadBuffer = false; + //setDownloadBuffer(db, contentLength); + downloadBufferPointer = db; + downloadZerocopyBuffer = downloadBufferPointer.data(); + downloadBufferCurrentSize = 0; + downloadBufferMaximumSize = contentLength; + q->setAttribute(QNetworkRequest::DownloadBufferAttribute, qVariantFromValue > (downloadBufferPointer)); } - setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu); + q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu); // reconstruct the HTTP header QList > headerMap = hm; @@ -779,7 +1019,7 @@ void QNetworkAccessHttpBackend::replyDownloadMetaData QByteArray header; for (; it != end; ++it) { - QByteArray value = rawHeader(it->first); + QByteArray value = q->rawHeader(it->first); if (!value.isEmpty()) { if (qstricmp(it->first.constData(), "set-cookie") == 0) value += '\n'; @@ -787,19 +1027,19 @@ void QNetworkAccessHttpBackend::replyDownloadMetaData value += ", "; } value += it->second; - setRawHeader(it->first, value); + q->setRawHeader(it->first, value); } - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); - setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase); + q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); + q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase); // is it a redirection? checkForRedirect(statusCode); if (statusCode >= 500 && statusCode < 600) { - QAbstractNetworkCache *nc = networkCache(); + QAbstractNetworkCache *nc = managerPrivate->networkCache; if (nc) { - QNetworkCacheMetaData metaData = nc->metaData(url()); + QNetworkCacheMetaData metaData = nc->metaData(request.url()); QNetworkHeadersPrivate cacheHeaders; cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; @@ -819,9 +1059,9 @@ void QNetworkAccessHttpBackend::replyDownloadMetaData #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) qDebug() << "Received a 304 from" << url(); #endif - QAbstractNetworkCache *nc = networkCache(); + QAbstractNetworkCache *nc = managerPrivate->networkCache; if (nc) { - QNetworkCacheMetaData oldMetaData = nc->metaData(url()); + QNetworkCacheMetaData oldMetaData = nc->metaData(request.url()); QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData); if (oldMetaData != metaData) nc->updateMetaData(metaData); @@ -839,8 +1079,10 @@ void QNetworkAccessHttpBackend::replyDownloadMetaData metaDataChanged(); } -void QNetworkAccessHttpBackend::replyDownloadProgressSlot(qint64 received, qint64 total) +void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceived, qint64 bytesTotal) { + Q_Q(QNetworkReplyHttpImpl); + // we can be sure here that there is a download buffer int pendingSignals = (int)pendingDownloadProgressEmissions->fetchAndAddAcquire(-1) - 1; @@ -850,32 +1092,63 @@ void QNetworkAccessHttpBackend::replyDownloadProgressSlot(qint64 received, qint return; } - // Now do the actual notification of new bytes - writeDownstreamDataDownloadBuffer(received, total); + if (!q->isOpen()) + return; + + if (cacheEnabled && bytesReceived == bytesTotal) { + // Write everything in one go if we use a download buffer. might be more performant. + initCacheSaveDevice(); + // need to check again if cache enabled and device exists + if (cacheSaveDevice && cacheEnabled) + cacheSaveDevice->write(downloadZerocopyBuffer, bytesTotal); + // FIXME where is it closed? + } + + bytesDownloaded = bytesReceived; + lastBytesDownloaded = bytesReceived; + + downloadBufferCurrentSize = bytesReceived; + + // Only emit readyRead when actual data is there + // emit readyRead before downloadProgress incase this will cause events to be + // processed and we get into a recursive call (as in QProgressDialog). + if (bytesDownloaded > 0) + emit q->readyRead(); + emit q->downloadProgress(bytesDownloaded, bytesTotal); } -void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &, +void QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *auth) { - authenticationRequired(auth); + Q_Q(QNetworkReplyHttpImpl); + managerPrivate->authenticationRequired(auth, q_func(), synchronous, url, &urlForLastAuthentication); +} + +#ifndef QT_NO_NETWORKPROXY +void QNetworkReplyHttpImplPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy, + QAuthenticator *authenticator) +{ + managerPrivate->proxyAuthenticationRequired(proxy, synchronous, authenticator, &lastProxyAuthentication); } +#endif -void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode, +void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCode, const QString &errorString) { #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) qDebug() << "http error!" << errorCode << errorString; #endif + // FIXME? error(errorCode, errorString); } #ifndef QT_NO_OPENSSL -void QNetworkAccessHttpBackend::replySslErrors( +void QNetworkReplyHttpImplPrivate::replySslErrors( const QList &list, bool *ignoreAll, QList *toBeIgnored) { - // Go to generic backend - sslErrors(list); + Q_Q(QNetworkReplyHttpImpl); + emit q->sslErrors(list); // Check if the callback set any ignore and return this here to http thread if (pendingIgnoreAllSslErrors) *ignoreAll = true; @@ -883,31 +1156,30 @@ void QNetworkAccessHttpBackend::replySslErrors( *toBeIgnored = pendingIgnoreSslErrorsList; } -void QNetworkAccessHttpBackend::replySslConfigurationChanged(const QSslConfiguration &c) +void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &sslConfiguration) { // Receiving the used SSL configuration from the HTTP thread - if (pendingSslConfiguration) - *pendingSslConfiguration = c; - else if (!c.isNull()) - pendingSslConfiguration = new QSslConfiguration(c); + this->sslConfiguration = sslConfiguration; } #endif // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread -void QNetworkAccessHttpBackend::resetUploadDataSlot(bool *r) +void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r) { *r = uploadByteDevice->reset(); } // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread -void QNetworkAccessHttpBackend::sentUploadDataSlot(qint64 amount) +void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 amount) { uploadByteDevice->advanceReadPointer(amount); } // Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread -void QNetworkAccessHttpBackend::wantUploadDataSlot(qint64 maxSize) +void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize) { + Q_Q(QNetworkReplyHttpImpl); + // call readPointer qint64 currentUploadDataLength = 0; char *data = const_cast(uploadByteDevice->readPointer(maxSize, currentUploadDataLength)); @@ -915,37 +1187,39 @@ void QNetworkAccessHttpBackend::wantUploadDataSlot(qint64 maxSize) QByteArray dataArray(data, currentUploadDataLength); // Communicate back to HTTP thread - emit haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size()); + emit q->haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size()); } /* A simple web page that can be used to test us: http://www.procata.com/cachetest/ */ -bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData) +bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData &metaData) { + Q_Q(QNetworkReplyHttpImpl); + setCachingEnabled(false); if (!metaData.isValid()) return false; - QAbstractNetworkCache *nc = networkCache(); + QAbstractNetworkCache *nc = managerPrivate->networkCache; Q_ASSERT(nc); - QIODevice *contents = nc->data(url()); + QIODevice *contents = nc->data(url); if (!contents) { #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) - qDebug() << "Can not send cache, the contents are 0" << url(); + qDebug() << "Can not send cache, the contents are 0" << url; #endif return false; } - contents->setParent(this); + contents->setParent(q); QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes(); int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status < 100) status = 200; // fake it - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status); - setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); - setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true); + q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status); + q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); + q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true); QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders(); QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(), @@ -955,16 +1229,19 @@ bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &m checkForRedirect(status); + cacheLoadDevice = contents; + q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead())); + q->connect(cacheLoadDevice, SIGNAL(readChannelFinished()), SLOT(_q_cacheLoadReadyRead())); + // This needs to be emitted in the event loop because it can be reached at // the direct code path of qnam.get(...) before the user has a chance // to connect any signals. - QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection); - qRegisterMetaType("QIODevice*"); - QMetaObject::invokeMethod(this, "writeDownstreamData", Qt::QueuedConnection, Q_ARG(QIODevice*, contents)); + QMetaObject::invokeMethod(q, "metaDataChanged", Qt::QueuedConnection); + QMetaObject::invokeMethod(q, "_q_cacheLoadReadyRead", Qt::QueuedConnection); #if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) - qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes"; + qDebug() << "Successfully sent cache:" << url << contents->size() << "bytes"; #endif // Set the following flag so we can ignore some signals from HTTP thread @@ -973,50 +1250,49 @@ bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &m return true; } -void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev) -{ - delete dev; - finished(); -} - #ifndef QT_NO_OPENSSL -void QNetworkAccessHttpBackend::ignoreSslErrors() +void QNetworkReplyHttpImpl::ignoreSslErrors() { - pendingIgnoreAllSslErrors = true; + Q_D(QNetworkReplyHttpImpl); + + d->pendingIgnoreAllSslErrors = true; } -void QNetworkAccessHttpBackend::ignoreSslErrors(const QList &errors) +void QNetworkReplyHttpImpl::ignoreSslErrorsImplementation(const QList &errors) { + Q_D(QNetworkReplyHttpImpl); + // the pending list is set if QNetworkReply::ignoreSslErrors(const QList &errors) // is called before QNetworkAccessManager::get() (or post(), etc.) - pendingIgnoreSslErrorsList = errors; -} - -void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const -{ - if (pendingSslConfiguration) - config = *pendingSslConfiguration; - else - config = request().sslConfiguration(); + d->pendingIgnoreSslErrorsList = errors; } -void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig) +void QNetworkReplyHttpImpl::setSslConfigurationImplementation(const QSslConfiguration &newconfig) { // Setting a SSL configuration on a reply is not supported. The user needs to set // her/his QSslConfiguration on the QNetworkRequest. Q_UNUSED(newconfig); } + +QSslConfiguration QNetworkReplyHttpImpl::sslConfigurationImplementation() const +{ + Q_D(const QNetworkReplyHttpImpl); + qDebug() << "sslConfigurationImplementation"; + return d->sslConfiguration; +} #endif -QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const +QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const { + Q_Q(const QNetworkReplyHttpImpl); + QNetworkCacheMetaData metaData = oldMetaData; QNetworkHeadersPrivate cacheHeaders; cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; - QList newHeaders = rawHeaderList(); + QList newHeaders = q->rawHeaderList(); foreach (QByteArray header, newHeaders) { QByteArray originalHeader = header; header = header.toLower(); @@ -1043,7 +1319,7 @@ QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetwo // Don't store Warning 1xx headers if (header == "warning") { - QByteArray v = rawHeader(header); + QByteArray v = q->rawHeader(header); if (v.length() == 3 && v[0] == '1' && v[1] >= '0' && v[1] <= '9' @@ -1076,7 +1352,7 @@ QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetwo qDebug() << "old" << o; } #endif - cacheHeaders.setRawHeader(originalHeader, rawHeader(header)); + cacheHeaders.setRawHeader(originalHeader, q->rawHeader(header)); } metaData.setRawHeaders(cacheHeaders.rawHeaders); @@ -1154,37 +1430,532 @@ QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetwo return metaData; } -bool QNetworkAccessHttpBackend::canResume() const +bool QNetworkReplyHttpImplPrivate::canResume() const { + Q_Q(const QNetworkReplyHttpImpl); + // Only GET operation supports resuming. - if (operation() != QNetworkAccessManager::GetOperation) + if (operation != QNetworkAccessManager::GetOperation) return false; // Can only resume if server/resource supports Range header. QByteArray acceptRangesheaderName("Accept-Ranges"); - if (!hasRawHeader(acceptRangesheaderName) || rawHeader(acceptRangesheaderName) == "none") + if (!q->hasRawHeader(acceptRangesheaderName) || q->rawHeader(acceptRangesheaderName) == "none") return false; // We only support resuming for byte ranges. - if (request().hasRawHeader("Range")) { - QByteArray range = request().rawHeader("Range"); + if (request.hasRawHeader("Range")) { + QByteArray range = request.rawHeader("Range"); if (!range.startsWith("bytes=")) return false; } // If we're using a download buffer then we don't support resuming/migration // right now. Too much trouble. - if (usingZerocopyDownloadBuffer) + if (downloadZerocopyBuffer) return false; return true; } -void QNetworkAccessHttpBackend::setResumeOffset(quint64 offset) +void QNetworkReplyHttpImplPrivate::setResumeOffset(quint64 offset) { resumeOffset = offset; } +/*! + Starts the backend. Returns true if the backend is started. Returns false if the backend + could not be started due to an unopened or roaming session. The caller should recall this + function once the session has been opened or the roaming process has finished. +*/ +bool QNetworkReplyHttpImplPrivate::start() +{ + if (!managerPrivate->networkSession) { + postRequest(); + return true; + } + + // This is not ideal. + const QString host = url.host(); + if (host == QLatin1String("localhost") || + QHostAddress(host) == QHostAddress::LocalHost || + QHostAddress(host) == QHostAddress::LocalHostIPv6) { + // Don't need an open session for localhost access. + postRequest(); + return true; + } + + if (managerPrivate->networkSession->isOpen() && + managerPrivate->networkSession->state() == QNetworkSession::Connected) { + postRequest(); + return true; + } + + return false; +} + +void QNetworkReplyHttpImplPrivate::_q_startOperation() +{ + // ensure this function is only being called once + if (state == Working) { + qDebug("QNetworkReplyImpl::_q_startOperation was called more than once"); + return; + } + state = Working; + +#ifndef QT_NO_BEARERMANAGEMENT + if (!start()) { // ### we should call that method even if bearer is not used + // backend failed to start because the session state is not Connected. + // QNetworkAccessManager will call reply->backend->start() again for us when the session + // state changes. + state = WaitingForSession; + + QNetworkSession *session = managerPrivate->networkSession.data(); + + if (session) { + Q_Q(QNetworkReplyHttpImpl); + + QObject::connect(session, SIGNAL(error(QNetworkSession::SessionError)), + q, SLOT(_q_networkSessionFailed())); + + if (!session->isOpen()) + session->open(); + } else { + qWarning("Backend is waiting for QNetworkSession to connect, but there is none!"); + } + + return; + } +#endif + + if (synchronous) { + state = Finished; + q_func()->setFinished(true); + } else { + if (state != Finished) { +// if (operation == QNetworkAccessManager::GetOperation) +// pendingNotifications.append(NotifyDownstreamReadyWrite); + +// handleNotifications(); + + } + } +} + +void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead() +{ + Q_Q(QNetworkReplyHttpImpl); + + if (state != Working) + return; + if (!cacheLoadDevice || !q->isOpen() || !cacheLoadDevice->bytesAvailable()) + return; + + // FIXME Optimize to use zerocopy download buffer if it is a QBuffer. + // Needs to be done where sendCacheContents() (?) of HTTP is emitting + // metaDataChanged ? + + + // FIXME + lastBytesDownloaded = bytesDownloaded; + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + + //pauseNotificationHandling(); + // emit readyRead before downloadProgress incase this will cause events to be + // processed and we get into a recursive call (as in QProgressDialog). + + // This readyRead() goes to the user. The user then may or may not read() anything. + emit q->readyRead(); + emit q->downloadProgress(bytesDownloaded, + totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + + // If there are still bytes available in the cacheLoadDevice then the user did not read + // in response to the readyRead() signal. This means we have to load from the cacheLoadDevice + // and buffer that stuff. This is needed to be able to properly emit finished() later. + while (cacheLoadDevice->bytesAvailable()) { + downloadMultiBuffer.append(cacheLoadDevice->readAll()); + } + + if (cacheLoadDevice->isSequential()) { + // check if end and we can read the EOF -1 + char c; + qint64 actualCount = cacheLoadDevice->read(&c, 1); + if (actualCount < 0) { + cacheLoadDevice->deleteLater(); + cacheLoadDevice = 0; + QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection); + } else if (actualCount == 1) { + // This is most probably not happening since most QIODevice returned something proper for bytesAvailable() + // and had already been "emptied". + cacheLoadDevice->ungetChar(c); + } + } else if ((!cacheLoadDevice->isSequential() && cacheLoadDevice->atEnd())) { + // This codepath is in case the cache device is a QBuffer, e.g. from QNetworkDiskCache. + cacheLoadDevice->deleteLater(); + cacheLoadDevice = 0; + QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection); + } + +} + + +void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingDataFinished() +{ + Q_Q(QNetworkReplyHttpImpl); + + // make sure this is only called once, ever. + //_q_bufferOutgoingData may call it or the readChannelFinished emission + if (state != Buffering) + return; + + // disconnect signals + QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); + QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); + + // finally, start the request + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); +} + +void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData() +{ + Q_Q(QNetworkReplyHttpImpl); + + if (!outgoingDataBuffer) { + // first call, create our buffer + outgoingDataBuffer = QSharedPointer(new QRingBuffer()); + + QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); + QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); + } + + qint64 bytesBuffered = 0; + qint64 bytesToBuffer = 0; + + // read data into our buffer + forever { + bytesToBuffer = outgoingData->bytesAvailable(); + // unknown? just try 2 kB, this also ensures we always try to read the EOF + if (bytesToBuffer <= 0) + bytesToBuffer = 2*1024; + + char *dst = outgoingDataBuffer->reserve(bytesToBuffer); + bytesBuffered = outgoingData->read(dst, bytesToBuffer); + + if (bytesBuffered == -1) { + // EOF has been reached. + outgoingDataBuffer->chop(bytesToBuffer); + + _q_bufferOutgoingDataFinished(); + break; + } else if (bytesBuffered == 0) { + // nothing read right now, just wait until we get called again + outgoingDataBuffer->chop(bytesToBuffer); + + break; + } else { + // don't break, try to read() again + outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered); + } + } +} + +#ifndef QT_NO_BEARERMANAGEMENT +void QNetworkReplyHttpImplPrivate::_q_networkSessionConnected() +{ + Q_Q(QNetworkReplyHttpImpl); + + if (!manager) + return; + + QNetworkSession *session = managerPrivate->networkSession.data(); + if (!session) + return; + + if (session->state() != QNetworkSession::Connected) + return; + + switch (state) { + case QNetworkReplyImplPrivate::Buffering: + case QNetworkReplyImplPrivate::Working: + case QNetworkReplyImplPrivate::Reconnecting: + // Migrate existing downloads to new network connection. + migrateBackend(); + break; + case QNetworkReplyImplPrivate::WaitingForSession: + // Start waiting requests. + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); + break; + default: + ; + } +} + +void QNetworkReplyHttpImplPrivate::_q_networkSessionFailed() +{ + // Abort waiting and working replies. + if (state == WaitingForSession || state == Working) { + state = Working; + error(QNetworkReplyImpl::UnknownNetworkError, + QCoreApplication::translate("QNetworkReply", "Network session error.")); + finished(); + } +} +#endif + + +// need to have this function since the reply is a private member variable +// and the special backends need to access this. +void QNetworkReplyHttpImplPrivate::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal) +{ + Q_Q(QNetworkReplyHttpImpl); + if (isFinished) + return; + emit q->uploadProgress(bytesSent, bytesTotal); +} + +QNonContiguousByteDevice* QNetworkReplyHttpImplPrivate::createUploadByteDevice() +{ + Q_Q(QNetworkReplyHttpImpl); + + if (outgoingDataBuffer) + uploadByteDevice = QSharedPointer(QNonContiguousByteDeviceFactory::create(outgoingDataBuffer)); + else if (outgoingData) { + uploadByteDevice = QSharedPointer(QNonContiguousByteDeviceFactory::create(outgoingData)); + } else { + return 0; + } + + bool bufferDisallowed = + request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, + QVariant(false)) == QVariant(true); + if (bufferDisallowed) + uploadByteDevice->disableReset(); + + // We want signal emissions only for normal asynchronous uploads + if (synchronous) + QObject::connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)), + q, SLOT(emitReplyUploadProgress(qint64,qint64))); + + return uploadByteDevice.data(); +} + +void QNetworkReplyHttpImplPrivate::_q_finished() +{ + // This gets called queued, just forward to real call then + finished(); +} + +void QNetworkReplyHttpImplPrivate::finished() +{ + Q_Q(QNetworkReplyHttpImpl); + + if (state == Finished || state == Aborted || state == WaitingForSession) + return; + + //pauseNotificationHandling(); + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (preMigrationDownloaded != Q_INT64_C(-1)) + totalSize = totalSize.toLongLong() + preMigrationDownloaded; + + // FIXME why should it be 0 + if (manager) { +#ifndef QT_NO_BEARERMANAGEMENT + QNetworkSession *session = managerPrivate->networkSession.data(); + if (session && session->state() == QNetworkSession::Roaming && + state == Working && errorCode != QNetworkReply::OperationCanceledError) { + // only content with a known size will fail with a temporary network failure error + if (!totalSize.isNull()) { + if (bytesDownloaded != totalSize) { + if (migrateBackend()) { + // either we are migrating or the request is finished/aborted + if (state == Reconnecting || state == WaitingForSession) { + //resumeNotificationHandling(); + return; // exit early if we are migrating. + } + } else { + error(QNetworkReply::TemporaryNetworkFailureError, + QNetworkReply::tr("Temporary network failure.")); + } + } + } + } +#endif + } + //resumeNotificationHandling(); + + state = Finished; + q->setFinished(true); + + //pendingNotifications.clear(); + + //pauseNotificationHandling(); + if (totalSize.isNull() || totalSize == -1) { + emit q->downloadProgress(bytesDownloaded, bytesDownloaded); + } + + if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer)) + emit q->uploadProgress(0, 0); + //resumeNotificationHandling(); + + // if we don't know the total size of or we received everything save the cache + if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize) + completeCacheSave(); + + // note: might not be a good idea, since users could decide to delete us + // which would delete the backend too... + // maybe we should protect the backend + //pauseNotificationHandling(); + emit q->readChannelFinished(); + emit q->finished(); + //resumeNotificationHandling(); +} + +void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage) +{ + Q_Q(QNetworkReplyHttpImpl); + // Can't set and emit multiple errors. + if (errorCode != QNetworkReply::NoError) { + qWarning() << "QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once."; + return; + } + + errorCode = code; + q->setErrorString(errorMessage); + + // note: might not be a good idea, since users could decide to delete us + // which would delete the backend too... + // maybe we should protect the backend + emit q->error(code); +} + +void QNetworkReplyHttpImplPrivate::metaDataChanged() +{ + // FIXME merge this with replyDownloadMetaData(); ? + + Q_Q(QNetworkReplyHttpImpl); + // 1. do we have cookies? + // 2. are we allowed to set them? + if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && manager + && (static_cast + (request.attribute(QNetworkRequest::CookieSaveControlAttribute, + QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic)) { + QList cookies = + qvariant_cast >(cookedHeaders.value(QNetworkRequest::SetCookieHeader)); + QNetworkCookieJar *jar = manager->cookieJar(); + if (jar) + jar->setCookiesFromUrl(cookies, url); + } + emit q->metaDataChanged(); +} + +/* + Migrates the backend of the QNetworkReply to a new network connection if required. Returns + true if the reply is migrated or it is not required; otherwise returns false. +*/ +bool QNetworkReplyHttpImplPrivate::migrateBackend() +{ + Q_Q(QNetworkReplyHttpImpl); + + // Network reply is already finished or aborted, don't need to migrate. + if (state == Finished || state == Aborted) + return true; + + // Backend does not support resuming download. + if (!canResume()) + return false; + + // Request has outgoing data, not migrating. + if (outgoingData) + return false; + + // Request is serviced from the cache, don't need to migrate. + if (cacheLoadDevice) + return true; + + state = Reconnecting; + +// if (backend) { +// delete backend; +// backend = 0; +// } + + cookedHeaders.clear(); + rawHeaders.clear(); + + preMigrationDownloaded = bytesDownloaded; + +// backend = manager->d_func()->findBackend(operation, request); + +// if (backend) { +// backend->setParent(q); +// backend->reply = this; +// backend->setResumeOffset(bytesDownloaded); +// } + + // FIXME + Q_ASSERT(0); + // What probably needs to be done is an abort and then re-send? + + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); + + return true; +} + + +void QNetworkReplyHttpImplPrivate::createCache() +{ + // check if we can save and if we're allowed to + if (!managerPrivate->networkCache + || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool() + || request.attribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::PreferNetwork).toInt() + == QNetworkRequest::AlwaysNetwork) + return; + cacheEnabled = true; +} + +bool QNetworkReplyHttpImplPrivate::isCachingEnabled() const +{ + return (cacheEnabled && managerPrivate->networkCache != 0); +} + +void QNetworkReplyHttpImplPrivate::setCachingEnabled(bool enable) +{ + if (!enable && !cacheEnabled) + return; // nothing to do + if (enable && cacheEnabled) + return; // nothing to do either! + + if (enable) { + if (bytesDownloaded) { + qDebug() << "x" << bytesDownloaded; + // refuse to enable in this case + qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written"); + return; + } + + createCache(); + } else { + // someone told us to turn on, then back off? + // ok... but you should make up your mind + qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false)"); + managerPrivate->networkCache->remove(url); + cacheSaveDevice = 0; + cacheEnabled = false; + } +} + +void QNetworkReplyHttpImplPrivate::completeCacheSave() +{ + if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) { + managerPrivate->networkCache->remove(url); + } else if (cacheEnabled && cacheSaveDevice) { + managerPrivate->networkCache->insert(cacheSaveDevice); + } + cacheSaveDevice = 0; + cacheEnabled = false; +} + QT_END_NAMESPACE #endif // QT_NO_HTTP diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h index 712dd2f..476022d 100644 --- a/src/network/access/qnetworkreplyhttpimpl_p.h +++ b/src/network/access/qnetworkreplyhttpimpl_p.h @@ -39,8 +39,8 @@ ** ****************************************************************************/ -#ifndef QNETWORKACCESSHTTPBACKEND_P_H -#define QNETWORKACCESSHTTPBACKEND_P_H +#ifndef QNETWORKREPLYHTTPIMPL_P_H +#define QNETWORKREPLYHTTPIMPL_P_H // // W A R N I N G @@ -53,53 +53,83 @@ // We mean it. // -#include "qhttpnetworkconnection_p.h" -#include "qnetworkaccessbackend_p.h" #include "qnetworkrequest.h" #include "qnetworkreply.h" -#include "qabstractsocket.h" #include "QtCore/qpointer.h" #include "QtCore/qdatetime.h" #include "QtCore/qsharedpointer.h" #include "qatomic.h" +#include +#include +#include +#include +#include + +#ifndef QT_NO_OPENSSL +#include +#endif + #ifndef QT_NO_HTTP QT_BEGIN_NAMESPACE -class QNetworkAccessCachedHttpConnection; - -class QNetworkAccessHttpBackendIODevice; +class QIODevice; -class QNetworkAccessHttpBackend: public QNetworkAccessBackend +class QNetworkReplyHttpImplPrivate; +class QNetworkReplyHttpImpl: public QNetworkReply { Q_OBJECT public: - QNetworkAccessHttpBackend(); - virtual ~QNetworkAccessHttpBackend(); - - virtual void open(); - virtual void closeDownstreamChannel(); + QNetworkReplyHttpImpl(QNetworkAccessManager* const, const QNetworkRequest&, QNetworkAccessManager::Operation&, QIODevice* outgoingData); + virtual ~QNetworkReplyHttpImpl(); - virtual void downstreamReadyWrite(); - virtual void setDownstreamLimited(bool b); + void close(); + void abort(); + qint64 bytesAvailable() const; + bool isSequential () const; + qint64 size() const; + qint64 readData(char*, qint64); + void setReadBufferSize(qint64 size); + bool canReadLine () const; - virtual void copyFinished(QIODevice *); #ifndef QT_NO_OPENSSL - virtual void ignoreSslErrors(); - virtual void ignoreSslErrors(const QList &errors); + void ignoreSslErrors(); + Q_INVOKABLE void ignoreSslErrorsImplementation(const QList &errors); + Q_INVOKABLE void setSslConfigurationImplementation(const QSslConfiguration &configuration); + Q_INVOKABLE QSslConfiguration sslConfigurationImplementation() const; +#endif +// Q_INVOKABLE QSslConfiguration sslConfigurationImplementation() const; +// Q_INVOKABLE void setSslConfigurationImplementation(const QSslConfiguration &configuration); - virtual void fetchSslConfiguration(QSslConfiguration &configuration) const; - virtual void setSslConfiguration(const QSslConfiguration &configuration); + Q_DECLARE_PRIVATE(QNetworkReplyHttpImpl) + Q_PRIVATE_SLOT(d_func(), void _q_startOperation()) + Q_PRIVATE_SLOT(d_func(), void _q_cacheLoadReadyRead()) + Q_PRIVATE_SLOT(d_func(), void _q_bufferOutgoingData()) + Q_PRIVATE_SLOT(d_func(), void _q_bufferOutgoingDataFinished()) +#ifndef QT_NO_BEARERMANAGEMENT + Q_PRIVATE_SLOT(d_func(), void _q_networkSessionConnected()) + Q_PRIVATE_SLOT(d_func(), void _q_networkSessionFailed()) #endif - QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const; + Q_PRIVATE_SLOT(d_func(), void _q_finished()) + Q_PRIVATE_SLOT(d_func(), void replyDownloadData(QByteArray)) + Q_PRIVATE_SLOT(d_func(), void replyFinished()) + Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64)) + Q_PRIVATE_SLOT(d_func(), void replyDownloadProgressSlot(qint64,qint64)) + Q_PRIVATE_SLOT(d_func(), void httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *)) + Q_PRIVATE_SLOT(d_func(), void httpError(QNetworkReply::NetworkError, const QString &)) +#ifndef QT_NO_OPENSSL + Q_PRIVATE_SLOT(d_func(), void replySslErrors(const QList &, bool *, QList *)) + Q_PRIVATE_SLOT(d_func(), void replySslConfigurationChanged(const QSslConfiguration&)) +#endif + Q_PRIVATE_SLOT(d_func(), void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth)) - // we return true since HTTP needs to send PUT/POST data again after having authenticated - bool needsResetableUploadData() { return true; } + Q_PRIVATE_SLOT(d_func(), void resetUploadDataSlot(bool *r)) + Q_PRIVATE_SLOT(d_func(), void wantUploadDataSlot(qint64)) + Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64)) + Q_PRIVATE_SLOT(d_func(), void emitReplyUploadProgress(qint64, qint64)) - bool canResume() const; - void setResumeOffset(quint64 offset); signals: // To HTTP thread: @@ -109,57 +139,164 @@ signals: void startHttpRequestSynchronously(); void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize); -private slots: - // From HTTP thread: - void replyDownloadData(QByteArray); - void replyFinished(); - void replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64); - void replyDownloadProgressSlot(qint64,qint64); - void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth); - void httpError(QNetworkReply::NetworkError error, const QString &errorString); -#ifndef QT_NO_OPENSSL - void replySslErrors(const QList &, bool *, QList *); - void replySslConfigurationChanged(const QSslConfiguration&); +}; + +class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate +{ +public: + + static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio); + + enum State { + Idle, // The reply is idle. + Buffering, // The reply is buffering outgoing data. + Working, // The reply is uploading/downloading data. + Finished, // The reply has finished. + Aborted, // The reply has been aborted. + WaitingForSession, // The reply is waiting for the session to open before connecting. + Reconnecting // The reply will reconnect to once roaming has completed. + }; + + QNetworkReplyHttpImplPrivate(); + ~QNetworkReplyHttpImplPrivate(); + + bool start(); + void _q_startOperation(); + + void _q_cacheLoadReadyRead(); + + void _q_bufferOutgoingData(); + void _q_bufferOutgoingDataFinished(); + +#ifndef QT_NO_BEARERMANAGEMENT + void _q_networkSessionConnected(); + void _q_networkSessionFailed(); #endif + void _q_finished(); - // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread: - void resetUploadDataSlot(bool *r); - void wantUploadDataSlot(qint64); - void sentUploadDataSlot(qint64); - bool sendCacheContents(const QNetworkCacheMetaData &metaData); + // ? + void consume(qint64 count); + + void setDownloadBuffer(QSharedPointer sp, qint64 size); + char* getDownloadBuffer(qint64 size); + + // FIXME + void finished(); + void error(QNetworkReply::NetworkError code, const QString &errorString); + void metaDataChanged(); + void redirectionRequested(const QUrl &target); + + + QNetworkAccessManager *manager; + QNetworkAccessManagerPrivate *managerPrivate; + QNetworkRequest request; + QNetworkAccessManager::Operation operation; + + QNonContiguousByteDevice* createUploadByteDevice(); + QSharedPointer uploadByteDevice; + QIODevice *outgoingData; + QSharedPointer outgoingDataBuffer; + void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal); // dup? + + + bool migrateBackend(); + quint64 resumeOffset; + bool canResume() const; + void setResumeOffset(quint64 offset); + qint64 bytesUploaded; + qint64 preMigrationDownloaded; + + void createCache(); + void completeCacheSave(); + void setCachingEnabled(bool enable); + bool isCachingEnabled() const; + void initCacheSaveDevice(); + QAbstractNetworkCache *networkCache() const; + QIODevice *cacheLoadDevice; + bool cacheEnabled; // is this for saving? + QIODevice *cacheSaveDevice; + bool loadingFromCache; + + + QUrl urlForLastAuthentication; +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy lastProxyAuthentication; + QList proxyList; +#endif -private: - QHttpNetworkRequest httpRequest; // There is also a copy in the HTTP thread int statusCode; QString reasonPhrase; + + State state; + + + // Used for normal downloading. For "zero copy" the downloadZerocopyBuffer is used + QByteDataBuffer downloadMultiBuffer; + QByteDataBuffer pendingDownloadData; // For signal compression + qint64 bytesDownloaded; + qint64 lastBytesDownloaded; + + // only used when the "zero copy" style is used. Else downloadMultiBuffer is used. + // Please note that the whole "zero copy" download buffer API is private right now. Do not use it. + qint64 downloadBufferReadPosition; + qint64 downloadBufferCurrentSize; + qint64 downloadBufferMaximumSize; + QSharedPointer downloadBufferPointer; + char* downloadZerocopyBuffer; + + + QHttpNetworkRequest httpRequest; // There is also a copy in the HTTP thread + // Will be increased by HTTP thread: QSharedPointer pendingDownloadDataEmissions; QSharedPointer pendingDownloadProgressEmissions; - bool loadingFromCache; - QByteDataBuffer pendingDownloadData; - bool usingZerocopyDownloadBuffer; + + bool synchronous; #ifndef QT_NO_OPENSSL - QSslConfiguration *pendingSslConfiguration; + QSslConfiguration sslConfiguration; bool pendingIgnoreAllSslErrors; QList pendingIgnoreSslErrorsList; #endif - quint64 resumeOffset; void validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache); void invalidateCache(); + bool sendCacheContents(const QNetworkCacheMetaData &metaData); + QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const; + + void postRequest(); - void readFromHttp(); + + void checkForRedirect(const int statusCode); -}; -class QNetworkAccessHttpBackendFactory : public QNetworkAccessBackendFactory -{ public: - virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, - const QNetworkRequest &request) const; + // From HTTP thread: + void replyDownloadData(QByteArray); + void replyFinished(); + void replyDownloadMetaData(QList >,int,QString,bool,QSharedPointer,qint64); + void replyDownloadProgressSlot(qint64,qint64); + void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth); + void httpError(QNetworkReply::NetworkError error, const QString &errorString); +#ifndef QT_NO_OPENSSL + void replySslErrors(const QList &, bool *, QList *); + void replySslConfigurationChanged(const QSslConfiguration&); +#endif +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth); +#endif + + // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread: + void resetUploadDataSlot(bool *r); + void wantUploadDataSlot(qint64); + void sentUploadDataSlot(qint64); + + + + + Q_DECLARE_PUBLIC(QNetworkReplyHttpImpl) }; QT_END_NAMESPACE diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp index 9eb505d..78e2463 100644 --- a/src/network/access/qnetworkreplyimpl.cpp +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -47,7 +47,6 @@ #include "QtCore/qdatetime.h" #include "QtNetwork/qsslconfiguration.h" #include "QtNetwork/qnetworksession.h" -#include "qnetworkaccesshttpbackend_p.h" #include "qnetworkaccessmanager_p.h" #include @@ -356,7 +355,7 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const // for HTTP, we want to send out the request as fast as possible to the network, without // invoking methods in a QueuedConnection #ifndef QT_NO_HTTP - if (qobject_cast(backend) || (backend && backend->isSynchronous())) { + if (backend && backend->isSynchronous()) { _q_startOperation(); } else { QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); @@ -1043,11 +1042,7 @@ bool QNetworkReplyImplPrivate::migrateBackend() } #ifndef QT_NO_HTTP - if (qobject_cast(backend)) { - _q_startOperation(); - } else { - QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); - } + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); #else QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); #endif // QT_NO_HTTP -- 2.7.4