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 #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() +class QNetworkAccessBackendPrivate : public QObjectPrivate { - QMutexLocker locker(&factoryData()->mutex); - factoryData()->append(this); -} +public: + QNetworkAccessBackend::TargetTypes m_targetTypes; + QNetworkAccessBackend::SecurityFeatures m_securityFeatures; + QNetworkAccessBackend::IOFeatures m_ioFeatures; + QSharedPointer uploadByteDevice; + QIODevice *wrappedUploadByteDevice; + QNetworkReplyImplPrivate *m_reply = nullptr; + QNetworkAccessManagerPrivate *m_manager = nullptr; -QNetworkAccessBackendFactory::~QNetworkAccessBackendFactory() -{ - if (QNetworkAccessBackendFactoryData::valid.loadRelaxed()) { - QMutexLocker locker(&factoryData()->mutex); - factoryData()->removeAll(this); - } -} + 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,255 +125,150 @@ 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 - // We want signal emissions only for normal asynchronous uploads - if (!isSynchronous()) - connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)), this, SLOT(emitReplyUploadProgress(qint64,qint64))); + 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. - return uploadByteDevice.data(); -} + \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. +*/ -// 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) -{ - if (reply->isFinished) - return; - reply->emitUploadProgress(bytesSent, bytesTotal); -} +/*! + \enum QNetworkAccessBackend::SecurityFeature -QNetworkAccessBackend::QNetworkAccessBackend() - : manager(nullptr) - , reply(nullptr) - , synchronous(false) -{ -} + 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. -QNetworkAccessBackend::~QNetworkAccessBackend() -{ -} + \value None + No specific features are claimed to be supported. + \value TLS + The plugin supports and expects to use TLS. +*/ -void QNetworkAccessBackend::downstreamReadyWrite() -{ - // do nothing -} +/*! + \enum QNetworkAccessBackend::IOFeature -void QNetworkAccessBackend::setDownstreamLimited(bool b) -{ - Q_UNUSED(b); - // do nothing -} + Use the values in this enum to specify what type of IO + features the plugin may utilize. -void QNetworkAccessBackend::copyFinished(QIODevice *) -{ - // do nothing -} + \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 +*/ -void QNetworkAccessBackend::ignoreSslErrors() -{ - // do nothing -} +/*! + 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. -void QNetworkAccessBackend::ignoreSslErrors(const QList &errors) + \sa TargetType, SecurityFeature, IOFeature +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes, + SecurityFeatures securityFeatures, + IOFeatures ioFeatures) + : QObject(*(new QNetworkAccessBackendPrivate), nullptr) { - Q_UNUSED(errors); - // do nothing -} - -void QNetworkAccessBackend::fetchSslConfiguration(QSslConfiguration &) const -{ - // do nothing -} - -void QNetworkAccessBackend::setSslConfiguration(const QSslConfiguration &) -{ - // do nothing -} - -QNetworkCacheMetaData QNetworkAccessBackend::fetchCacheMetaData(const QNetworkCacheMetaData &) const -{ - return QNetworkCacheMetaData(); -} - -QNetworkAccessManager::Operation QNetworkAccessBackend::operation() const -{ - return reply->operation; -} - -QNetworkRequest QNetworkAccessBackend::request() const -{ - return reply->request; -} - -#ifndef QT_NO_NETWORKPROXY -QList QNetworkAccessBackend::proxyList() const -{ - return reply->proxyList; -} -#endif - -QAbstractNetworkCache *QNetworkAccessBackend::networkCache() const -{ - if (!manager) - return nullptr; - return manager->networkCache; -} - -void QNetworkAccessBackend::setCachingEnabled(bool enable) -{ - reply->setCachingEnabled(enable); -} - -bool QNetworkAccessBackend::isCachingEnabled() const -{ - return reply->isCachingEnabled(); -} - -qint64 QNetworkAccessBackend::nextDownstreamBlockSize() const -{ - return reply->nextDownstreamBlockSize(); -} - -void QNetworkAccessBackend::writeDownstreamData(QByteDataBuffer &list) -{ - reply->appendDownstreamData(list); -} - -void QNetworkAccessBackend::writeDownstreamData(QIODevice *data) -{ - reply->appendDownstreamData(data); -} - -// not actually appending data, it was already written to the user buffer -void QNetworkAccessBackend::writeDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal) -{ - reply->appendDownstreamDataDownloadBuffer(bytesReceived, bytesTotal); -} - -char* QNetworkAccessBackend::getDownloadBuffer(qint64 size) -{ - return reply->getDownloadBuffer(size); -} - -QVariant QNetworkAccessBackend::header(QNetworkRequest::KnownHeaders header) const -{ - return reply->q_func()->header(header); -} - -void QNetworkAccessBackend::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) -{ - reply->setCookedHeader(header, value); -} - -bool QNetworkAccessBackend::hasRawHeader(const QByteArray &headerName) const -{ - return reply->q_func()->hasRawHeader(headerName); -} - -QByteArray QNetworkAccessBackend::rawHeader(const QByteArray &headerName) const -{ - return reply->q_func()->rawHeader(headerName); -} - -QList QNetworkAccessBackend::rawHeaderList() const -{ - return reply->q_func()->rawHeaderList(); -} - -void QNetworkAccessBackend::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue) -{ - reply->setRawHeader(headerName, headerValue); -} - -QVariant QNetworkAccessBackend::attribute(QNetworkRequest::Attribute code) const -{ - return reply->q_func()->attribute(code); -} - -void QNetworkAccessBackend::setAttribute(QNetworkRequest::Attribute code, const QVariant &value) -{ - if (value.isValid()) - reply->attributes.insert(code, value); - else - reply->attributes.remove(code); -} -QUrl QNetworkAccessBackend::url() const -{ - return reply->url; -} - -void QNetworkAccessBackend::setUrl(const QUrl &url) -{ - reply->url = url; -} - -void QNetworkAccessBackend::finished() -{ - reply->finished(); -} - -void QNetworkAccessBackend::error(QNetworkReply::NetworkError code, const QString &errorString) -{ - reply->error(code, errorString); -} - -#ifndef QT_NO_NETWORKPROXY -void QNetworkAccessBackend::proxyAuthenticationRequired(const QNetworkProxy &proxy, - QAuthenticator *authenticator) -{ - manager->proxyAuthenticationRequired(QUrl(), proxy, synchronous, authenticator, &reply->lastProxyAuthentication); -} -#endif - -void QNetworkAccessBackend::authenticationRequired(QAuthenticator *authenticator) -{ - manager->authenticationRequired(authenticator, reply->q_func(), synchronous, reply->url, &reply->urlForLastAuthentication); -} - -void QNetworkAccessBackend::metaDataChanged() -{ - reply->metaDataChanged(); -} - -void QNetworkAccessBackend::redirectionRequested(const QUrl &target) -{ - reply->redirectionRequested(target); -} - -void QNetworkAccessBackend::encrypted() -{ -#ifndef QT_NO_SSL - reply->encrypted(); -#endif -} - -void QNetworkAccessBackend::sslErrors(const QList &errors) -{ -#ifndef QT_NO_SSL - reply->sslErrors(errors); -#else - Q_UNUSED(errors); -#endif + Q_D(QNetworkAccessBackend); + d->m_targetTypes = targetTypes; + d->m_securityFeatures = securityFeatures; + d->m_ioFeatures = ioFeatures; } /*! - 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. + \overload +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes) + : QNetworkAccessBackend(targetTypes, SecurityFeature::None, IOFeature::None) +{ +} + +/*! + \overload +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes, + SecurityFeatures securityFeatures) + : QNetworkAccessBackend(targetTypes, securityFeatures, IOFeature::None) +{ +} + +/*! + \overload +*/ +QNetworkAccessBackend::QNetworkAccessBackend(TargetTypes targetTypes, IOFeatures ioFeatures) + : QNetworkAccessBackend(targetTypes, SecurityFeature::None, ioFeatures) +{ +} + +/*! + 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 +{ + return d_func()->m_securityFeatures; +} + +/*! + Returns the TargetTypes that the backend claims to target. + + \sa TargetType +*/ +QNetworkAccessBackend::TargetTypes QNetworkAccessBackend::targetTypes() const noexcept +{ + return d_func()->m_targetTypes; +} + +/*! + Returns the I/O features that the backend claims to support. + + \sa IOFeature +*/ +QNetworkAccessBackend::IOFeatures QNetworkAccessBackend::ioFeatures() const noexcept +{ + return d_func()->m_ioFeatures; +} + +/*! + Prepares the backend and calls open(). + E.g. for TargetType::Networked it will prepare proxyList(). + + \sa TargetType, targetTypes */ bool QNetworkAccessBackend::start() { + Q_D(QNetworkAccessBackend); #ifndef QT_NO_NETWORKPROXY - reply->proxyList = manager->queryProxy(QNetworkProxyQuery(url())); + if (targetTypes() & QNetworkAccessBackend::TargetType::Networked) + d->m_reply->proxyList = d->m_manager->queryProxy(QNetworkProxyQuery(url())); #endif // now start the request @@ -378,4 +276,468 @@ bool QNetworkAccessBackend::start() return true; } +#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) +{ + Q_UNUSED(configuration); + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden setSslConfiguration.", + metaObject()->className()); + } +} + +/*! + Override this and return the QSslConfiguration used if you + have the TLS security feature + + \sa SecurityFeature, securityFeatures +*/ +QSslConfiguration QNetworkAccessBackend::sslConfiguration() const +{ + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden sslConfiguration.", + metaObject()->className()); + } + return {}; +} +#endif + +/*! + 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() +{ + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden ignoreSslErrors.", + metaObject()->className()); + } +} + +/*! + 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 &errors) +{ + Q_UNUSED(errors); + if (securityFeatures() & SecurityFeature::TLS) { + qWarning("Backend (%s) claiming to use TLS hasn't overridden ignoreSslErrors.", + metaObject()->className()); + } +} + +/*! + 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() +{ + if (ioFeatures() & IOFeature::ZeroCopy) { + qWarning("Backend (%s) claiming to support ZeroCopy hasn't overridden readPointer.", + metaObject()->className()); + } + return {}; +} + +/*! + 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) +{ + Q_UNUSED(distance); + if (ioFeatures() & IOFeature::ZeroCopy) { + qWarning("Backend (%s) claiming to support ZeroCopy hasn't overridden advanceReadPointer.", + metaObject()->className()); + } +} + +/*! + 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) +{ + 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; +} + +/*! + 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() +{ + // Base implementation does nothing + return false; +} + +#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 QNetworkAccessBackend::proxyList() const +{ + Q_ASSERT(targetTypes() & TargetType::Networked); + return d_func()->m_reply->proxyList; +} +#endif + +/*! + Returns the current URL of the reply +*/ +QUrl QNetworkAccessBackend::url() const +{ + return d_func()->m_reply->url; +} + +/*! + 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) +{ + d_func()->m_reply->url = url; +} + +/*! + 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 +{ + return d_func()->m_reply->cookedHeaders.value(header); +} + +/*! + 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) +{ + d_func()->m_reply->setCookedHeader(header, value); +} + +/*! + 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 d_func()->m_reply->q_func()->rawHeader(header); +} + +/*! + 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) +{ + d_func()->m_reply->setRawHeader(header, value); +} + +/*! + Returns the operation which was requested when calling + QNetworkAccessManager. +*/ +QNetworkAccessManager::Operation QNetworkAccessBackend::operation() const +{ + return d_func()->m_reply->operation; +} + +/*! + 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 d_func()->m_canCache; +} + +/*! + 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) +{ + d_func()->m_canCache = canCache; +} + +/*! + 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) +{ + Q_D(QNetworkAccessBackend); + if (value.isValid()) + d->m_reply->attributes.insert(attribute, value); + else + d->m_reply->attributes.remove(attribute); +} + +/*! + 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() +{ + 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; +} + +/*! + 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 d_func()->wrappedUploadByteDevice; +} + +/*! + \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 +{ + return d_func()->m_isSynchronous; +} + +/*! + \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) +{ + if ((ioFeatures() & IOFeature::SupportsSynchronousMode) == 0) + return; + d_func()->m_isSynchronous = synchronous; +} + +/*! + Call this slot when you have more data available to notify + the backend that we can attempt to read again. +*/ +void QNetworkAccessBackend::readyRead() +{ + 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() +{ + 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) +{ + 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) +{ + 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) +{ + 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() +{ + d_func()->m_reply->metaDataChanged(); +} + +/*! + Call this slot if, when connecting to the resource, a redirect + to \a destination was requested. +*/ +void QNetworkAccessBackend::redirectionRequested(const QUrl &destination) +{ + d_func()->m_reply->redirectionRequested(destination); +} + +/*! + \internal +*/ +void QNetworkAccessBackend::setReplyPrivate(QNetworkReplyImplPrivate *reply) +{ + d_func()->m_reply = reply; +} + +/*! + \internal +*/ +void QNetworkAccessBackend::setManagerPrivate(QNetworkAccessManagerPrivate *manager) +{ + d_func()->m_manager = manager; +} + +/*! + Returns the network cache object that was available when the + request was started. Returns \c nullptr if none was available. +*/ +QAbstractNetworkCache *QNetworkAccessBackend::networkCache() const +{ + return d_func()->m_manager->networkCache; +} + +// -- 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 -#include -#include "qnetworkreplyimpl_p.h" -#include "QtCore/qobject.h" -Q_MOC_INCLUDE() -Q_MOC_INCLUDE() +#include +#include +#include + +#include +#include +#include + +#if QT_CONFIG(ssl) +#include +#endif QT_BEGIN_NAMESPACE -class QNetworkProxy; -class QNetworkProxyQuery; -class QNetworkRequest; -class QStringList; -class QUrl; -class QSslConfiguration; - -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; + + inline bool needsResetableUploadData() const noexcept + { + return ioFeatures() & IOFeature::NeedResetableUpload; + } - virtual void open() = 0; virtual bool start(); - virtual void closeDownstreamChannel() = 0; - - // slot-like: - virtual void downstreamReadyWrite(); - virtual void setDownstreamLimited(bool b); - virtual void copyFinished(QIODevice *); + 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 &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 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 rawHeaderList() const; - QByteArray rawHeader(const QByteArray &headerName) const; - void setRawHeader(const QByteArray &headerName, const QByteArray &value); + bool isCachingEnabled() const; + void setCachingEnabled(bool canCache); - // attributes: - QVariant attribute(QNetworkRequest::Attribute code) const; - void setAttribute(QNetworkRequest::Attribute code, const QVariant &value); + void setAttribute(QNetworkRequest::Attribute attribute, const QVariant &value); - bool isSynchronous() { return synchronous; } - void setSynchronous(bool sync) { synchronous = sync; } + QIODevice *createUploadByteDevice(); + QIODevice *uploadByteDevice(); - // 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(); - - // 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); - - // not actually appending data, it was already written to the user buffer - void writeDownstreamDataDownloadBuffer(qint64, qint64); - char* getDownloadBuffer(qint64); - - QSharedPointer 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 &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; + void setReplyPrivate(QNetworkReplyImplPrivate *reply); + void setManagerPrivate(QNetworkAccessManagerPrivate *manager); + bool isSynchronous() const; + void setSynchronous(bool synchronous); - 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; }; +#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::close() { } + +qint64 QNetworkAccessCacheBackend::bytesAvailable() const { + return device ? device->bytesAvailable() : qint64(0); } -void QNetworkAccessCacheBackend::closeUpstreamChannel() +qint64 QNetworkAccessCacheBackend::read(char *data, qint64 maxlen) { - Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); -} - -void QNetworkAccessCacheBackend::upstreamReadyRead() -{ - Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); -} - -void QNetworkAccessCacheBackend::downstreamReadyWrite() -{ - 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,19 +113,36 @@ 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; +} + +qint64 QNetworkAccessDebugPipeBackend::bytesAvailable() const +{ + return socket.bytesAvailable(); } void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64) @@ -133,42 +155,6 @@ void QNetworkAccessDebugPipeBackend::uploadReadyReadSlot() pushFromUpstreamToSocket(); } -void QNetworkAccessDebugPipeBackend::pushFromSocketToDownstream() -{ - 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); - } - } -} - void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket() { // FIXME @@ -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 +#include + #if defined(Q_OS_MACOS) #include #include @@ -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(); #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 // 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,21 +320,15 @@ void QNetworkReplyImplPrivate::handleNotifications() return; switch (notification) { case NotifyDownstreamReadyWrite: - if (copyDevice) + if (copyDevice) { _q_copyReadyRead(); - else - backend->downstreamReadyWrite(); + } else if (backend) { + if (backend->bytesAvailable() > 0) + readFromBackend(); + else if (backend->wantToRead()) + readFromBackend(); + } break; - - case NotifyCloseDownstreamChannel: - backend->closeDownstreamChannel(); - break; - - case NotifyCopyFinished: { - QIODevice *dev = qExchange(copyDevice, nullptr); - backend->copyFinished(dev); - 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 &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(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 sp, qint64 size); char* getDownloadBuffer(qint64 size); @@ -152,6 +149,8 @@ public: void encrypted(); void sslErrors(const QList &errors); + void readFromBackend(); + QNetworkAccessBackend *backend; QIODevice *outgoingData; QSharedPointer 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)