diff options
-rw-r--r-- | src/network/.prev_CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/network/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessauthenticationmanager_p.h | 1 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessbackend.cpp | 634 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessbackend_p.h | 229 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesscachebackend.cpp | 27 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesscachebackend_p.h | 9 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessdebugpipebackend.cpp | 89 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessdebugpipebackend_p.h | 8 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessfilebackend.cpp | 81 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessfilebackend_p.h | 8 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 34 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager_p.h | 4 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 2 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyimpl.cpp | 116 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyimpl_p.h | 6 | ||||
-rw-r--r-- | src/network/network.pro | 3 |
17 files changed, 796 insertions, 464 deletions
diff --git a/src/network/.prev_CMakeLists.txt b/src/network/.prev_CMakeLists.txt index d7ef0fd712..ee82f114f1 100644 --- a/src/network/.prev_CMakeLists.txt +++ b/src/network/.prev_CMakeLists.txt @@ -5,6 +5,7 @@ ##################################################################### qt_add_module(Network + PLUGIN_TYPES networkaccessbackends SOURCES access/qabstractnetworkcache.cpp access/qabstractnetworkcache.h access/qabstractnetworkcache_p.h access/qhsts.cpp access/qhsts_p.h @@ -70,13 +71,6 @@ qt_extend_target(Network CONDITION MSVC AND (TEST_architecture_arch STREQUAL "i3 "/BASE:0x64000000" ) -qt_extend_target(Network CONDITION QT_FEATURE_ftp - SOURCES - access/qftp.cpp access/qftp_p.h - access/qnetworkaccessftpbackend.cpp access/qnetworkaccessftpbackend_p.h - kernel/qurlinfo.cpp kernel/qurlinfo_p.h -) - qt_extend_target(Network CONDITION QT_FEATURE_networkdiskcache SOURCES access/qnetworkdiskcache.cpp access/qnetworkdiskcache.h access/qnetworkdiskcache_p.h diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index e54582c299..1782922ebe 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -5,6 +5,7 @@ ##################################################################### qt_add_module(Network + PLUGIN_TYPES networkaccessbackends SOURCES access/qabstractnetworkcache.cpp access/qabstractnetworkcache.h access/qabstractnetworkcache_p.h access/qhsts.cpp access/qhsts_p.h diff --git a/src/network/access/qnetworkaccessauthenticationmanager_p.h b/src/network/access/qnetworkaccessauthenticationmanager_p.h index 361b17b594..bf57dffbb0 100644 --- a/src/network/access/qnetworkaccessauthenticationmanager_p.h +++ b/src/network/access/qnetworkaccessauthenticationmanager_p.h @@ -54,7 +54,6 @@ #include <QtNetwork/private/qtnetworkglobal_p.h> #include "qnetworkaccessmanager.h" #include "qnetworkaccesscache_p.h" -#include "qnetworkaccessbackend_p.h" #include "QtNetwork/qnetworkproxy.h" #include "QtCore/QMutex" diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp index 8013785cc1..f7e29340d9 100644 --- a/src/network/access/qnetworkaccessbackend.cpp +++ b/src/network/access/qnetworkaccessbackend.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qnetworkaccessbackend_p.h" +#include "qnetworkreplyimpl_p.h" #include "qnetworkaccessmanager_p.h" #include "qnetworkrequest.h" #include "qnetworkreply.h" @@ -73,22 +74,24 @@ public: Q_GLOBAL_STATIC(QNetworkAccessBackendFactoryData, factoryData) QBasicAtomicInt QNetworkAccessBackendFactoryData::valid = Q_BASIC_ATOMIC_INITIALIZER(0); -QNetworkAccessBackendFactory::QNetworkAccessBackendFactory() -{ - QMutexLocker locker(&factoryData()->mutex); - factoryData()->append(this); -} - -QNetworkAccessBackendFactory::~QNetworkAccessBackendFactory() +class QNetworkAccessBackendPrivate : public QObjectPrivate { - if (QNetworkAccessBackendFactoryData::valid.loadRelaxed()) { - QMutexLocker locker(&factoryData()->mutex); - factoryData()->removeAll(this); - } -} +public: + QNetworkAccessBackend::TargetTypes m_targetTypes; + QNetworkAccessBackend::SecurityFeatures m_securityFeatures; + QNetworkAccessBackend::IOFeatures m_ioFeatures; + QSharedPointer<QNonContiguousByteDevice> uploadByteDevice; + QIODevice *wrappedUploadByteDevice; + QNetworkReplyImplPrivate *m_reply = nullptr; + QNetworkAccessManagerPrivate *m_manager = nullptr; + + bool m_canCache = false; + bool m_isSynchronous = false; +}; -QNetworkAccessBackend *QNetworkAccessManagerPrivate::findBackend(QNetworkAccessManager::Operation op, - const QNetworkRequest &request) +QNetworkAccessBackend * +QNetworkAccessManagerPrivate::findBackend(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) { if (QNetworkAccessBackendFactoryData::valid.loadRelaxed()) { QMutexLocker locker(&factoryData()->mutex); @@ -97,7 +100,7 @@ QNetworkAccessBackend *QNetworkAccessManagerPrivate::findBackend(QNetworkAccessM while (it != end) { QNetworkAccessBackend *backend = (*it)->create(op, request); if (backend) { - backend->manager = this; + backend->setManagerPrivate(this); return backend; // found a factory that handled our request } ++it; @@ -122,260 +125,619 @@ QStringList QNetworkAccessManagerPrivate::backendSupportedSchemes() const return QStringList(); } -QNonContiguousByteDevice* QNetworkAccessBackend::createUploadByteDevice() -{ - if (reply->outgoingDataBuffer) - uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(reply->outgoingDataBuffer); - else if (reply->outgoingData) { - uploadByteDevice = QNonContiguousByteDeviceFactory::createShared(reply->outgoingData); - } else { - return nullptr; - } +/*! + \enum QNetworkAccessBackend::TargetType + + Use the values in this enum to specify what type of target + the plugin supports. Setting the right type can be important, + for example: proxyList() is only available for a Networked + plugin. + + \value Networked + The plugin supports and expect to connect to networked + resources. E.g. over TCP, UDP or similar. + \value Local + The plugin supports and expects to access local files, + generate data and/or locally connected devices. +*/ - // We want signal emissions only for normal asynchronous uploads - if (!isSynchronous()) - connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)), this, SLOT(emitReplyUploadProgress(qint64,qint64))); +/*! + \enum QNetworkAccessBackend::SecurityFeature - return uploadByteDevice.data(); -} + Use the values in this enum to specify what type of security + features the plugin may utilize. Setting the right type(s) + can be important, for example: setSslConfiguration() may not + be called for any plugin that do not claim to support TLS. + + \value None + No specific features are claimed to be supported. + \value TLS + The plugin supports and expects to use TLS. +*/ + +/*! + \enum QNetworkAccessBackend::IOFeature + + Use the values in this enum to specify what type of IO + features the plugin may utilize. + + \value None + No specific features are claimed to be supported. + \value ZeroCopy + The plugin will have raw data available in contiguous + segments and can return a pointer to the data at request. + Claiming to support this requires implementing readPointer() + and advanceReadPointer(). + \value NeedResetableUpload + The plugin may encounter scenarios where data to upload that + has already been consumed needs to be restored and re-sent. + E.g. some data was consumed and sent before a redirect + response was received, and after the redirect the + previously-consumed data needs to be re-sent. + \omitvalue SupportsSynchronousMode +*/ + +/*! + Constructs the QNetworkAccessBackend. + You can opt in to specific backend behaviors with \a targetTypes, + \a securityFeatures and \a ioFeatures. + See their respective enums and values for more information. -// need to have this function since the reply is a private member variable -// and the special backends need to access this. -void QNetworkAccessBackend::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal) + \sa TargetType, SecurityFeature, IOFeature +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes, + SecurityFeatures securityFeatures, + IOFeatures ioFeatures) + : QObject(*(new QNetworkAccessBackendPrivate), nullptr) { - if (reply->isFinished) - return; - reply->emitUploadProgress(bytesSent, bytesTotal); + Q_D(QNetworkAccessBackend); + d->m_targetTypes = targetTypes; + d->m_securityFeatures = securityFeatures; + d->m_ioFeatures = ioFeatures; } -QNetworkAccessBackend::QNetworkAccessBackend() - : manager(nullptr) - , reply(nullptr) - , synchronous(false) +/*! + \overload +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes) + : QNetworkAccessBackend(targetTypes, SecurityFeature::None, IOFeature::None) { } -QNetworkAccessBackend::~QNetworkAccessBackend() +/*! + \overload +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes, + SecurityFeatures securityFeatures) + : QNetworkAccessBackend(targetTypes, securityFeatures, IOFeature::None) { } -void QNetworkAccessBackend::downstreamReadyWrite() +/*! + \overload +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes, IOFeatures ioFeatures) + : QNetworkAccessBackend(targetTypes, SecurityFeature::None, ioFeatures) { - // do nothing } -void QNetworkAccessBackend::setDownstreamLimited(bool b) +/*! + Destructs the QNetworkAccessBackend base class. +*/ +QNetworkAccessBackend::~QNetworkAccessBackend() { } + +/*! + Returns the security related features that the backend claims to + support. + + \sa SecurityFeature +*/ +QNetworkAccessBackend::SecurityFeatures QNetworkAccessBackend::securityFeatures() const noexcept { - Q_UNUSED(b); - // do nothing + return d_func()->m_securityFeatures; } -void QNetworkAccessBackend::copyFinished(QIODevice *) +/*! + Returns the TargetTypes that the backend claims to target. + + \sa TargetType +*/ +QNetworkAccessBackend::TargetTypes QNetworkAccessBackend::targetTypes() const noexcept { - // do nothing + return d_func()->m_targetTypes; } -void QNetworkAccessBackend::ignoreSslErrors() +/*! + Returns the I/O features that the backend claims to support. + + \sa IOFeature +*/ +QNetworkAccessBackend::IOFeatures QNetworkAccessBackend::ioFeatures() const noexcept { - // do nothing + return d_func()->m_ioFeatures; } -void QNetworkAccessBackend::ignoreSslErrors(const QList<QSslError> &errors) +/*! + Prepares the backend and calls open(). + E.g. for TargetType::Networked it will prepare proxyList(). + + \sa TargetType, targetTypes +*/ +bool QNetworkAccessBackend::start() { - Q_UNUSED(errors); - // do nothing + Q_D(QNetworkAccessBackend); +#ifndef QT_NO_NETWORKPROXY + if (targetTypes() & QNetworkAccessBackend::TargetType::Networked) + d->m_reply->proxyList = d->m_manager->queryProxy(QNetworkProxyQuery(url())); +#endif + + // now start the request + open(); + return true; } -void QNetworkAccessBackend::fetchSslConfiguration(QSslConfiguration &) const +#if QT_CONFIG(ssl) +/*! + Passes a \a configuration with the user's desired TLS + configuration. If you don't have the TLS security feature this + may not be called. + + \sa SecurityFeature, securityFeatures +*/ +void QNetworkAccessBackend::setSslConfiguration(const QSslConfiguration &configuration) { - // do nothing + Q_UNUSED(configuration); + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden setSslConfiguration.", + metaObject()->className()); + } } -void QNetworkAccessBackend::setSslConfiguration(const QSslConfiguration &) +/*! + Override this and return the QSslConfiguration used if you + have the TLS security feature + + \sa SecurityFeature, securityFeatures +*/ +QSslConfiguration QNetworkAccessBackend::sslConfiguration() const { - // do nothing + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden sslConfiguration.", + metaObject()->className()); + } + return {}; } +#endif -QNetworkCacheMetaData QNetworkAccessBackend::fetchCacheMetaData(const QNetworkCacheMetaData &) const +/*! + This function will be called when the user wants to ignore + all TLS handshake errors. Derive this function if TLS is + supported. + + \sa SecurityFeature, securityFeatures +*/ +void QNetworkAccessBackend::ignoreSslErrors() { - return QNetworkCacheMetaData(); + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden ignoreSslErrors.", + metaObject()->className()); + } } -QNetworkAccessManager::Operation QNetworkAccessBackend::operation() const +/*! + This function will be called when the user wants to ignore + specific \a errors. Derive this function if TLS is supported. + + \sa SecurityFeature, securityFeatures +*/ +void QNetworkAccessBackend::ignoreSslErrors(const QList<QSslError> &errors) { - return reply->operation; + Q_UNUSED(errors); + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden ignoreSslErrors.", + metaObject()->className()); + } } -QNetworkRequest QNetworkAccessBackend::request() const +/*! + The data which the returned value views must stay valid until + at least the next call to a non-const function. advanceReadPointer + will be called if any of the data was used. + + Note: This will only be called if IOFeature::ZeroCopy was + specified in the call to the constructor. + + \sa advanceReadPointer, read +*/ +QByteArrayView QNetworkAccessBackend::readPointer() { - return reply->request; + if (ioFeatures() & IOFeature::ZeroCopy) { + qWarning("Backend (%s) claiming to support ZeroCopy hasn't overridden readPointer.", + metaObject()->className()); + } + return {}; } -#ifndef QT_NO_NETWORKPROXY -QList<QNetworkProxy> QNetworkAccessBackend::proxyList() const +/*! + This function is to notify your class that \a distance + bytes have been read using readPointer and next time + readPointer() is called those bytes should not be included. + + Note: This will only be called if IOFeature::ZeroCopy was + specified in the call to the constructor. + + \sa readPointer +*/ +void QNetworkAccessBackend::advanceReadPointer(qint64 distance) { - return reply->proxyList; + Q_UNUSED(distance); + if (ioFeatures() & IOFeature::ZeroCopy) { + qWarning("Backend (%s) claiming to support ZeroCopy hasn't overridden advanceReadPointer.", + metaObject()->className()); + } } -#endif -QAbstractNetworkCache *QNetworkAccessBackend::networkCache() const +/*! + Implement this function to support reading from the resource + made available by your plugin. + Store data in \a data, up to a maximum of \a maxlen bytes. + Then return the total amount of bytes that was copied. + + \sa readPointer, wantToRead +*/ +qint64 QNetworkAccessBackend::read(char *data, qint64 maxlen) { - if (!manager) - return nullptr; - return manager->networkCache; + Q_UNUSED(data); + Q_UNUSED(maxlen); + if ((ioFeatures() & IOFeature::ZeroCopy) == 0) { + qWarning("Backend (%s) is not ZeroCopy and has not implemented read(...)!", + metaObject()->className()); + } + return 0; } -void QNetworkAccessBackend::setCachingEnabled(bool enable) +/*! + This is called before we read if there are no bytes available + and we are ready to read more. Return \c true if new data was + made available. + + \sa read, readPointer +*/ +bool QNetworkAccessBackend::wantToRead() { - reply->setCachingEnabled(enable); + // Base implementation does nothing + return false; } -bool QNetworkAccessBackend::isCachingEnabled() const +#if QT_CONFIG(networkproxy) +/*! + Returns a list of proxies configured for the URL returned by + url(). + + It is only valid to call this function if TargetType::Networked + was specified in the call to the constructor. +*/ +QList<QNetworkProxy> QNetworkAccessBackend::proxyList() const { - return reply->isCachingEnabled(); + Q_ASSERT(targetTypes() & TargetType::Networked); + return d_func()->m_reply->proxyList; } +#endif -qint64 QNetworkAccessBackend::nextDownstreamBlockSize() const +/*! + Returns the current URL of the reply +*/ +QUrl QNetworkAccessBackend::url() const { - return reply->nextDownstreamBlockSize(); + return d_func()->m_reply->url; } -void QNetworkAccessBackend::writeDownstreamData(QByteDataBuffer &list) +/*! + Sets the URL of the reply. This could e.g. be needed if a + redirect or similar was performed. +*/ +void QNetworkAccessBackend::setUrl(const QUrl &url) { - reply->appendDownstreamData(list); + d_func()->m_reply->url = url; } -void QNetworkAccessBackend::writeDownstreamData(QIODevice *data) +/*! + Returns the value of the \a header. + If no such header was known it returns a default-constructed + QVariant. + + \sa setHeader, rawHeader, setRawHeader +*/ +QVariant QNetworkAccessBackend::header(QNetworkRequest::KnownHeaders header) const { - reply->appendDownstreamData(data); + return d_func()->m_reply->cookedHeaders.value(header); } -// not actually appending data, it was already written to the user buffer -void QNetworkAccessBackend::writeDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal) +/*! + Sets the value of the \a header to \a value. + This can be queried on the QNetworkReply instance which was + returned when calling one of the appropriate functions on + QNetworkAccessManager. + + \sa header, rawHeader, setRawHeader +*/ +void QNetworkAccessBackend::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) { - reply->appendDownstreamDataDownloadBuffer(bytesReceived, bytesTotal); + d_func()->m_reply->setCookedHeader(header, value); } -char* QNetworkAccessBackend::getDownloadBuffer(qint64 size) +/*! + Returns the value of the \a header. + If no such header was known it returns a default-constructed + QVariant. + + \sa setHeader, rawHeader, setRawHeader +*/ +QByteArray QNetworkAccessBackend::rawHeader(const QByteArray &header) const { - return reply->getDownloadBuffer(size); + return d_func()->m_reply->q_func()->rawHeader(header); } -QVariant QNetworkAccessBackend::header(QNetworkRequest::KnownHeaders header) const +/*! + Sets the value of the \a header to \a value. + + This value is accessible on the QNetworkReply instance which was + returned when calling one of the appropriate functions on + QNetworkAccessManager. + + \sa header, rawHeader, setRawHeader +*/ +void QNetworkAccessBackend::setRawHeader(const QByteArray &header, const QByteArray &value) { - return reply->q_func()->header(header); + d_func()->m_reply->setRawHeader(header, value); } -void QNetworkAccessBackend::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) +/*! + Returns the operation which was requested when calling + QNetworkAccessManager. +*/ +QNetworkAccessManager::Operation QNetworkAccessBackend::operation() const { - reply->setCookedHeader(header, value); + return d_func()->m_reply->operation; } -bool QNetworkAccessBackend::hasRawHeader(const QByteArray &headerName) const +/*! + Returns \c true if setCachingEnabled was previously called with \c true. + Returns \c false otherwise, which is the default value. + + \sa setCachingEnabled +*/ +bool QNetworkAccessBackend::isCachingEnabled() const { - return reply->q_func()->hasRawHeader(headerName); + return d_func()->m_canCache; } -QByteArray QNetworkAccessBackend::rawHeader(const QByteArray &headerName) const +/*! + If \a canCache is \c true then this hints to us that we can cache + the reply that is created. + + \sa isCachingEnabled +*/ +void QNetworkAccessBackend::setCachingEnabled(bool canCache) { - return reply->q_func()->rawHeader(headerName); + d_func()->m_canCache = canCache; } -QList<QByteArray> QNetworkAccessBackend::rawHeaderList() const +/*! + Set \a attribute to \a value. If \c{value.isValid()} returns + \c false then the attribute is unset. + + This value is accessible on the QNetworkReply instance which was + returned when calling one of the appropriate functions on + QNetworkAccessManager. +*/ +void QNetworkAccessBackend::setAttribute(QNetworkRequest::Attribute attribute, + const QVariant &value) { - return reply->q_func()->rawHeaderList(); + Q_D(QNetworkAccessBackend); + if (value.isValid()) + d->m_reply->attributes.insert(attribute, value); + else + d->m_reply->attributes.remove(attribute); } -void QNetworkAccessBackend::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue) +/*! + Creates a QIODevice for the data provided to upload, if any. + + Emission of upload progress is handled internally as the device + gets read from. + + Returns a pointer to a device with data or nullptr if there was + no data to upload. +*/ +QIODevice *QNetworkAccessBackend::createUploadByteDevice() { - reply->setRawHeader(headerName, headerValue); + Q_D(QNetworkAccessBackend); + + if (d->m_reply->outgoingDataBuffer) + d->uploadByteDevice = + QNonContiguousByteDeviceFactory::createShared(d->m_reply->outgoingDataBuffer); + else if (d->m_reply->outgoingData) { + d->uploadByteDevice = + QNonContiguousByteDeviceFactory::createShared(d->m_reply->outgoingData); + } else { + return nullptr; + } + + // We want signal emissions only for normal asynchronous uploads + if (!isSynchronous()) { + connect(d->uploadByteDevice.data(), &QNonContiguousByteDevice::readProgress, this, + [this](qint64 a, qint64 b) { + Q_D(QNetworkAccessBackend); + if (!d->m_reply->isFinished) + d->m_reply->emitUploadProgress(a, b); + }); + } + + d->wrappedUploadByteDevice = QNonContiguousByteDeviceFactory::wrap(d->uploadByteDevice.data()); + return d->wrappedUploadByteDevice; } -QVariant QNetworkAccessBackend::attribute(QNetworkRequest::Attribute code) const +/*! + Returns the upload byte device associated with the current + request. This does not create the request but simply returns + the pointer stored in this base class so it doesn't need to be + stored in the subclass too. +*/ +QIODevice *QNetworkAccessBackend::uploadByteDevice() { - return reply->q_func()->attribute(code); + return d_func()->wrappedUploadByteDevice; } -void QNetworkAccessBackend::setAttribute(QNetworkRequest::Attribute code, const QVariant &value) +/*! + \internal + Returns \c true if synchronous mode is enabled. + If it is disabled or not supported it will return \c {false}. +*/ +bool QNetworkAccessBackend::isSynchronous() const { - if (value.isValid()) - reply->attributes.insert(code, value); - else - reply->attributes.remove(code); + return d_func()->m_isSynchronous; } -QUrl QNetworkAccessBackend::url() const + +/*! + \internal + Enables or disables synchronous mode depending on \a synchronous + if the backend supports it. Otherwise it will always be disabled. +*/ +void QNetworkAccessBackend::setSynchronous(bool synchronous) { - return reply->url; + if ((ioFeatures() & IOFeature::SupportsSynchronousMode) == 0) + return; + d_func()->m_isSynchronous = synchronous; } -void QNetworkAccessBackend::setUrl(const QUrl &url) +/*! + Call this slot when you have more data available to notify + the backend that we can attempt to read again. +*/ +void QNetworkAccessBackend::readyRead() { - reply->url = url; + d_func()->m_reply->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); } +/*! + Call this slot when there will be no more data available, + regardless of whether the transfer was successful or unsuccessful. + For unsuccessful transfers make sure to call error() first! +*/ void QNetworkAccessBackend::finished() { - reply->finished(); + d_func()->m_reply->finished(); } +/*! + Call this slot if an error occurs. An error would be something + you cannot recover from (e.g. the file requested is missing). + The \a code and \a errorString is transferred to and stored in + the QNetworkReply and the \a code is emitted through the + QNetworkReply::errorOccurred() signal. +*/ void QNetworkAccessBackend::error(QNetworkReply::NetworkError code, const QString &errorString) { - reply->error(code, errorString); + Q_ASSERT(!d_func()->m_reply->isFinished); + d_func()->m_reply->error(code, errorString); } #ifndef QT_NO_NETWORKPROXY +/*! + Call this slot if, when connecting through a proxy, it requests + authentication. This may cause the + QNetworkAccessManager::proxyAuthenticationRequired() signal to be + emitted if the credentials are not already stored in an internal + cache. + To be able to make the lookup in the cache and potentially the + subsequent request the \a proxy needs to be known. The credentials + will be stored in \a authenticator. While \a authenticator is a + pointer, passing \c nullptr is invalid. +*/ void QNetworkAccessBackend::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) { - manager->proxyAuthenticationRequired(QUrl(), proxy, synchronous, authenticator, &reply->lastProxyAuthentication); + Q_D(QNetworkAccessBackend); + Q_ASSERT(authenticator); + d->m_manager->proxyAuthenticationRequired(QUrl(), proxy, isSynchronous(), authenticator, + &d->m_reply->lastProxyAuthentication); } #endif +/*! + Call this slot if the remote resource requests authentication. + This may cause the + QNetworkAccessManager::authenticationRequired() signal to be + emitted if the credentials are not already stored in an internal + cache. + The credentials will be stored in \a authenticator. While + \a authenticator is a pointer, passing \c nullptr is invalid. +*/ void QNetworkAccessBackend::authenticationRequired(QAuthenticator *authenticator) { - manager->authenticationRequired(authenticator, reply->q_func(), synchronous, reply->url, &reply->urlForLastAuthentication); + Q_D(QNetworkAccessBackend); + Q_ASSERT(authenticator); + d->m_manager->authenticationRequired(authenticator, d->m_reply->q_func(), isSynchronous(), + d->m_reply->url, &d->m_reply->urlForLastAuthentication); } +/*! + Call this slot, if appropriate, after having processed and + updated metadata (e.g. headers). +*/ void QNetworkAccessBackend::metaDataChanged() { - reply->metaDataChanged(); + d_func()->m_reply->metaDataChanged(); } -void QNetworkAccessBackend::redirectionRequested(const QUrl &target) +/*! + Call this slot if, when connecting to the resource, a redirect + to \a destination was requested. +*/ +void QNetworkAccessBackend::redirectionRequested(const QUrl &destination) { - reply->redirectionRequested(target); + d_func()->m_reply->redirectionRequested(destination); } -void QNetworkAccessBackend::encrypted() +/*! + \internal +*/ +void QNetworkAccessBackend::setReplyPrivate(QNetworkReplyImplPrivate *reply) { -#ifndef QT_NO_SSL - reply->encrypted(); -#endif + d_func()->m_reply = reply; } -void QNetworkAccessBackend::sslErrors(const QList<QSslError> &errors) +/*! + \internal +*/ +void QNetworkAccessBackend::setManagerPrivate(QNetworkAccessManagerPrivate *manager) { -#ifndef QT_NO_SSL - reply->sslErrors(errors); -#else - Q_UNUSED(errors); -#endif + d_func()->m_manager = manager; } /*! - Starts the backend. Returns \c true if the backend is started. Returns \c 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. + Returns the network cache object that was available when the + request was started. Returns \c nullptr if none was available. */ -bool QNetworkAccessBackend::start() +QAbstractNetworkCache *QNetworkAccessBackend::networkCache() const { -#ifndef QT_NO_NETWORKPROXY - reply->proxyList = manager->queryProxy(QNetworkProxyQuery(url())); -#endif + return d_func()->m_manager->networkCache; +} - // now start the request - open(); - return true; +// -- QNetworkAccessBackendFactory +/*! + Constructs QNetworkAccessBackendFactory +*/ +QNetworkAccessBackendFactory::QNetworkAccessBackendFactory() +{ + if (factoryData()) + factoryData->append(this); } +/*! + Destructs QNetworkAccessBackendFactory +*/ +QNetworkAccessBackendFactory::~QNetworkAccessBackendFactory() = default; + QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h index b18f30c287..0f7b21684a 100644 --- a/src/network/access/qnetworkaccessbackend_p.h +++ b/src/network/access/qnetworkaccessbackend_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -40,153 +40,108 @@ #ifndef QNETWORKACCESSBACKEND_P_H #define QNETWORKACCESSBACKEND_P_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 <QtNetwork/private/qtnetworkglobal_p.h> -#include "qnetworkreplyimpl_p.h" -#include "QtCore/qobject.h" -Q_MOC_INCLUDE(<QAuthenticator>) -Q_MOC_INCLUDE(<QtNetwork/QSslError>) +#include <QtNetwork/qtnetworkglobal.h> -QT_BEGIN_NAMESPACE +#include <QtNetwork/qnetworkrequest.h> +#include <QtNetwork/qnetworkaccessmanager.h> +#include <QtNetwork/qnetworkreply.h> -class QNetworkProxy; -class QNetworkProxyQuery; -class QNetworkRequest; -class QStringList; -class QUrl; -class QSslConfiguration; +#include <QtCore/qobject.h> +#include <QtCore/qflags.h> +#include <QtCore/qbytearrayview.h> + +#if QT_CONFIG(ssl) +#include <QtNetwork/qsslconfiguration.h> +#endif + +QT_BEGIN_NAMESPACE -class QNetworkAccessManagerPrivate; class QNetworkReplyImplPrivate; -class QAbstractNetworkCache; -class QNetworkCacheMetaData; -class QNonContiguousByteDevice; - -// Should support direct file upload from disk or download to disk. -// -// - The HTTP handler will use two QIODevices for communication (pull mechanism) -// - KIO uses a pull mechanism too (data/dataReq signals) -class QNetworkAccessBackend : public QObject +class QNetworkAccessManagerPrivate; +class QNetworkAccessBackendPrivate; +class Q_NETWORK_EXPORT QNetworkAccessBackend : public QObject { Q_OBJECT + Q_DECLARE_PRIVATE(QNetworkAccessBackend); + public: - QNetworkAccessBackend(); + enum class TargetType { + Networked = 0x1, // We need to query for proxy in case it is needed + Local = 0x2, // Local file, generated data or local device + }; + Q_ENUM(TargetType) + Q_DECLARE_FLAGS(TargetTypes, TargetType) + + enum class SecurityFeature { + None = 0x0, + TLS = 0x1, // We need to set QSslConfiguration + }; + Q_ENUM(SecurityFeature) + Q_DECLARE_FLAGS(SecurityFeatures, SecurityFeature) + + enum class IOFeature { + None = 0x0, + ZeroCopy = 0x1, // readPointer and advanceReadPointer() is available! + NeedResetableUpload = 0x2, // Need to buffer upload data + SupportsSynchronousMode = 0x4, // Used for XMLHttpRequest + }; + Q_ENUM(IOFeature) + Q_DECLARE_FLAGS(IOFeatures, IOFeature) + + QNetworkAccessBackend(TargetTypes targetTypes, SecurityFeatures securityFeatures, + IOFeatures ioFeatures); + QNetworkAccessBackend(TargetTypes targetTypes); + QNetworkAccessBackend(TargetTypes targetTypes, SecurityFeatures securityFeatures); + QNetworkAccessBackend(TargetTypes targetTypes, IOFeatures ioFeatures); virtual ~QNetworkAccessBackend(); - // To avoid mistaking with QIODevice names, the functions here - // have different names. The Connection has two streams: - // - // - Upstream: - // The upstream uses a QNonContiguousByteDevice provided - // by the backend. This device emits the usual readyRead() - // signal when the backend has data available for the connection - // to write. The different backends can listen on this signal - // and then pull upload data from the QNonContiguousByteDevice and - // deal with it. - // - // - // - Downstream: - // Downstream is the data that is being read from this - // connection and is given to the user. Downstream operates in a - // semi-"push" mechanism: the Connection object pushes the data - // it gets from the network, but it should avoid writing too - // much if the data isn't being used fast enough. The value - // returned by suggestedDownstreamBlockSize() can be used to - // determine how much should be written at a time. The - // downstreamBytesConsumed() function will be called when the - // downstream buffer is consumed by the user -- the Connection - // may choose to re-fill it with more data it has ready or get - // more data from the network (for instance, by reading from its - // socket). + SecurityFeatures securityFeatures() const noexcept; + TargetTypes targetTypes() const noexcept; + IOFeatures ioFeatures() const noexcept; - virtual void open() = 0; - virtual bool start(); - virtual void closeDownstreamChannel() = 0; + inline bool needsResetableUploadData() const noexcept + { + return ioFeatures() & IOFeature::NeedResetableUpload; + } - // slot-like: - virtual void downstreamReadyWrite(); - virtual void setDownstreamLimited(bool b); - virtual void copyFinished(QIODevice *); + virtual bool start(); + virtual void open() = 0; + virtual void close() = 0; +#if QT_CONFIG(ssl) + virtual void setSslConfiguration(const QSslConfiguration &configuration); + virtual QSslConfiguration sslConfiguration() const; +#endif virtual void ignoreSslErrors(); virtual void ignoreSslErrors(const QList<QSslError> &errors); + virtual qint64 bytesAvailable() const = 0; + virtual QByteArrayView readPointer(); + virtual void advanceReadPointer(qint64 distance); + virtual qint64 read(char *data, qint64 maxlen); + virtual bool wantToRead(); - virtual void fetchSslConfiguration(QSslConfiguration &configuration) const; - virtual void setSslConfiguration(const QSslConfiguration &configuration); - - virtual QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const; - - // information about the request - QNetworkAccessManager::Operation operation() const; - QNetworkRequest request() const; -#ifndef QT_NO_NETWORKPROXY +#if QT_CONFIG(networkproxy) QList<QNetworkProxy> proxyList() const; #endif - - QAbstractNetworkCache *networkCache() const; - void setCachingEnabled(bool enable); - bool isCachingEnabled() const; - - // information about the reply QUrl url() const; void setUrl(const QUrl &url); - - // "cooked" headers QVariant header(QNetworkRequest::KnownHeaders header) const; void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); + QByteArray rawHeader(const QByteArray &header) const; + void setRawHeader(const QByteArray &header, const QByteArray &value); + QNetworkAccessManager::Operation operation() const; - // raw headers: - bool hasRawHeader(const QByteArray &headerName) const; - QList<QByteArray> rawHeaderList() const; - QByteArray rawHeader(const QByteArray &headerName) const; - void setRawHeader(const QByteArray &headerName, const QByteArray &value); - - // attributes: - QVariant attribute(QNetworkRequest::Attribute code) const; - void setAttribute(QNetworkRequest::Attribute code, const QVariant &value); - - bool isSynchronous() { return synchronous; } - void setSynchronous(bool sync) { synchronous = sync; } - - // return true if the QNonContiguousByteDevice of the upload - // data needs to support reset(). Currently needed for HTTP. - // This will possibly enable buffering of the upload data. - virtual bool needsResetableUploadData() { return false; } - - // Returns \c true if backend is able to resume downloads. - virtual bool canResume() const { return false; } - virtual void setResumeOffset(quint64 offset) { Q_UNUSED(offset); } - - virtual bool processRequestSynchronously() { return false; } - -protected: - // Create the device used for reading the upload data - QNonContiguousByteDevice* createUploadByteDevice(); + bool isCachingEnabled() const; + void setCachingEnabled(bool canCache); - // these functions control the downstream mechanism - // that is, data that has come via the connection and is going out the backend - qint64 nextDownstreamBlockSize() const; - void writeDownstreamData(QByteDataBuffer &list); + void setAttribute(QNetworkRequest::Attribute attribute, const QVariant &value); - // not actually appending data, it was already written to the user buffer - void writeDownstreamDataDownloadBuffer(qint64, qint64); - char* getDownloadBuffer(qint64); + QIODevice *createUploadByteDevice(); + QIODevice *uploadByteDevice(); - QSharedPointer<QNonContiguousByteDevice> uploadByteDevice; + QAbstractNetworkCache *networkCache() const; public slots: - // for task 251801, needs to be a slot to be called asynchronously - void writeDownstreamData(QIODevice *data); - + void readyRead(); protected slots: void finished(); void error(QNetworkReply::NetworkError code, const QString &errorString); @@ -196,26 +151,21 @@ protected slots: void authenticationRequired(QAuthenticator *auth); void metaDataChanged(); void redirectionRequested(const QUrl &destination); - void encrypted(); - void sslErrors(const QList<QSslError> &errors); - void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal); - -protected: - // FIXME In the long run we should get rid of our QNAM architecture - // and scrap this ReplyImpl/Backend distinction. - QNetworkAccessManagerPrivate *manager; - QNetworkReplyImplPrivate *reply; private: - friend class QNetworkAccessManager; - friend class QNetworkAccessManagerPrivate; - friend class QNetworkReplyImplPrivate; - - bool synchronous; + void setReplyPrivate(QNetworkReplyImplPrivate *reply); + void setManagerPrivate(QNetworkAccessManagerPrivate *manager); + bool isSynchronous() const; + void setSynchronous(bool synchronous); + + friend class QNetworkAccessManager; // for setReplyPrivate + friend class QNetworkAccessManagerPrivate; // for setManagerPrivate + friend class QNetworkReplyImplPrivate; // for {set,is}Synchronous() }; -class QNetworkAccessBackendFactory +class Q_NETWORK_EXPORT QNetworkAccessBackendFactory : public QObject { + Q_OBJECT public: QNetworkAccessBackendFactory(); virtual ~QNetworkAccessBackendFactory(); @@ -224,7 +174,8 @@ public: const QNetworkRequest &request) const = 0; }; -QT_END_NAMESPACE +#define QNetworkAccessBackendFactory_iid "org.qt-project.Qt.NetworkAccessBackendFactory" +Q_DECLARE_INTERFACE(QNetworkAccessBackendFactory, QNetworkAccessBackendFactory_iid); +QT_END_NAMESPACE #endif - diff --git a/src/network/access/qnetworkaccesscachebackend.cpp b/src/network/access/qnetworkaccesscachebackend.cpp index 1a4ef27dd6..9ed951751a 100644 --- a/src/network/access/qnetworkaccesscachebackend.cpp +++ b/src/network/access/qnetworkaccesscachebackend.cpp @@ -48,7 +48,7 @@ QT_BEGIN_NAMESPACE QNetworkAccessCacheBackend::QNetworkAccessCacheBackend() - : QNetworkAccessBackend() + : QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Local) { } @@ -107,11 +107,11 @@ bool QNetworkAccessCacheBackend::sendCacheContents() metaDataChanged(); if (operation() == QNetworkAccessManager::GetOperation) { - QIODevice *contents = nc->data(url()); - if (!contents) + device = nc->data(url()); + if (!device) return false; - contents->setParent(this); - writeDownstreamData(contents); + device->setParent(this); + readyRead(); } #if defined(QNETWORKACCESSCACHEBACKEND_DEBUG) @@ -126,23 +126,16 @@ bool QNetworkAccessCacheBackend::start() return true; } -void QNetworkAccessCacheBackend::closeDownstreamChannel() -{ -} - -void QNetworkAccessCacheBackend::closeUpstreamChannel() -{ - Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); -} +void QNetworkAccessCacheBackend::close() { } -void QNetworkAccessCacheBackend::upstreamReadyRead() +qint64 QNetworkAccessCacheBackend::bytesAvailable() const { - Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); + return device ? device->bytesAvailable() : qint64(0); } -void QNetworkAccessCacheBackend::downstreamReadyWrite() +qint64 QNetworkAccessCacheBackend::read(char *data, qint64 maxlen) { - Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); + return device ? device->read(data, maxlen) : qint64(0); } QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccesscachebackend_p.h b/src/network/access/qnetworkaccesscachebackend_p.h index ceb02946dc..fc26dda454 100644 --- a/src/network/access/qnetworkaccesscachebackend_p.h +++ b/src/network/access/qnetworkaccesscachebackend_p.h @@ -66,16 +66,15 @@ public: ~QNetworkAccessCacheBackend(); void open() override; - void closeDownstreamChannel() override; - void closeUpstreamChannel(); + void close() override; bool start() override; - - void upstreamReadyRead(); - void downstreamReadyWrite() override; + qint64 bytesAvailable() const override; + qint64 read(char *data, qint64 maxlen) override; private: bool sendCacheContents(); + QIODevice *device = nullptr; }; QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessdebugpipebackend.cpp b/src/network/access/qnetworkaccessdebugpipebackend.cpp index 0029df41fe..51c13b7241 100644 --- a/src/network/access/qnetworkaccessdebugpipebackend.cpp +++ b/src/network/access/qnetworkaccessdebugpipebackend.cpp @@ -80,8 +80,13 @@ QNetworkAccessDebugPipeBackendFactory::create(QNetworkAccessManager::Operation o } QNetworkAccessDebugPipeBackend::QNetworkAccessDebugPipeBackend() - : bareProtocol(false), hasUploadFinished(false), hasDownloadFinished(false), - hasEverythingFinished(false), bytesDownloaded(0), bytesUploaded(0) + : QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Networked), + bareProtocol(false), + hasUploadFinished(false), + hasDownloadFinished(false), + hasEverythingFinished(false), + bytesDownloaded(0), + bytesUploaded(0) { } @@ -108,65 +113,46 @@ void QNetworkAccessDebugPipeBackend::open() if (operation() == QNetworkAccessManager::PutOperation) { createUploadByteDevice(); - QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); + QObject::connect(uploadByteDevice(), SIGNAL(readyRead()), this, + SLOT(uploadReadyReadSlot())); QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection); } } void QNetworkAccessDebugPipeBackend::socketReadyRead() { - pushFromSocketToDownstream(); + readyRead(); } -void QNetworkAccessDebugPipeBackend::downstreamReadyWrite() +qint64 QNetworkAccessDebugPipeBackend::read(char *data, qint64 maxlen) { - pushFromSocketToDownstream(); + qint64 haveRead = socket.read(data, maxlen); + + if (haveRead == -1) { + hasDownloadFinished = true; + // this ensures a good last downloadProgress is emitted + setHeader(QNetworkRequest::ContentLengthHeader, QVariant()); + possiblyFinish(); + return haveRead; + } + + bytesDownloaded += haveRead; + return haveRead; } -void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64) +qint64 QNetworkAccessDebugPipeBackend::bytesAvailable() const { - pushFromUpstreamToSocket(); + return socket.bytesAvailable(); } -void QNetworkAccessDebugPipeBackend::uploadReadyReadSlot() +void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64) { pushFromUpstreamToSocket(); } -void QNetworkAccessDebugPipeBackend::pushFromSocketToDownstream() +void QNetworkAccessDebugPipeBackend::uploadReadyReadSlot() { - QByteArray buffer; - - if (socket.state() == QAbstractSocket::ConnectingState) { - return; - } - - forever { - if (hasDownloadFinished) - return; - - buffer.resize(ReadBufferSize); - qint64 haveRead = socket.read(buffer.data(), ReadBufferSize); - - if (haveRead == -1) { - hasDownloadFinished = true; - // this ensures a good last downloadProgress is emitted - setHeader(QNetworkRequest::ContentLengthHeader, QVariant()); - possiblyFinish(); - break; - } else if (haveRead == 0) { - break; - } else { - // have read something - buffer.resize(haveRead); - bytesDownloaded += haveRead; - - QByteDataBuffer list; - list.append(buffer); - buffer.clear(); // important because of implicit sharing! - writeDownstreamData(list); - } - } + pushFromUpstreamToSocket(); } void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket() @@ -180,20 +166,20 @@ void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket() if (socket.bytesToWrite() >= WriteBufferSize) return; - qint64 haveRead; - const char *readPointer = uploadByteDevice->readPointer(WriteBufferSize, haveRead); + QByteArray data(WriteBufferSize, Qt::Uninitialized); + qint64 haveRead = uploadByteDevice()->peek(data.data(), data.size()); if (haveRead == -1) { // EOF hasUploadFinished = true; - emitReplyUploadProgress(bytesUploaded, bytesUploaded); possiblyFinish(); break; - } else if (haveRead == 0 || readPointer == nullptr) { + } else if (haveRead == 0) { // nothing to read right now, we will be called again later break; } else { qint64 haveWritten; - haveWritten = socket.write(readPointer, haveRead); + data.truncate(haveRead); + haveWritten = socket.write(std::move(data)); if (haveWritten < 0) { // write error! @@ -203,13 +189,11 @@ void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket() finished(); return; } else { - uploadByteDevice->advanceReadPointer(haveWritten); + uploadByteDevice()->skip(haveWritten); bytesUploaded += haveWritten; - emitReplyUploadProgress(bytesUploaded, -1); } //QCoreApplication::processEvents(); - } } } @@ -232,7 +216,7 @@ void QNetworkAccessDebugPipeBackend::possiblyFinish() } -void QNetworkAccessDebugPipeBackend::closeDownstreamChannel() +void QNetworkAccessDebugPipeBackend::close() { qWarning("QNetworkAccessDebugPipeBackend::closeDownstreamChannel() %d",operation());; //if (operation() == QNetworkAccessManager::GetOperation) @@ -266,11 +250,10 @@ void QNetworkAccessDebugPipeBackend::socketError() void QNetworkAccessDebugPipeBackend::socketDisconnected() { - pushFromSocketToDownstream(); - if (socket.bytesToWrite() == 0) { // normal close } else { + readyRead(); // @todo this is odd // abnormal close QString msg = QNetworkAccessDebugPipeBackend::tr("Remote host closed the connection prematurely on %1") .arg(url().toString()); diff --git a/src/network/access/qnetworkaccessdebugpipebackend_p.h b/src/network/access/qnetworkaccessdebugpipebackend_p.h index 761c7055b8..50ef5cea7d 100644 --- a/src/network/access/qnetworkaccessdebugpipebackend_p.h +++ b/src/network/access/qnetworkaccessdebugpipebackend_p.h @@ -68,13 +68,13 @@ public: QNetworkAccessDebugPipeBackend(); virtual ~QNetworkAccessDebugPipeBackend(); - virtual void open() override; - virtual void closeDownstreamChannel() override; + void open() override; + void close() override; - virtual void downstreamReadyWrite() override; + qint64 read(char *data, qint64 maxlen) override; + qint64 bytesAvailable() const override; protected: - void pushFromSocketToDownstream(); void pushFromUpstreamToSocket(); void possiblyFinish(); diff --git a/src/network/access/qnetworkaccessfilebackend.cpp b/src/network/access/qnetworkaccessfilebackend.cpp index 046a16162a..c5f1826203 100644 --- a/src/network/access/qnetworkaccessfilebackend.cpp +++ b/src/network/access/qnetworkaccessfilebackend.cpp @@ -95,8 +95,12 @@ QNetworkAccessFileBackendFactory::create(QNetworkAccessManager::Operation op, return nullptr; } +// We pass TargetType::Local even though it's kind of Networked but we're using a QFile to access +// the resource so it cannot use proxies anyway QNetworkAccessFileBackend::QNetworkAccessFileBackend() - : totalBytes(0), hasUploadFinished(false) + : QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Local), + totalBytes(0), + hasUploadFinished(false) { } @@ -152,7 +156,7 @@ void QNetworkAccessFileBackend::open() case QNetworkAccessManager::PutOperation: mode = QIODevice::WriteOnly | QIODevice::Truncate; createUploadByteDevice(); - QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); + QObject::connect(uploadByteDevice(), SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot())); QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection); break; default: @@ -163,6 +167,8 @@ void QNetworkAccessFileBackend::open() mode |= QIODevice::Unbuffered; bool opened = file.open(mode); + if (file.isSequential()) + connect(&file, &QIODevice::readChannelFinished, this, [this]() { finished(); }); // could we open the file? if (!opened) { @@ -186,8 +192,8 @@ void QNetworkAccessFileBackend::uploadReadyReadSlot() return; forever { - qint64 haveRead; - const char *readPointer = uploadByteDevice->readPointer(-1, haveRead); + QByteArray data(16 * 1024, Qt::Uninitialized); + qint64 haveRead = uploadByteDevice()->peek(data.data(), data.size()); if (haveRead == -1) { // EOF hasUploadFinished = true; @@ -195,12 +201,13 @@ void QNetworkAccessFileBackend::uploadReadyReadSlot() file.close(); finished(); break; - } else if (haveRead == 0 || readPointer == nullptr) { + } else if (haveRead == 0) { // nothing to read right now, we will be called again later break; } else { qint64 haveWritten; - haveWritten = file.write(readPointer, haveRead); + data.truncate(haveRead); + haveWritten = file.write(data); if (haveWritten < 0) { // write error! @@ -211,7 +218,7 @@ void QNetworkAccessFileBackend::uploadReadyReadSlot() finished(); return; } else { - uploadByteDevice->advanceReadPointer(haveWritten); + uploadByteDevice()->skip(haveWritten); } @@ -220,21 +227,13 @@ void QNetworkAccessFileBackend::uploadReadyReadSlot() } } -void QNetworkAccessFileBackend::closeDownstreamChannel() +void QNetworkAccessFileBackend::close() { if (operation() == QNetworkAccessManager::GetOperation) { file.close(); } } -void QNetworkAccessFileBackend::downstreamReadyWrite() -{ - Q_ASSERT_X(operation() == QNetworkAccessManager::GetOperation, "QNetworkAccessFileBackend", - "We're being told to download data but operation isn't GET!"); - - readMoreFromFile(); -} - bool QNetworkAccessFileBackend::loadFileInfo() { QFileInfo fi(file); @@ -254,40 +253,36 @@ bool QNetworkAccessFileBackend::loadFileInfo() return true; } -bool QNetworkAccessFileBackend::readMoreFromFile() +qint64 QNetworkAccessFileBackend::bytesAvailable() const { - qint64 wantToRead; - while ((wantToRead = nextDownstreamBlockSize()) > 0) { - // ### FIXME!! - // Obtain a pointer from the ringbuffer! - // Avoid extra copy - QByteArray data; - data.reserve(wantToRead); - qint64 actuallyRead = file.read(data.data(), wantToRead); - if (actuallyRead <= 0) { - // EOF or error - if (file.error() != QFile::NoError) { - QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Read error reading from %1: %2") - .arg(url().toString(), file.errorString()); - error(QNetworkReply::ProtocolFailure, msg); + if (operation() != QNetworkAccessManager::GetOperation) + return 0; + return file.bytesAvailable(); +} - finished(); - return false; - } +qint64 QNetworkAccessFileBackend::read(char *data, qint64 maxlen) +{ + if (operation() != QNetworkAccessManager::GetOperation) + return 0; + qint64 actuallyRead = file.read(data, maxlen); + if (actuallyRead <= 0) { + // EOF or error + if (file.error() != QFile::NoError) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Read error reading from %1: %2") + .arg(url().toString(), file.errorString()); + error(QNetworkReply::ProtocolFailure, msg); finished(); - return true; + return -1; } - data.resize(actuallyRead); - totalBytes += actuallyRead; - - QByteDataBuffer list; - list.append(data); - data.clear(); // important because of implicit sharing! - writeDownstreamData(list); + finished(); + return actuallyRead; } - return true; + if (!file.isSequential() && file.atEnd()) + finished(); + totalBytes += actuallyRead; + return actuallyRead; } QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessfilebackend_p.h b/src/network/access/qnetworkaccessfilebackend_p.h index 2204958ee0..32b2102abc 100644 --- a/src/network/access/qnetworkaccessfilebackend_p.h +++ b/src/network/access/qnetworkaccessfilebackend_p.h @@ -66,10 +66,11 @@ public: QNetworkAccessFileBackend(); virtual ~QNetworkAccessFileBackend(); - virtual void open() override; - virtual void closeDownstreamChannel() override; + void open() override; + void close() override; - virtual void downstreamReadyWrite() override; + qint64 bytesAvailable() const; + qint64 read(char *data, qint64 maxlen); public slots: void uploadReadyReadSlot(); @@ -79,7 +80,6 @@ private: bool hasUploadFinished; bool loadFileInfo(); - bool readMoreFromFile(); }; class QNetworkAccessFileBackendFactory: public QNetworkAccessBackendFactory diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index b56da732e2..99c929d6a2 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -60,6 +60,9 @@ #include "qnetworkreplydataimpl_p.h" #include "qnetworkreplyfileimpl_p.h" +#include "qnetworkaccessbackend_p.h" +#include "qnetworkreplyimpl_p.h" + #include "QtCore/qbuffer.h" #include "QtCore/qlist.h" #include "QtCore/qurl.h" @@ -77,6 +80,8 @@ #include <QHostInfo> +#include <QtCore/private/qfactoryloader_p.h> + #if defined(Q_OS_MACOS) #include <CoreServices/CoreServices.h> #include <SystemConfiguration/SystemConfiguration.h> @@ -96,6 +101,9 @@ Q_GLOBAL_STATIC(QNetworkAccessFileBackendFactory, fileBackend) Q_GLOBAL_STATIC(QNetworkAccessDebugPipeBackendFactory, debugpipeBackend) #endif +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QNetworkAccessBackendFactory_iid, + QLatin1String("/networkaccessbackends"))) #if defined(Q_OS_MACOS) bool getProxyAuth(const QString& proxyHostname, const QString &scheme, QString& username, QString& password) { @@ -396,6 +404,7 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent) : QObject(*new QNetworkAccessManagerPrivate, parent) { ensureInitialized(); + d_func()->ensureBackendPluginsLoaded(); qRegisterMetaType<QNetworkReply::NetworkError>(); #ifndef QT_NO_NETWORKPROXY @@ -1171,9 +1180,9 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera QNetworkReplyImplPrivate *priv = reply->d_func(); priv->manager = this; priv->backend = new QNetworkAccessCacheBackend(); - priv->backend->manager = this->d_func(); + priv->backend->setManagerPrivate(this->d_func()); priv->backend->setParent(reply); - priv->backend->reply = priv; + priv->backend->setReplyPrivate(priv); priv->setup(op, req, outgoingData); return reply; } @@ -1251,7 +1260,7 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera if (priv->backend) { priv->backend->setParent(reply); - priv->backend->reply = priv; + priv->backend->setReplyPrivate(priv); } #ifndef QT_NO_SSL @@ -1685,6 +1694,25 @@ QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkReq } #endif // QT_CONFIG(http) +/*! + \internal + Go through the instances so the factories will be created and + register themselves to QNetworkAccessBackendFactoryData +*/ +void QNetworkAccessManagerPrivate::ensureBackendPluginsLoaded() +{ + static QBasicMutex mutex; + std::unique_lock locker(mutex); + if (!loader()) + return; +#if QT_CONFIG(library) + loader->update(); +#endif + int index = 0; + while (loader->instance(index)) + ++index; +} + QT_END_NAMESPACE #include "moc_qnetworkaccessmanager.cpp" diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h index da9f6fd0bd..5433876385 100644 --- a/src/network/access/qnetworkaccessmanager_p.h +++ b/src/network/access/qnetworkaccessmanager_p.h @@ -130,6 +130,8 @@ public: QNetworkRequest prepareMultipart(const QNetworkRequest &request, QHttpMultiPart *multiPart); #endif + void ensureBackendPluginsLoaded(); + // this is the cache for storing downloaded files QAbstractNetworkCache *networkCache; @@ -153,8 +155,6 @@ public: // this cache can be used by individual backends to cache e.g. their TCP connections to a server // and use the connections for multiple requests. QNetworkAccessCache objectCache; - static inline QNetworkAccessCache *getObjectCache(QNetworkAccessBackend *backend) - { return &backend->manager->objectCache; } Q_AUTOTEST_EXPORT static void clearAuthenticationCache(QNetworkAccessManager *manager); Q_AUTOTEST_EXPORT static void clearConnectionCache(QNetworkAccessManager *manager); diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 88969c6483..bdb81ee0b1 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -61,6 +61,8 @@ #include "qnetworkcookiejar.h" #include "qnetconmonitor_p.h" +#include "qnetworkreplyimpl_p.h" + #include <string.h> // for strchr QT_BEGIN_NAMESPACE diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp index 400cfff8d3..61745155f5 100644 --- a/src/network/access/qnetworkreplyimpl.cpp +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -56,7 +56,7 @@ inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate() copyDevice(nullptr), cacheEnabled(false), cacheSaveDevice(nullptr), notificationHandlingPaused(false), - bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), + bytesDownloaded(0), bytesUploaded(-1), httpStatusCode(0), state(Idle) , downloadBufferReadPosition(0) @@ -124,7 +124,7 @@ void QNetworkReplyImplPrivate::_q_copyReadyRead() // FIXME Optimize to use download buffer if it is a QBuffer. // Needs to be done where sendCacheContents() (?) of HTTP is emitting // metaDataChanged ? - + qint64 lastBytesDownloaded = bytesDownloaded; forever { qint64 bytesToRead = nextDownstreamBlockSize(); if (bytesToRead == 0) @@ -135,13 +135,11 @@ void QNetworkReplyImplPrivate::_q_copyReadyRead() qint64 bytesActuallyRead = copyDevice->read(buffer.reserve(bytesToRead), bytesToRead); if (bytesActuallyRead == -1) { buffer.chop(bytesToRead); - backendNotify(NotifyCopyFinished); break; } buffer.chop(bytesToRead - bytesActuallyRead); if (!copyDevice->isSequential() && copyDevice->atEnd()) { - backendNotify(NotifyCopyFinished); bytesDownloaded += bytesActuallyRead; break; } @@ -154,7 +152,6 @@ void QNetworkReplyImplPrivate::_q_copyReadyRead() return; } - lastBytesDownloaded = bytesDownloaded; QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); pauseNotificationHandling(); // emit readyRead before downloadProgress incase this will cause events to be @@ -323,22 +320,16 @@ void QNetworkReplyImplPrivate::handleNotifications() return; switch (notification) { case NotifyDownstreamReadyWrite: - if (copyDevice) + if (copyDevice) { _q_copyReadyRead(); - else - backend->downstreamReadyWrite(); - break; - - case NotifyCloseDownstreamChannel: - backend->closeDownstreamChannel(); - break; - - case NotifyCopyFinished: { - QIODevice *dev = qExchange(copyDevice, nullptr); - backend->copyFinished(dev); + } else if (backend) { + if (backend->bytesAvailable() > 0) + readFromBackend(); + else if (backend->wantToRead()) + readFromBackend(); + } break; } - } } } @@ -463,7 +454,8 @@ void QNetworkReplyImplPrivate::initCacheSaveDevice() // save the meta data QNetworkCacheMetaData metaData; metaData.setUrl(url); - metaData = backend->fetchCacheMetaData(metaData); + // @todo @future: fetchCacheMetaData is not currently implemented in any backend, but can be useful again in the future + // metaData = backend->fetchCacheMetaData(metaData); // save the redirect request also in the cache QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute); @@ -512,7 +504,6 @@ void QNetworkReplyImplPrivate::appendDownstreamData(QByteDataBuffer &data) data.clear(); bytesDownloaded += bytesWritten; - lastBytesDownloaded = bytesDownloaded; appendDownstreamDataSignalEmissions(); } @@ -562,16 +553,6 @@ void QNetworkReplyImplPrivate::appendDownstreamData(QIODevice *data) _q_copyReadyRead(); } -void QNetworkReplyImplPrivate::appendDownstreamData(const QByteArray &data) -{ - Q_UNUSED(data); - // TODO implement - - // TODO call - - qFatal("QNetworkReplyImplPrivate::appendDownstreamData not implemented"); -} - static void downloadBufferDeleter(char *ptr) { delete[] ptr; @@ -620,16 +601,11 @@ void QNetworkReplyImplPrivate::appendDownstreamDataDownloadBuffer(qint64 bytesRe initCacheSaveDevice(); if (cacheSaveDevice && bytesReceived == bytesTotal) { -// if (lastBytesDownloaded == -1) -// lastBytesDownloaded = 0; -// cacheSaveDevice->write(downloadBuffer + lastBytesDownloaded, bytesReceived - lastBytesDownloaded); - // Write everything in one go if we use a download buffer. might be more performant. cacheSaveDevice->write(downloadBuffer, bytesTotal); } bytesDownloaded = bytesReceived; - lastBytesDownloaded = bytesReceived; downloadBufferCurrentSize = bytesReceived; @@ -748,6 +724,30 @@ void QNetworkReplyImplPrivate::sslErrors(const QList<QSslError> &errors) #endif } +void QNetworkReplyImplPrivate::readFromBackend() +{ + Q_Q(QNetworkReplyImpl); + if (!backend) + return; + + if (backend->ioFeatures() & QNetworkAccessBackend::IOFeature::ZeroCopy) { + if (backend->bytesAvailable()) + emit q->readyRead(); + } else { + while (backend->bytesAvailable() + && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { + qint64 toRead = qMin(nextDownstreamBlockSize(), backend->bytesAvailable()); + if (toRead == 0) + toRead = 16 * 1024; // try to read something + char *data = buffer.reserve(toRead); + qint64 bytesRead = backend->read(data, toRead); + Q_ASSERT(bytesRead <= toRead); + buffer.chop(toRead - bytesRead); + emit q->readyRead(); + } + } +} + QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent) : QNetworkReply(*new QNetworkReplyImplPrivate, parent) { @@ -799,7 +799,7 @@ void QNetworkReplyImpl::close() // stop the download if (d->backend) - d->backend->closeDownstreamChannel(); + d->backend->close(); if (d->copyDevice) disconnect(d->copyDevice, nullptr, this, nullptr); @@ -823,21 +823,16 @@ qint64 QNetworkReplyImpl::bytesAvailable() const qint64 maxAvail = d->downloadBufferCurrentSize - d->downloadBufferReadPosition; return QNetworkReply::bytesAvailable() + maxAvail; } - - return QNetworkReply::bytesAvailable(); + return QNetworkReply::bytesAvailable() + (d->backend ? d->backend->bytesAvailable() : 0); } void QNetworkReplyImpl::setReadBufferSize(qint64 size) { Q_D(QNetworkReplyImpl); - if (size > d->readBufferMaxSize && - size > d->buffer.size()) - d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); - + qint64 oldMaxSize = d->readBufferMaxSize; QNetworkReply::setReadBufferSize(size); - - if (d->backend) - d->backend->setDownstreamLimited(d->readBufferMaxSize > 0); + if (size > oldMaxSize && size > d->buffer.size()) + d->readFromBackend(); } #ifndef QT_NO_SSL @@ -845,7 +840,7 @@ void QNetworkReplyImpl::sslConfigurationImplementation(QSslConfiguration &config { Q_D(const QNetworkReplyImpl); if (d->backend) - d->backend->fetchSslConfiguration(configuration); + configuration = d->backend->sslConfiguration(); } void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config) @@ -877,6 +872,35 @@ qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen) { Q_D(QNetworkReplyImpl); + if (d->backend + && d->backend->ioFeatures().testFlag(QNetworkAccessBackend::IOFeature::ZeroCopy)) { + qint64 bytesRead = 0; + while (d->backend->bytesAvailable()) { + QByteArrayView view = d->backend->readPointer(); + if (view.size()) { + qint64 bytesToCopy = qMin(qint64(view.size()), maxlen - bytesRead); + memcpy(data + bytesRead, view.data(), bytesToCopy); // from zero to one copy + + // We might have to cache this + if (d->cacheEnabled && !d->cacheSaveDevice) + d->initCacheSaveDevice(); + if (d->cacheEnabled && d->cacheSaveDevice) + d->cacheSaveDevice->write(view.data(), view.size()); + + bytesRead += bytesToCopy; + d->backend->advanceReadPointer(bytesToCopy); + } else { + break; + } + } + QVariant totalSize = d->cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + emit downloadProgress(bytesRead, + totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + return bytesRead; + } else if (d->backend && d->backend->bytesAvailable()) { + return d->backend->read(data, maxlen); + } + // Special case code if we have the "zero copy" download buffer if (d->downloadBuffer) { qint64 maxAvail = qMin<qint64>(d->downloadBufferCurrentSize - d->downloadBufferReadPosition, maxlen); diff --git a/src/network/access/qnetworkreplyimpl_p.h b/src/network/access/qnetworkreplyimpl_p.h index bb5afe49d6..7d014f9173 100644 --- a/src/network/access/qnetworkreplyimpl_p.h +++ b/src/network/access/qnetworkreplyimpl_p.h @@ -106,8 +106,6 @@ class QNetworkReplyImplPrivate: public QNetworkReplyPrivate public: enum InternalNotifications { NotifyDownstreamReadyWrite, - NotifyCloseDownstreamChannel, - NotifyCopyFinished }; QNetworkReplyImplPrivate(); @@ -139,7 +137,6 @@ public: void appendDownstreamDataSignalEmissions(); void appendDownstreamData(QByteDataBuffer &data); void appendDownstreamData(QIODevice *data); - void appendDownstreamData(const QByteArray &data); void setDownloadBuffer(QSharedPointer<char> sp, qint64 size); char* getDownloadBuffer(qint64 size); @@ -152,6 +149,8 @@ public: void encrypted(); void sslErrors(const QList<QSslError> &errors); + void readFromBackend(); + QNetworkAccessBackend *backend; QIODevice *outgoingData; QSharedPointer<QRingBuffer> outgoingDataBuffer; @@ -171,7 +170,6 @@ public: #endif qint64 bytesDownloaded; - qint64 lastBytesDownloaded; qint64 bytesUploaded; QString httpReasonPhrase; diff --git a/src/network/network.pro b/src/network/network.pro index b1bfc51f90..b9fbd3427f 100644 --- a/src/network/network.pro +++ b/src/network/network.pro @@ -14,6 +14,9 @@ msvc:equals(QT_ARCH, i386): QMAKE_LFLAGS += /BASE:0x64000000 QMAKE_DOCS = $$PWD/doc/qtnetwork.qdocconf +MODULE_PLUGIN_TYPES = \ + networkaccessbackends + include(access/access.pri) include(kernel/kernel.pri) include(socket/socket.pri) |