diff --git a/src/network/access/qrestaccessmanager.cpp b/src/network/access/qrestaccessmanager.cpp index 9c6c04fef10..474a4e24a5b 100644 --- a/src/network/access/qrestaccessmanager.cpp +++ b/src/network/access/qrestaccessmanager.cpp @@ -599,20 +599,13 @@ Q_LOGGING_CATEGORY(lcQrest, "qt.network.access.rest") \overload */ -/* - Memory management/object ownership: - - QRestAM is parent of QNAM and QRestReplies - - QRestReplies are parents of QNetworkReplies -*/ - -/*! - Constructs a QRestAccessManager and sets \a parent as the parent object. -*/ -QRestAccessManager::QRestAccessManager(QObject *parent) +QRestAccessManager::QRestAccessManager(QNetworkAccessManager *manager, QObject *parent) : QObject(*new QRestAccessManagerPrivate, parent) { Q_D(QRestAccessManager); - d->ensureNetworkAccessManager(); + d->qnam = manager; + if (!d->qnam) + qCWarning(lcQrest, "QRestAccessManager: QNetworkAccesManager is nullptr"); } /*! @@ -622,95 +615,6 @@ QRestAccessManager::QRestAccessManager(QObject *parent) QRestAccessManager::~QRestAccessManager() = default; -/*! - Returns whether QRestAccessManager is currently configured to automatically - delete replies once they have finished. By default this is \c true. - - \sa setDeletesRepliesOnFinished() -*/ -bool QRestAccessManager::deletesRepliesOnFinished() const -{ - Q_D(const QRestAccessManager); - return d->deletesRepliesOnFinished; -} - -/*! - Enables or disables automatic deletion of QRestReply instances - once the request has finished, according to the provided - \a autoDelete parameter. The deletion is done with deleteLater() - so that using the replies in directly-connected slots or callbacks is safe. - - \sa deletesRepliesOnFinished() -*/ -void QRestAccessManager::setDeletesRepliesOnFinished(bool autoDelete) -{ - Q_D(QRestAccessManager); - d->deletesRepliesOnFinished = autoDelete; -} - -/*! - Aborts all unfinished network requests. Calling this function is same - as calling QRestReply::abort() for all individual unfinished requests. - - \sa QRestReply::abort(), QNetworkReply::abort() -*/ -void QRestAccessManager::abortRequests() -{ - Q_D(QRestAccessManager); - - // Make copy of the reply container, as it might get modified when - // aborting individual requests if they finish immediately - const auto requests = d->activeRequests; - for (const auto &[req, _] : requests.asKeyValueRange()) - req->abort(); -} - -/*! - Sets \a timeout used for transfers. - - \sa QNetworkAccessManager::setTransferTimeout(), transferTimeout(), - QNetworkRequestFactory::setTransferTimeout() -*/ -void QRestAccessManager::setTransferTimeout(std::chrono::milliseconds timeout) -{ - Q_D(QRestAccessManager); - d->qnam->setTransferTimeout(timeout); -} - -/*! - Returns the timeout used for transfers. - - \sa setTransferTimeout(), QNetworkAccessManager::transferTimeoutAsDuration(), - QNetworkRequestFactory::transferTimeout() -*/ -std::chrono::milliseconds QRestAccessManager::transferTimeout() const -{ - Q_D(const QRestAccessManager); - return d->qnam->transferTimeoutAsDuration(); -} - -#ifndef QT_NO_DEBUG_STREAM -/*! - \fn QDebug QRestAccessManager::operator<<(QDebug debug, - const QRestAccessManager &manager) - - Writes \a manager into \a debug stream. - - \sa {Debugging Techniques} -*/ -QDebug operator<<(QDebug debug, const QRestAccessManager &manager) -{ - const QDebugStateSaver saver(debug); - debug.resetFormat().nospace(); - - debug << "QRestAccessManager(deletesRepliesOnFinished = " << manager.deletesRepliesOnFinished() - << ", transferTimeout = " << manager.transferTimeout() - << ", active requests = " << manager.d_func()->activeRequests.size() - << ")"; - return debug; -} -#endif // QT_NO_DEBUG_STREAM - /*! Returns the underlying QNetworkAccessManager instance. The instance can be used for accessing less-frequently used features and configurations. @@ -734,7 +638,7 @@ QRestAccessManagerPrivate::~QRestAccessManagerPrivate() } } -QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, const QJsonObject &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -743,7 +647,7 @@ QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, data, request, context, slot); } -QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, const QJsonArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -752,14 +656,14 @@ QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, data, request, context, slot); } -QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, const QVariantMap &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { return postWithDataImpl(request, QJsonObject::fromVariantMap(data), context, slot); } -QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, const QByteArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -767,7 +671,7 @@ QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->post(request, data); }, context, slot); } -QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, QHttpMultiPart *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -775,7 +679,7 @@ QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->post(request, data); }, context, slot); } -QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, QIODevice *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -783,14 +687,14 @@ QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->post(request, data); }, context, slot); } -QRestReply *QRestAccessManager::getNoDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::getNoDataImpl(const QNetworkRequest &request, const QObject *context, QtPrivate::QSlotObjectBase *slot) { Q_D(QRestAccessManager); return d->executeRequest([&]() { return d->qnam->get(request); }, context, slot); } -QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, const QByteArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -798,7 +702,7 @@ QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->get(request, data); }, context, slot); } -QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, const QJsonObject &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -807,7 +711,7 @@ QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, data, request, context, slot); } -QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, QIODevice *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -815,21 +719,21 @@ QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->get(request, data); }, context, slot); } -QRestReply *QRestAccessManager::deleteResourceNoDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::deleteResourceNoDataImpl(const QNetworkRequest &request, const QObject *context, QtPrivate::QSlotObjectBase *slot) { Q_D(QRestAccessManager); return d->executeRequest([&]() { return d->qnam->deleteResource(request); }, context, slot); } -QRestReply *QRestAccessManager::headNoDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::headNoDataImpl(const QNetworkRequest &request, const QObject *context, QtPrivate::QSlotObjectBase *slot) { Q_D(QRestAccessManager); return d->executeRequest([&]() { return d->qnam->head(request); }, context, slot); } -QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, const QJsonObject &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -838,7 +742,7 @@ QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, data, request, context, slot); } -QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, const QJsonArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -847,14 +751,14 @@ QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, data, request, context, slot); } -QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, const QVariantMap &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { return putWithDataImpl(request, QJsonObject::fromVariantMap(data), context, slot); } -QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, const QByteArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -862,7 +766,7 @@ QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->put(request, data); }, context, slot); } -QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, QHttpMultiPart *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -870,7 +774,7 @@ QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, return d->executeRequest([&]() { return d->qnam->put(request, data); }, context, slot); } -QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, QIODevice *data, +QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, QIODevice *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { Q_D(QRestAccessManager); @@ -879,7 +783,7 @@ QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, static const auto PATCH = "PATCH"_ba; -QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, const QJsonObject &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -889,7 +793,7 @@ QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request data, request, context, slot); } -QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, const QJsonArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -899,14 +803,14 @@ QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request data, request, context, slot); } -QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, const QVariantMap &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { return patchWithDataImpl(request, QJsonObject::fromVariantMap(data), context, slot); } -QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, const QByteArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { @@ -915,7 +819,7 @@ QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request context, slot); } -QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, QIODevice *data, +QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, QIODevice *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) { Q_D(QRestAccessManager); @@ -923,7 +827,7 @@ QRestReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request context, slot); } -QRestReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request, const QByteArray& method, const QByteArray &data, const QObject *context, QtPrivate::QSlotObjectBase *slot) @@ -933,7 +837,7 @@ QRestReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &reques context, slot); } -QRestReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request, const QByteArray& method, QIODevice *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) @@ -943,7 +847,7 @@ QRestReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &reques context, slot); } -QRestReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request, +QNetworkReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request, const QByteArray& method, QHttpMultiPart *data, const QObject *context, QtPrivate::QSlotObjectBase *slot) @@ -953,32 +857,31 @@ QRestReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &reques context, slot); } -QRestReply *QRestAccessManagerPrivate::createActiveRequest(QNetworkReply *networkReply, - const QObject *contextObject, - QtPrivate::QSlotObjectBase *slot) +QNetworkReply *QRestAccessManagerPrivate::createActiveRequest(QNetworkReply *reply, + const QObject *contextObject, + QtPrivate::QSlotObjectBase *slot) { Q_Q(QRestAccessManager); - Q_ASSERT(networkReply); - auto restReply = new QRestReply(networkReply, q); + Q_ASSERT(reply); QtPrivate::SlotObjSharedPtr slotPtr(QtPrivate::SlotObjUniquePtr{slot}); // adopts - activeRequests.insert(restReply, CallerInfo{contextObject, slotPtr}); + activeRequests.insert(reply, CallerInfo{contextObject, slotPtr}); + // The signal connections below are made to 'q' to avoid stray signal + // handling upon its destruction while requests were still in progress - // If context object is provided, use it with connect => context object lifecycle is considered - const QObject *context = contextObject ? contextObject : q; - QObject::connect(networkReply, &QNetworkReply::finished, context, [restReply, this]() { - handleReplyFinished(restReply); + QObject::connect(reply, &QNetworkReply::finished, q, [reply, this]() { + handleReplyFinished(reply); }); // Safe guard in case reply is destroyed before it's finished - QObject::connect(restReply, &QRestReply::destroyed, q, [restReply, this]() { - activeRequests.remove(restReply); + QObject::connect(reply, &QObject::destroyed, q, [reply, this]() { + activeRequests.remove(reply); }); // If context object is destroyed, clean up any possible replies it had associated with it if (contextObject) { - QObject::connect(contextObject, &QObject::destroyed, q, [restReply, this]() { - activeRequests.remove(restReply); + QObject::connect(contextObject, &QObject::destroyed, q, [reply, this]() { + activeRequests.remove(reply); }); } - return restReply; + return reply; } void QRestAccessManagerPrivate::verifyThreadAffinity(const QObject *contextObject) @@ -994,27 +897,17 @@ void QRestAccessManagerPrivate::verifyThreadAffinity(const QObject *contextObjec } } -void QRestAccessManagerPrivate::ensureNetworkAccessManager() +QNetworkReply* QRestAccessManagerPrivate::warnNoAccessManager() { - Q_Q(QRestAccessManager); - if (!qnam) { - qnam = new QNetworkAccessManager(q); - connect(qnam, &QNetworkAccessManager::authenticationRequired, this, - &QRestAccessManagerPrivate::handleAuthenticationRequired); -#ifndef QT_NO_NETWORKPROXY - QObject::connect(qnam, &QNetworkAccessManager::proxyAuthenticationRequired, - q, &QRestAccessManager::proxyAuthenticationRequired); -#endif - } + qCWarning(lcQrest, "QRestAccessManager: QNetworkAccessManager not set"); + return nullptr; } -void QRestAccessManagerPrivate::handleReplyFinished(QRestReply *restReply) +void QRestAccessManagerPrivate::handleReplyFinished(QNetworkReply *reply) { - Q_Q(QRestAccessManager); - - auto request = activeRequests.find(restReply); + auto request = activeRequests.find(reply); if (request == activeRequests.end()) { - qCWarning(lcQrest, "Unexpected reply received, ignoring"); + qCDebug(lcQrest, "QRestAccessManager: Unexpected reply received, ignoring"); return; } @@ -1022,41 +915,14 @@ void QRestAccessManagerPrivate::handleReplyFinished(QRestReply *restReply) activeRequests.erase(request); if (caller.slot) { - // Callback was provided. If we have context object, use it. - // For clarity: being here with a context object means it has not been destroyed - // while the request has been in progress + // Callback was provided + QRestReply restReply(reply); void *argv[] = { nullptr, &restReply }; + // If we have context object, use it QObject *context = caller.contextObject - ? const_cast(caller.contextObject) : nullptr; + ? const_cast(caller.contextObject.get()) : nullptr; caller.slot->call(context, argv); } - if (restReply->hasError()) - emit restReply->errorOccurred(restReply); - emit restReply->finished(restReply); - emit q->requestFinished(restReply); - - if (deletesRepliesOnFinished) - restReply->deleteLater(); -} - -void QRestAccessManagerPrivate::handleAuthenticationRequired(QNetworkReply *networkReply, - QAuthenticator *authenticator) -{ - Q_Q(QRestAccessManager); - QRestReply *restReply = restReplyFromNetworkReply(networkReply); - if (restReply) - emit q->authenticationRequired(restReply, authenticator); - else - qCWarning(lcQrest, "No matching QRestReply for authentication, ignoring."); -} - -QRestReply *QRestAccessManagerPrivate::restReplyFromNetworkReply(QNetworkReply *networkReply) -{ - for (const auto &[restReply,_] : activeRequests.asKeyValueRange()) { - if (restReply->networkReply() == networkReply) - return restReply; - } - return nullptr; } QT_END_NAMESPACE diff --git a/src/network/access/qrestaccessmanager.h b/src/network/access/qrestaccessmanager.h index 9656853f43d..bc92626d187 100644 --- a/src/network/access/qrestaccessmanager.h +++ b/src/network/access/qrestaccessmanager.h @@ -6,8 +6,6 @@ #include -#include - QT_BEGIN_NAMESPACE class QDebug; @@ -16,57 +14,57 @@ class QRestReply; #define QREST_METHOD_WITH_DATA(METHOD, DATA) \ public: \ template = true> \ -QRestReply *METHOD(const QNetworkRequest &request, DATA data, \ +QNetworkReply *METHOD(const QNetworkRequest &request, DATA data, \ const ContextTypeForFunctor *context, \ Functor &&callback) \ { \ return METHOD##WithDataImpl(request, data, context, \ QtPrivate::makeCallableObject(std::forward(callback))); \ } \ -QRestReply *METHOD(const QNetworkRequest &request, DATA data) \ +QNetworkReply *METHOD(const QNetworkRequest &request, DATA data) \ { \ return METHOD##WithDataImpl(request, data, nullptr, nullptr); \ } \ private: \ -QRestReply *METHOD##WithDataImpl(const QNetworkRequest &request, DATA data, \ +QNetworkReply *METHOD##WithDataImpl(const QNetworkRequest &request, DATA data, \ const QObject *context, QtPrivate::QSlotObjectBase *slot); \ /* end */ #define QREST_METHOD_NO_DATA(METHOD) \ public: \ template = true> \ -QRestReply *METHOD(const QNetworkRequest &request, \ +QNetworkReply *METHOD(const QNetworkRequest &request, \ const ContextTypeForFunctor *context, \ Functor &&callback) \ { \ return METHOD##NoDataImpl(request, context, \ QtPrivate::makeCallableObject(std::forward(callback))); \ } \ -QRestReply *METHOD(const QNetworkRequest &request) \ +QNetworkReply *METHOD(const QNetworkRequest &request) \ { \ return METHOD##NoDataImpl(request, nullptr, nullptr); \ } \ private: \ -QRestReply *METHOD##NoDataImpl(const QNetworkRequest &request, \ +QNetworkReply *METHOD##NoDataImpl(const QNetworkRequest &request, \ const QObject *context, QtPrivate::QSlotObjectBase *slot); \ /* end */ #define QREST_METHOD_CUSTOM_WITH_DATA(DATA) \ public: \ template = true> \ -QRestReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data, \ +QNetworkReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data, \ const ContextTypeForFunctor *context, \ Functor &&callback) \ { \ return customWithDataImpl(request, method, data, context, \ QtPrivate::makeCallableObject(std::forward(callback))); \ } \ -QRestReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data) \ +QNetworkReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data) \ { \ return customWithDataImpl(request, method, data, nullptr, nullptr); \ } \ private: \ -QRestReply *customWithDataImpl(const QNetworkRequest& request, const QByteArray &method, \ +QNetworkReply *customWithDataImpl(const QNetworkRequest& request, const QByteArray &method, \ DATA data, const QObject* context, \ QtPrivate::QSlotObjectBase *slot); \ /* end */ @@ -75,27 +73,18 @@ class QRestAccessManagerPrivate; class Q_NETWORK_EXPORT QRestAccessManager : public QObject { Q_OBJECT - - using CallbackPrototype = void(*)(QRestReply*); + using CallbackPrototype = void(*)(QRestReply&); template using ContextTypeForFunctor = typename QtPrivate::ContextTypeForFunctor::ContextType; template using if_compatible_callback = std::enable_if_t< QtPrivate::AreFunctionsCompatible::value, bool>; public: - explicit QRestAccessManager(QObject *parent = nullptr); + explicit QRestAccessManager(QNetworkAccessManager *manager, QObject *parent = nullptr); ~QRestAccessManager() override; QNetworkAccessManager *networkAccessManager() const; - bool deletesRepliesOnFinished() const; - void setDeletesRepliesOnFinished(bool autoDelete); - - void setTransferTimeout(std::chrono::milliseconds timeout); - std::chrono::milliseconds transferTimeout() const; - - void abortRequests(); - QREST_METHOD_NO_DATA(deleteResource) QREST_METHOD_NO_DATA(head) QREST_METHOD_NO_DATA(get) @@ -123,17 +112,7 @@ public: QREST_METHOD_CUSTOM_WITH_DATA(QIODevice *) QREST_METHOD_CUSTOM_WITH_DATA(QHttpMultiPart *) -Q_SIGNALS: -#ifndef QT_NO_NETWORKPROXY - void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); -#endif - void authenticationRequired(QRestReply *reply, QAuthenticator *authenticator); - void requestFinished(QRestReply *reply); - private: -#ifndef QT_NO_DEBUG_STREAM - friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QRestAccessManager &manager); -#endif Q_DECLARE_PRIVATE(QRestAccessManager) Q_DISABLE_COPY(QRestAccessManager) }; diff --git a/src/network/access/qrestaccessmanager_p.h b/src/network/access/qrestaccessmanager_p.h index 91c6f89dd02..08f1341a092 100644 --- a/src/network/access/qrestaccessmanager_p.h +++ b/src/network/access/qrestaccessmanager_p.h @@ -34,30 +34,28 @@ public: QRestAccessManagerPrivate(); ~QRestAccessManagerPrivate() override; - void ensureNetworkAccessManager(); - - QRestReply *createActiveRequest(QNetworkReply *networkReply, const QObject *contextObject, - QtPrivate::QSlotObjectBase *slot); - - void removeActiveRequest(QRestReply *restReply); - void handleReplyFinished(QRestReply *restReply); - void handleAuthenticationRequired(QNetworkReply *networkReply, QAuthenticator *authenticator); - QRestReply *restReplyFromNetworkReply(QNetworkReply *networkReply); + QNetworkReply* createActiveRequest(QNetworkReply *reply, const QObject *contextObject, + QtPrivate::QSlotObjectBase *slot); + void handleReplyFinished(QNetworkReply *reply); template - QRestReply *executeRequest(Functor requestOperation, + QNetworkReply *executeRequest(Functor requestOperation, const QObject *context, QtPrivate::QSlotObjectBase *slot) { + if (!qnam) + return warnNoAccessManager(); verifyThreadAffinity(context); QNetworkReply *reply = requestOperation(); return createActiveRequest(reply, context, slot); } template - QRestReply *executeRequest(Functor requestOperation, Json jsonData, + QNetworkReply *executeRequest(Functor requestOperation, Json jsonData, const QNetworkRequest &request, const QObject *context, QtPrivate::QSlotObjectBase *slot) { + if (!qnam) + return warnNoAccessManager(); verifyThreadAffinity(context); QNetworkRequest req(request); if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) { @@ -70,12 +68,14 @@ public: } void verifyThreadAffinity(const QObject *contextObject); + Q_DECL_COLD_FUNCTION + QNetworkReply* warnNoAccessManager(); struct CallerInfo { - const QObject *contextObject = nullptr; + QPointer contextObject = nullptr; QtPrivate::SlotObjSharedPtr slot; }; - QHash activeRequests; + QHash activeRequests; QNetworkAccessManager *qnam = nullptr; bool deletesRepliesOnFinished = true; diff --git a/src/network/access/qrestreply.cpp b/src/network/access/qrestreply.cpp index e9334a873cd..2cf8e19cb54 100644 --- a/src/network/access/qrestreply.cpp +++ b/src/network/access/qrestreply.cpp @@ -35,121 +35,27 @@ Q_DECLARE_LOGGING_CATEGORY(lcQrest) \sa QRestAccessManager, QNetworkReply */ -/*! - \fn void QRestReply::readyRead(QRestReply *reply) - - This signal is emitted when \a reply has received new data. - - \sa body(), bytesAvailable(), isFinished() -*/ - -/*! - \fn void QRestReply::downloadProgress(qint64 bytesReceived, - qint64 bytesTotal, - QRestReply* reply) - - This signal is emitted to indicate the progress of the download part of - this network \a reply. - - The \a bytesReceived parameter indicates the number of bytes received, - while \a bytesTotal indicates the total number of bytes expected to be - downloaded. If the number of bytes to be downloaded is not known, for - instance due to a missing \c Content-Length header, \a bytesTotal - will be -1. - - See \l QNetworkReply::downloadProgress() documentation for more details. - - \sa bytesAvailable(), readyRead(), uploadProgress() -*/ - -/*! - \fn void QRestReply::uploadProgress(qint64 bytesSent, qint64 bytesTotal, - QRestReply* reply) - - This signal is emitted to indicate the progress of the upload part of - \a reply. - - The \a bytesSent parameter indicates the number of bytes already uploaded, - while \a bytesTotal indicates the total number of bytes still to upload. - - If the number of bytes to upload is not known, \a bytesTotal will be -1. - - See \l QNetworkReply::uploadProgress() documentation for more details. - - \sa QNetworkReply::uploadProgress(), downloadProgress() -*/ - -/*! - \fn void QRestReply::finished(QRestReply *reply) - - This signal is emitted when \a reply has finished processing. This - signal is emitted also in cases when the reply finished due to network - or protocol errors (the server did not reply with an HTTP status). - - \sa isFinished(), httpStatus(), error() -*/ - -/*! - \fn void QRestReply::errorOccurred(QRestReply *reply) - - This signal is emitted if, while processing \a reply, an error occurs that - is considered to be a network/protocol error. These errors are - disctinct from HTTP error responses such as \c {500 Internal Server Error}. - This signal is emitted together with the - finished() signal, and often connecting to that is sufficient. - - \sa finished(), isFinished(), httpStatus(), error() -*/ - -QRestReply::QRestReply(QNetworkReply *reply, QObject *parent) - : QObject(*new QRestReplyPrivate, parent) +QRestReply::QRestReply(QNetworkReply *reply) + : wrapped(reply) { - Q_D(QRestReply); - Q_ASSERT(reply); - d->networkReply = reply; - // Reparent so that destruction of QRestReply destroys QNetworkReply - reply->setParent(this); - - QObject::connect(reply, &QNetworkReply::readyRead, this, [this] { - emit readyRead(this); - }); - QObject::connect(reply, &QNetworkReply::downloadProgress, this, - [this](qint64 bytesReceived, qint64 bytesTotal) { - emit downloadProgress(bytesReceived, bytesTotal, this); - }); - QObject::connect(reply, &QNetworkReply::uploadProgress, this, - [this] (qint64 bytesSent, qint64 bytesTotal) { - emit uploadProgress(bytesSent, bytesTotal, this); - }); + if (!wrapped) + qCWarning(lcQrest, "QRestReply: QNetworkReply is nullptr"); } /*! Destroys this QRestReply object. - - \sa abort() */ QRestReply::~QRestReply() - = default; +{ + delete d; +} /*! Returns a pointer to the underlying QNetworkReply wrapped by this object. */ QNetworkReply *QRestReply::networkReply() const { - Q_D(const QRestReply); - return d->networkReply; -} - -/*! - Aborts the network operation immediately. The finished() signal - will be emitted. - - \sa QRestAccessManager::abortRequests() QNetworkReply::abort() -*/ -void QRestReply::abort() -{ - Q_D(QRestReply); - d->networkReply->abort(); + return wrapped; } /*! @@ -167,19 +73,24 @@ void QRestReply::abort() set to QJsonParseError::NoError to distinguish this case from an actual error. - \sa body(), text(), finished(), isFinished() + \sa body(), text() */ std::optional QRestReply::json(QJsonParseError *error) { - Q_D(QRestReply); - if (!isFinished()) { + if (!wrapped) { + if (error) + *error = {0, QJsonParseError::ParseError::NoError}; + return std::nullopt; + } + + if (!wrapped->isFinished()) { qCWarning(lcQrest, "Attempt to read json() of an unfinished reply, ignoring."); if (error) *error = {0, QJsonParseError::ParseError::NoError}; return std::nullopt; } QJsonParseError parseError; - const QByteArray data = d->networkReply->readAll(); + const QByteArray data = wrapped->readAll(); const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); if (error) *error = parseError; @@ -199,8 +110,7 @@ std::optional QRestReply::json(QJsonParseError *error) */ QByteArray QRestReply::body() { - Q_D(QRestReply); - return d->networkReply->readAll(); + return wrapped ? wrapped->readAll() : QByteArray{}; } /*! @@ -221,15 +131,21 @@ QByteArray QRestReply::body() */ QString QRestReply::text() { - Q_D(QRestReply); QString result; + if (!wrapped) + return result; - QByteArray data = d->networkReply->readAll(); + QByteArray data = wrapped->readAll(); if (data.isEmpty()) return result; + // Text decoding needs to persist decoding state across calls to this function, + // so allocate decoder if not yet allocated. + if (!d) + d = new QRestReplyPrivate; + if (!d->decoder) { - const QByteArray charset = d->contentCharset(); + const QByteArray charset = QRestReplyPrivate::contentCharset(wrapped); d->decoder = QStringDecoder(charset); if (!d->decoder->isValid()) { // the decoder may not support the mimetype's charset qCWarning(lcQrest, "text(): Charset \"%s\" is not supported", charset.constData()); @@ -259,8 +175,7 @@ QString QRestReply::text() */ int QRestReply::httpStatus() const { - Q_D(const QRestReply); - return d->networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + return wrapped ? wrapped->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : 0; } /*! @@ -296,8 +211,16 @@ bool QRestReply::isHttpStatusSuccess() const */ bool QRestReply::hasError() const { - Q_D(const QRestReply); - return d->hasNonHttpError(); + if (!wrapped) + return false; + + const int status = httpStatus(); + if (status > 0) { + // The HTTP status is set upon receiving the response headers, but the + // connection might still fail later while receiving the body data. + return wrapped->error() == QNetworkReply::RemoteHostClosedError; + } + return wrapped->error() != QNetworkReply::NoError; } /*! @@ -309,10 +232,9 @@ bool QRestReply::hasError() const */ QNetworkReply::NetworkError QRestReply::error() const { - Q_D(const QRestReply); if (!hasError()) return QNetworkReply::NetworkError::NoError; - return d->networkReply->error(); + return wrapped->error(); } /*! @@ -322,32 +244,11 @@ QNetworkReply::NetworkError QRestReply::error() const */ QString QRestReply::errorString() const { - Q_D(const QRestReply); if (hasError()) - return d->networkReply->errorString(); + return wrapped->errorString(); return {}; } -/*! - Returns whether the network request has finished. -*/ -bool QRestReply::isFinished() const -{ - Q_D(const QRestReply); - return d->networkReply->isFinished(); -} - -/*! - Returns the number of bytes available. - - \sa body -*/ -qint64 QRestReply::bytesAvailable() const -{ - Q_D(const QRestReply); - return d->networkReply->bytesAvailable(); -} - QRestReplyPrivate::QRestReplyPrivate() = default; @@ -377,38 +278,37 @@ static QLatin1StringView operationName(QNetworkAccessManager::Operation operatio } /*! - \fn QDebug QRestReply::operator<<(QDebug debug, const QRestReply *reply) + \fn QDebug QRestReply::operator<<(QDebug debug, const QRestReply &reply) Writes the \a reply into the \a debug object for debugging purposes. \sa {Debugging Techniques} */ -QDebug operator<<(QDebug debug, const QRestReply *reply) +QDebug operator<<(QDebug debug, const QRestReply &reply) { const QDebugStateSaver saver(debug); debug.resetFormat().nospace(); - if (!reply) { - debug << "QRestReply(nullptr)"; + if (!reply.networkReply()) { + debug << "QRestReply(no network reply)"; return debug; } - - debug << "QRestReply(isSuccess = " << reply->isSuccess() - << ", httpStatus = " << reply->httpStatus() - << ", isHttpStatusSuccess = " << reply->isHttpStatusSuccess() - << ", hasError = " << reply->hasError() - << ", errorString = " << reply->errorString() - << ", error = " << reply->error() - << ", isFinished = " << reply->isFinished() - << ", bytesAvailable = " << reply->bytesAvailable() - << ", url " << reply->networkReply()->url() - << ", operation = " << operationName(reply->networkReply()->operation()) - << ", reply headers = " << reply->networkReply()->rawHeaderPairs() + debug << "QRestReply(isSuccess = " << reply.isSuccess() + << ", httpStatus = " << reply.httpStatus() + << ", isHttpStatusSuccess = " << reply.isHttpStatusSuccess() + << ", hasError = " << reply.hasError() + << ", errorString = " << reply.errorString() + << ", error = " << reply.error() + << ", isFinished = " << reply.networkReply()->isFinished() + << ", bytesAvailable = " << reply.networkReply()->bytesAvailable() + << ", url " << reply.networkReply()->url() + << ", operation = " << operationName(reply.networkReply()->operation()) + << ", reply headers = " << reply.networkReply()->rawHeaderPairs() << ")"; return debug; } #endif // QT_NO_DEBUG_STREAM -QByteArray QRestReplyPrivate::contentCharset() const +QByteArray QRestReplyPrivate::contentCharset(const QNetworkReply* reply) { // Content-type consists of mimetype and optional parameters, of which one may be 'charset' // Example values and their combinations below are all valid, see RFC 7231 section 3.1.1.5 @@ -418,10 +318,10 @@ QByteArray QRestReplyPrivate::contentCharset() const // text/plain; charset=utf-8;version=1.7 // text/plain; charset = utf-8 // text/plain; charset ="utf-8" - QByteArray contentTypeValue = - networkReply->header(QNetworkRequest::KnownHeaders::ContentTypeHeader).toByteArray(); // Default to the most commonly used UTF-8. QByteArray charset{"UTF-8"}; + const QByteArray contentTypeValue = + reply->header(QNetworkRequest::KnownHeaders::ContentTypeHeader).toByteArray(); QList parameters = contentTypeValue.split(';'); if (parameters.size() >= 2) { // Need at least one parameter in addition to the mimetype itself @@ -442,18 +342,6 @@ QByteArray QRestReplyPrivate::contentCharset() const return charset; } -// Returns true if there's an error that isn't appropriately indicated by the HTTP status -bool QRestReplyPrivate::hasNonHttpError() const -{ - const int status = networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status > 0) { - // The HTTP status is set upon receiving the response headers, but the - // connection might still fail later while receiving the body data. - return networkReply->error() == QNetworkReply::RemoteHostClosedError; - } - return networkReply->error() != QNetworkReply::NoError; -} - QT_END_NAMESPACE #include "moc_qrestreply.cpp" diff --git a/src/network/access/qrestreply.h b/src/network/access/qrestreply.h index 9a99e9e64a6..93f25d0aa29 100644 --- a/src/network/access/qrestreply.h +++ b/src/network/access/qrestreply.h @@ -6,7 +6,10 @@ #include +#include + #include +#include QT_BEGIN_NAMESPACE @@ -17,53 +20,52 @@ class QJsonDocument; class QString; class QRestReplyPrivate; -class Q_NETWORK_EXPORT QRestReply : public QObject +class QRestReply { - Q_OBJECT - public: - ~QRestReply() override; + Q_NETWORK_EXPORT explicit QRestReply(QNetworkReply *reply); + Q_NETWORK_EXPORT ~QRestReply(); - QNetworkReply *networkReply() const; + QRestReply(QRestReply &&other) noexcept + : wrapped(std::move(other.wrapped)), + d(std::exchange(other.d, nullptr)) + { + } + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QRestReply) + void swap(QRestReply &other) noexcept + { + wrapped.swap(other.wrapped); + std::swap(d, other.d); + } - std::optional json(QJsonParseError *error = nullptr); - QByteArray body(); - QString text(); + Q_NETWORK_EXPORT QNetworkReply *networkReply() const; + + Q_NETWORK_EXPORT std::optional json(QJsonParseError *error = nullptr); + Q_NETWORK_EXPORT QByteArray body(); + Q_NETWORK_EXPORT QString text(); bool isSuccess() const { return !hasError() && isHttpStatusSuccess(); } - int httpStatus() const; - bool isHttpStatusSuccess() const; + Q_NETWORK_EXPORT int httpStatus() const; + Q_NETWORK_EXPORT bool isHttpStatusSuccess() const; - bool hasError() const; - QNetworkReply::NetworkError error() const; - QString errorString() const; - - bool isFinished() const; - qint64 bytesAvailable() const; - -public Q_SLOTS: - void abort(); - -Q_SIGNALS: - void finished(QRestReply *reply); - void errorOccurred(QRestReply *reply); - void readyRead(QRestReply *reply); - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, QRestReply *reply); - void uploadProgress(qint64 bytesSent, qint64 bytesTotal, QRestReply* reply); + Q_NETWORK_EXPORT bool hasError() const; + Q_NETWORK_EXPORT QNetworkReply::NetworkError error() const; + Q_NETWORK_EXPORT QString errorString() const; private: - friend class QRestAccessManagerPrivate; #ifndef QT_NO_DEBUG_STREAM - friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QRestReply *reply); + friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QRestReply &reply); #endif - explicit QRestReply(QNetworkReply *reply, QObject *parent = nullptr); - Q_DECLARE_PRIVATE(QRestReply) - Q_DISABLE_COPY_MOVE(QRestReply) + QPointer wrapped; + QRestReplyPrivate *d = nullptr; + Q_DISABLE_COPY(QRestReply) }; +Q_DECLARE_SHARED(QRestReply) + QT_END_NAMESPACE #endif // QRESTREPLY_H diff --git a/src/network/access/qrestreply_p.h b/src/network/access/qrestreply_p.h index 5e76b54bcbb..ae229cda7ba 100644 --- a/src/network/access/qrestreply_p.h +++ b/src/network/access/qrestreply_p.h @@ -15,27 +15,22 @@ // We mean it. // -#include "private/qobject_p.h" -#include -#include - #include QT_BEGIN_NAMESPACE +class QNetworkReply; class QStringDecoder; -class QRestReplyPrivate : public QObjectPrivate +class QRestReplyPrivate { public: QRestReplyPrivate(); - ~QRestReplyPrivate() override; + ~QRestReplyPrivate(); - QNetworkReply *networkReply = nullptr; std::optional decoder; - QByteArray contentCharset() const; - bool hasNonHttpError() const; + static QByteArray contentCharset(const QNetworkReply *reply); }; QT_END_NAMESPACE diff --git a/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp index b619f3fcd5b..f40a1c038c6 100644 --- a/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp +++ b/tests/auto/network/access/qrestaccessmanager/tst_qrestaccessmanager.cpp @@ -27,63 +27,76 @@ class tst_QRestAccessManager : public QObject Q_OBJECT private slots: - void initTestCase(); - void initialization(); void destruction(); void callbacks(); - void threading(); - void networkRequestReply(); - void abort(); - void authentication(); - void userInfo(); + void requests(); + void reply(); void errors(); void body(); void json(); void text(); void textStreaming(); - void download(); - void upload(); - void timeout(); private: - void memberHandler(QRestReply *reply); + void memberHandler(QRestReply &reply); friend class Transient; - QList m_expectedReplies; - QList m_actualReplies; + QList m_expectedReplies; + QList m_actualReplies; }; -void tst_QRestAccessManager::initTestCase() -{ - qRegisterMetaType(); // For QSignalSpy -} - void tst_QRestAccessManager::initialization() { - QRestAccessManager manager; - QVERIFY(manager.networkAccessManager()); - QCOMPARE(manager.deletesRepliesOnFinished(), true); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestAccessManager: QNetworkAccesManager is nullptr"); + QRestAccessManager manager1(nullptr); + QVERIFY(!manager1.networkAccessManager()); + + QNetworkAccessManager qnam; + QRestAccessManager manager2(&qnam); + QVERIFY(manager2.networkAccessManager()); } -#define VERIFY_REPLY_OK(METHOD) \ - QTRY_VERIFY(replyFromServer); \ - QCOMPARE(serverSideRequest.method, METHOD); \ - QVERIFY(replyFromServer->isSuccess()); \ - QVERIFY(!replyFromServer->hasError()); \ - replyFromServer->deleteLater(); \ - replyFromServer = nullptr; \ +void tst_QRestAccessManager::reply() +{ + QNetworkAccessManager qnam; -void tst_QRestAccessManager::networkRequestReply() + QNetworkReply *nr = qnam.get(QNetworkRequest(QUrl{"someurl"})); + QRestReply rr1(nr); + QCOMPARE(rr1.networkReply(), nr); + + // Move-construct + QRestReply rr2(std::move(rr1)); + QCOMPARE(rr2.networkReply(), nr); + + // Move-assign + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QRestReply: QNetworkReply is nullptr"); + QRestReply rr3(nullptr); + rr3 = std::move(rr2); + QCOMPARE(rr3.networkReply(), nr); +} + +#define VERIFY_REPLY_OK(METHOD) \ +{ \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + QCOMPARE(serverSideRequest.method, METHOD); \ + QVERIFY(restReply.isSuccess()); \ + QVERIFY(!restReply.hasError()); \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} + +void tst_QRestAccessManager::requests() { // A basic test for each HTTP method against the local testserver. - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); request.setRawHeader("Content-Type"_ba, "text/plain"); // To silence missing content-type warn - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; std::unique_ptr multiPart; QHttpPart part; part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\"")); @@ -99,7 +112,7 @@ void tst_QRestAccessManager::networkRequestReply() response = serverSideResponse; }); - auto callback = [&](QRestReply *reply) { replyFromServer = reply; }; + auto callback = [&](QRestReply &reply) { networkReply = reply.networkReply(); }; const QByteArray byteArrayData{"some_data"_ba}; const QJsonObject jsonObjectData{{"key1", "value1"}, {"key2", "value2"}}; const QJsonArray jsonArrayData{{"arrvalue1", "arrvalue2", QJsonObject{{"key1", "value1"}}}}; @@ -253,44 +266,9 @@ void tst_QRestAccessManager::networkRequestReply() //manager.sendCustomRequest(request, "FOOBAR", this, [](){}); // No verb || no data } -void tst_QRestAccessManager::abort() +void tst_QRestAccessManager::memberHandler(QRestReply &reply) { - // Test aborting requests - QRestAccessManager manager; - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - - QSignalSpy finishedSpy(&manager, &QRestAccessManager::requestFinished); - int callbackCount = 0; - auto callback = [&](QRestReply*) { - callbackCount++; - }; - - // Abort without any requests - manager.abortRequests(); - QTest::qWait(20); - QCOMPARE(finishedSpy.size(), 0); - - // Abort immediately after requesting - manager.get(request, this, callback); - manager.abortRequests(); - QTRY_COMPARE(callbackCount, 1); - QTRY_COMPARE(finishedSpy.size(), 1); - - // Abort after request has been sent out - server.setHandler([&](HttpData, HttpData&, ResponseControl &control) { - control.respond = false; - manager.abortRequests(); - }); - manager.get(request, this, callback); - QTRY_COMPARE(callbackCount, 2); - QTRY_COMPARE(finishedSpy.size(), 2); -} - -void tst_QRestAccessManager::memberHandler(QRestReply *reply) -{ - m_actualReplies.append(reply); + m_actualReplies.append(reply.networkReply()); } // Class that is destroyed during an active request. @@ -301,9 +279,9 @@ class Transient : public QObject public: explicit Transient(tst_QRestAccessManager *test) : QObject(test), m_test(test) {} - void memberHandler(QRestReply *reply) + void memberHandler(QRestReply &reply) { - m_test->m_actualReplies.append(reply); + m_test->m_actualReplies.append(reply.networkReply()); } private: @@ -311,53 +289,51 @@ private: }; template ::value, bool> = true> + QtPrivate::AreFunctionsCompatible::value, bool> = true> inline constexpr bool isCompatibleCallback(Functor &&) { return true; } template ::value, bool> = true, + !QtPrivate::AreFunctionsCompatible::value, bool> = true, typename = void> inline constexpr bool isCompatibleCallback(Functor &&) { return false; } void tst_QRestAccessManager::callbacks() { - QRestAccessManager manager; + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); - manager.setDeletesRepliesOnFinished(false); // Don't autodelete so we can compare results later QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error - QSignalSpy managerFinishedSpy(&manager, &QRestAccessManager::requestFinished); - auto lambdaHandler = [this](QRestReply *reply) { m_actualReplies.append(reply); }; - QRestReply *reply = nullptr; + auto lambdaHandler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; Transient *transient = nullptr; QByteArray data{"some_data"}; // Compile-time tests for callback signatures - static_assert(isCompatibleCallback([](QRestReply*){})); // Correct signature + static_assert(isCompatibleCallback([](QRestReply&){})); // Correct signature static_assert(isCompatibleCallback(lambdaHandler)); static_assert(isCompatibleCallback(&Transient::memberHandler)); static_assert(isCompatibleCallback([](){})); // Less parameters are allowed - static_assert(!isCompatibleCallback([](QString){})); // Wrong parameter type - static_assert(!isCompatibleCallback([](QNetworkReply*){})); // Wrong parameter type - static_assert(!isCompatibleCallback([](const QString &){})); // Wrong parameter type - static_assert(!isCompatibleCallback([](QRestReply*, QString){})); // Too many parameters + static_assert(!isCompatibleCallback([](QString){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply*){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](const QString &){})); // Wrong parameter type + static_assert(!isCompatibleCallback([](QRestReply&, QString){})); // Too many parameters // -- Test without data - // Without callback - reply = manager.get(request); - QCOMPARE(reply->isFinished(), false); // Test this once here + // Without callback using signals and slot + QNetworkReply* reply = manager.get(request); m_expectedReplies.append(reply); - QObject::connect(reply, &QRestReply::finished, lambdaHandler); + QObject::connect(reply, &QNetworkReply::finished, this, + [this, reply](){m_actualReplies.append(reply);}); // With lambda callback, without context object m_expectedReplies.append(manager.get(request, nullptr, lambdaHandler)); m_expectedReplies.append(manager.get(request, nullptr, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With lambda callback and context object m_expectedReplies.append(manager.get(request, this, lambdaHandler)); m_expectedReplies.append(manager.get(request, this, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With member callback and context object m_expectedReplies.append(manager.get(request, this, &tst_QRestAccessManager::memberHandler)); // With context object that is destroyed, there should be no callback or eg. crash. @@ -367,31 +343,26 @@ void tst_QRestAccessManager::callbacks() // Let requests finish QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); - QTRY_COMPARE(managerFinishedSpy.size(), m_actualReplies.size()); for (auto reply: m_actualReplies) { - QVERIFY(!reply->isSuccess()); - QVERIFY(reply->hasError()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - QCOMPARE(reply->isFinished(), true); - reply->deleteLater(); + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); } m_actualReplies.clear(); m_expectedReplies.clear(); - managerFinishedSpy.clear(); // -- Test with data - reply = manager.post(request, data); - m_expectedReplies.append(reply); - QObject::connect(reply, &QRestReply::finished, lambdaHandler); - // With lambda callback, without context object m_expectedReplies.append(manager.post(request, data, nullptr, lambdaHandler)); m_expectedReplies.append(manager.post(request, data, nullptr, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With lambda callback and context object m_expectedReplies.append(manager.post(request, data, this, lambdaHandler)); m_expectedReplies.append(manager.post(request, data, this, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With member callback and context object m_expectedReplies.append(manager.post(request, data, this, &tst_QRestAccessManager::memberHandler)); @@ -402,31 +373,27 @@ void tst_QRestAccessManager::callbacks() // Let requests finish QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); - QTRY_COMPARE(managerFinishedSpy.size(), m_actualReplies.size()); for (auto reply: m_actualReplies) { - QVERIFY(!reply->isSuccess()); - QVERIFY(reply->hasError()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - QCOMPARE(reply->isFinished(), true); + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); reply->deleteLater(); } m_actualReplies.clear(); m_expectedReplies.clear(); - managerFinishedSpy.clear(); // -- Test GET with data separately, as GET provides methods that are usable with and - // without data, and fairly easy to get the qrestaccessmanager.h template SFINAE subtly wrong - reply = manager.get(request, data); - m_expectedReplies.append(reply); - QObject::connect(reply, &QRestReply::finished, lambdaHandler); + // without data, and fairly easy to get the qrestaccessmanager.h template SFINAE subtly wrong. // With lambda callback, without context object m_expectedReplies.append(manager.get(request, data, nullptr, lambdaHandler)); m_expectedReplies.append(manager.get(request, data, nullptr, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With lambda callback and context object m_expectedReplies.append(manager.get(request, data, this, lambdaHandler)); m_expectedReplies.append(manager.get(request, data, this, - [this](QRestReply *reply){m_actualReplies.append(reply);})); + [this](QRestReply &reply){m_actualReplies.append(reply.networkReply());})); // With member callback and context object m_expectedReplies.append(manager.get(request, data, this, &tst_QRestAccessManager::memberHandler)); @@ -437,75 +404,30 @@ void tst_QRestAccessManager::callbacks() // Let requests finish QTRY_COMPARE(m_actualReplies.size(), m_expectedReplies.size()); - QTRY_COMPARE(managerFinishedSpy.size(), m_actualReplies.size()); for (auto reply: m_actualReplies) { - QVERIFY(!reply->isSuccess()); - QVERIFY(reply->hasError()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - QCOMPARE(reply->isFinished(), true); - reply->deleteLater(); + QRestReply restReply(reply); + QVERIFY(!restReply.isSuccess()); + QVERIFY(restReply.hasError()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + QCOMPARE(restReply.networkReply()->isFinished(), true); + restReply.networkReply()->deleteLater(); } m_actualReplies.clear(); m_expectedReplies.clear(); - managerFinishedSpy.clear(); -} - -class RestWorker : public QObject -{ - Q_OBJECT -public: - explicit RestWorker(QObject *parent = nullptr) : QObject(parent) - { - m_manager = new QRestAccessManager(this); - } - QRestAccessManager *m_manager; - -public slots: - void issueRestRequest() - { - QNetworkRequest request{u"i_dont_exist"_s}; - m_manager->get(request, this, [this](QRestReply *reply){ - emit result(reply->body()); - }); - } -signals: - void result(const QByteArray &data); -}; - -void tst_QRestAccessManager::threading() -{ - // QRestAccessManager and QRestReply are only allowed to use in the thread they live in. - - // A "sanity test" for checking that there are no problems with running the QRestAM - // in another thread. - QThread restWorkThread; - RestWorker restWorker; - restWorker.moveToThread(&restWorkThread); - - QList results; - QObject::connect(&restWorker, &RestWorker::result, this, [&](const QByteArray &data){ - results.append(data); - }); - restWorkThread.start(); - - QMetaObject::invokeMethod(&restWorker, &RestWorker::issueRestRequest); - QTRY_COMPARE(results.size(), 1); - restWorkThread.quit(); - restWorkThread.wait(); } void tst_QRestAccessManager::destruction() { - QRestAccessManager *manager = new QRestAccessManager; - manager->setDeletesRepliesOnFinished(false); // Don't autodelete so we can compare results later + std::unique_ptr qnam = std::make_unique(); + std::unique_ptr manager = std::make_unique(qnam.get()); QNetworkRequest request{u"i_dont_exist"_s}; // Will result in ProtocolUnknown error m_expectedReplies.clear(); m_actualReplies.clear(); - auto handler = [this](QRestReply *reply) { m_actualReplies.append(reply); }; + auto handler = [this](QRestReply &reply) { m_actualReplies.append(reply.networkReply()); }; // Delete reply immediately, make sure nothing bad happens and that there is no callback - QRestReply *reply = manager->get(request, this, handler); - delete reply; + QNetworkReply *networkReply = manager->get(request, this, handler); + delete networkReply; QTest::qWait(20); // allow some time for the callback to arrive (it shouldn't) QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 @@ -514,114 +436,39 @@ void tst_QRestAccessManager::destruction() manager->post(request, "data"_ba, this, handler); QTest::ignoreMessage(QtWarningMsg, "Access manager destroyed while 2 requests were still" " in progress"); - delete manager; + manager.reset(); + QTest::qWait(20); + QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 + + // Destroy the underlying QNAM while requests in progress + manager = std::make_unique(qnam.get()); + manager->get(request, this, handler); + manager->post(request, "data"_ba, this, handler); + qnam.reset(); QTest::qWait(20); QCOMPARE(m_actualReplies.size(), m_expectedReplies.size()); // Both should be 0 } -void tst_QRestAccessManager::authentication() -{ - // Test the case where server responds with '401' (authentication required). - // The QRestAM emits an authenticationRequired signal, which is used to the username/password. - // The QRestAM/QNAM underneath then automatically resends the request. - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; - - HttpData serverSideRequest; - server.setHandler([&](HttpData request, HttpData &response, ResponseControl&) { - if (!request.headers.contains("Authorization"_ba)) { - response.status = 401; - response.headers.insert("WWW-Authenticate: "_ba, "Basic realm=\"secret_place\""_ba); - } else { - response.status = 200; - } - serverSideRequest = request; // store for checking later the 'Authorization' header value - }); - - QObject::connect(&manager, &QRestAccessManager::authenticationRequired, this, - [](QRestReply*, QAuthenticator *authenticator) { - authenticator->setUser(u"a_user"_s); - authenticator->setPassword(u"a_password"_s); - }); - - // Issue a GET request without any authorization data. - int finishedCount = 0; - manager.get(request, this, [&](QRestReply *reply) { - finishedCount++; - replyFromServer = reply; - }); - QTRY_VERIFY(replyFromServer); - // Server and QRestAM/QNAM exchange req/res twice, but finished() should be emitted just once - QCOMPARE(finishedCount, 1); - QCOMPARE(serverSideRequest.headers["Authorization"_ba], "Basic YV91c2VyOmFfcGFzc3dvcmQ="_ba); -} - -void tst_QRestAccessManager::userInfo() -{ - // Tests setting of username and password into the request factory - using ReplyPtr = std::unique_ptr; - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - - QNetworkRequestFactory factory(server.url()); - factory.setUserName(u"a_user"_s); - const auto password = u"a_password"_s; - factory.setPassword(password); - - HttpData serverSideRequest; - server.setHandler([&](HttpData request, HttpData& response, ResponseControl&) { - if (!request.headers.contains("Authorization"_ba)) { - response.status = 401; - response.headers.insert("WWW-Authenticate: "_ba, "Basic realm=\"secret_place\""_ba); - } else { - response.status = 200; - } - serverSideRequest = request; // store for checking later the 'Authorization' header value - }); - - ReplyPtr reply(manager.get(factory.createRequest())); - QTRY_VERIFY(reply.get()->isFinished()); - QVERIFY(reply.get()->isSuccess()); - QCOMPARE(reply.get()->httpStatus(), 200); - QCOMPARE(serverSideRequest.headers["Authorization"_ba], "Basic YV91c2VyOmFfcGFzc3dvcmQ="_ba); - - // Verify that debug output does not contain password - QString debugOutput; - QDebug debug(&debugOutput); - debug << factory; - QVERIFY(debugOutput.contains("password = (is set)")); - QVERIFY(!debugOutput.contains(password)); -} - #define VERIFY_HTTP_ERROR_STATUS(STATUS) \ +{ \ serverSideResponse.status = STATUS; \ - reply = manager.get(request); \ - QObject::connect(reply, &QRestReply::errorOccurred, this, \ - [&](){ errorSignalReceived = true; }); \ - QTRY_VERIFY(reply->isFinished()); \ - QVERIFY(!errorSignalReceived); \ - QVERIFY(!reply->hasError()); \ - QCOMPARE(reply->httpStatus(), serverSideResponse.status); \ - QCOMPARE(reply->error(), QNetworkReply::NetworkError::NoError); \ - QVERIFY(!reply->isSuccess()); \ - reply->deleteLater(); \ + QRestReply restReply(manager.get(request)); \ + QTRY_VERIFY(restReply.networkReply()->isFinished()); \ + QVERIFY(!restReply.hasError()); \ + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); \ + QCOMPARE(restReply.error(), QNetworkReply::NetworkError::NoError); \ + QVERIFY(!restReply.isSuccess()); \ + restReply.networkReply()->deleteLater(); \ +} \ void tst_QRestAccessManager::errors() { // Tests the distinction between HTTP and other (network/protocol) errors - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *reply = nullptr; - bool errorSignalReceived = false; HttpData serverSideResponse; // The response data the server responds with server.setHandler([&](HttpData, HttpData &response, ResponseControl &) { @@ -650,48 +497,45 @@ void tst_QRestAccessManager::errors() VERIFY_HTTP_ERROR_STATUS(504) // QNetworkReply::UnknownServerError VERIFY_HTTP_ERROR_STATUS(505) // QNetworkReply::UnknownServerError - // Test that actual network/protocol errors come through - reply = manager.get({}); // Empty url - QObject::connect(reply, &QRestReply::errorOccurred, this, [&](){ errorSignalReceived = true; }); - QTRY_VERIFY(reply->isFinished()); - QTRY_VERIFY(errorSignalReceived); - QVERIFY(reply->hasError()); - QVERIFY(!reply->isSuccess()); - QCOMPARE(reply->error(), QNetworkReply::ProtocolUnknownError); - reply->deleteLater(); - errorSignalReceived = false; + { + // Test that actual network/protocol errors come through + QRestReply restReply(manager.get({})); // Empty url + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::ProtocolUnknownError); + restReply.networkReply()->deleteLater(); + } - reply = manager.get(QNetworkRequest{{"http://non-existent.foo.bar.test"}}); - QObject::connect(reply, &QRestReply::errorOccurred, this, [&](){ errorSignalReceived = true; }); - QTRY_VERIFY(reply->isFinished()); - QTRY_VERIFY(errorSignalReceived); - QVERIFY(reply->hasError()); - QVERIFY(!reply->isSuccess()); - QCOMPARE(reply->error(), QNetworkReply::HostNotFoundError); - reply->deleteLater(); - errorSignalReceived = false; + { + QRestReply restReply(manager.get(QNetworkRequest{{"http://non-existent.foo.bar.test"}})); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::HostNotFoundError); + restReply.networkReply()->deleteLater(); + } - reply = manager.get(request); - QObject::connect(reply, &QRestReply::errorOccurred, this, [&](){ errorSignalReceived = true; }); - reply->abort(); - QTRY_VERIFY(reply->isFinished()); - QTRY_VERIFY(errorSignalReceived); - QVERIFY(reply->hasError()); - QVERIFY(!reply->isSuccess()); - QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); - reply->deleteLater(); - errorSignalReceived = false; + { + QRestReply restReply(manager.get(request)); + restReply.networkReply()->abort(); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QVERIFY(restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + QCOMPARE(restReply.error(), QNetworkReply::OperationCanceledError); + restReply.networkReply()->deleteLater(); + } } void tst_QRestAccessManager::body() { // Test using QRestReply::body() data accessor - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; HttpData serverSideRequest; // The request data the server received HttpData serverSideResponse; // The response data the server responds with @@ -700,46 +544,57 @@ void tst_QRestAccessManager::body() response = serverSideResponse; }); - serverSideResponse.status = 200; - serverSideResponse.body = "some_data"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - QCOMPARE(replyFromServer->body(), serverSideResponse.body); - QCOMPARE(replyFromServer->httpStatus(), serverSideResponse.status); - QVERIFY(!replyFromServer->hasError()); - QVERIFY(replyFromServer->isSuccess()); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + serverSideResponse.status = 200; + serverSideResponse.body = "some_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.body(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } - serverSideResponse.body = ""_ba; // Empty - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - QCOMPARE(replyFromServer->body(), serverSideResponse.body); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + serverSideResponse.status = 200; + serverSideResponse.body = ""_ba; // Empty + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.body(), serverSideResponse.body); + networkReply->deleteLater(); + networkReply = nullptr; + } - serverSideResponse.status = 500; - serverSideResponse.body = "some_other_data"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - QCOMPARE(replyFromServer->body(), serverSideResponse.body); - QCOMPARE(replyFromServer->httpStatus(), serverSideResponse.status); - QVERIFY(!replyFromServer->hasError()); - QVERIFY(!replyFromServer->isSuccess()); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + serverSideResponse.status = 500; + serverSideResponse.body = "some_other_data"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + QCOMPARE(restReply.body(), serverSideResponse.body); + QCOMPARE(restReply.httpStatus(), serverSideResponse.status); + QVERIFY(!restReply.hasError()); + QVERIFY(!restReply.isSuccess()); + networkReply->deleteLater(); + networkReply = nullptr; + } } void tst_QRestAccessManager::json() { // Test using QRestReply::json() and jsonArray() data accessors - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; QJsonDocument responseJsonDocument; + std::optional json; QJsonParseError parseError; HttpData serverSideRequest; // The request data the server received @@ -750,95 +605,98 @@ void tst_QRestAccessManager::json() response = serverSideResponse; }); - // Test receiving valid json object - serverSideResponse.body = "{\"key1\":\"value1\",""\"key2\":\"value2\"}\n"_ba; - replyFromServer = manager.get(request); - // Read unfinished reply - QVERIFY(!replyFromServer->isFinished()); - QTest::ignoreMessage(QtWarningMsg, "Attempt to read json() of an unfinished reply, ignoring."); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; // Reset to impossible value - QVERIFY(!replyFromServer->json(&parseError)); - QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); - // Read finished reply - QTRY_VERIFY(replyFromServer->isFinished()); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - std::optional json = replyFromServer->json(&parseError); - QVERIFY(json); - QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); - responseJsonDocument = *json; - QVERIFY(responseJsonDocument.isObject()); - QCOMPARE(responseJsonDocument["key1"], "value1"); - QCOMPARE(responseJsonDocument["key2"], "value2"); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + // Test receiving valid json object + serverSideResponse.body = "{\"key1\":\"value1\",""\"key2\":\"value2\"}\n"_ba; + networkReply = manager.get(request); + // Read unfinished reply + QVERIFY(!networkReply->isFinished()); + QTest::ignoreMessage(QtWarningMsg, "Attempt to read json() of an unfinished reply, ignoring."); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; // Reset to impossible value + QRestReply restReply(networkReply); + QVERIFY(!restReply.json(&parseError)); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + // Read finished reply + QTRY_VERIFY(networkReply->isFinished()); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.json(&parseError); + QVERIFY(json); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isObject()); + QCOMPARE(responseJsonDocument["key1"], "value1"); + QCOMPARE(responseJsonDocument["key2"], "value2"); + networkReply->deleteLater(); + networkReply = nullptr; + } - // Test receiving an invalid json object - serverSideResponse.body = "foobar"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - QVERIFY(!replyFromServer->json(&parseError).has_value()); // std::nullopt returned - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); - QCOMPARE_GT(parseError.offset, 0); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + // Test receiving an invalid json object + serverSideResponse.body = "foobar"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + QVERIFY(!restReply.json(&parseError).has_value()); // std::nullopt returned + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); + QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); + QCOMPARE_GT(parseError.offset, 0); + networkReply->deleteLater(); + networkReply = nullptr; + } - // Test receiving valid json array - serverSideResponse.body = "[\"foo\", \"bar\"]\n"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - json = replyFromServer->json(&parseError); - QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); - QVERIFY(json); - responseJsonDocument = *json; - QVERIFY(responseJsonDocument.isArray()); - QCOMPARE(responseJsonDocument.array().size(), 2); - QCOMPARE(responseJsonDocument[0].toString(), "foo"_L1); - QCOMPARE(responseJsonDocument[1].toString(), "bar"_L1); - replyFromServer->deleteLater(); - replyFromServer = nullptr; - - // Test receiving an invalid json array - serverSideResponse.body = "foobar"_ba; - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - parseError.error = QJsonParseError::ParseError::DocumentTooLarge; - QVERIFY(!replyFromServer->json(&parseError).has_value()); // std::nullopt returned - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::NoError); - QCOMPARE_NE(parseError.error, QJsonParseError::ParseError::DocumentTooLarge); - QCOMPARE_GT(parseError.offset, 0); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + // Test receiving valid json array + serverSideResponse.body = "[\"foo\", \"bar\"]\n"_ba; + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + parseError.error = QJsonParseError::ParseError::DocumentTooLarge; + json = restReply.json(&parseError); + QCOMPARE(parseError.error, QJsonParseError::ParseError::NoError); + QVERIFY(json); + responseJsonDocument = *json; + QVERIFY(responseJsonDocument.isArray()); + QCOMPARE(responseJsonDocument.array().size(), 2); + QCOMPARE(responseJsonDocument[0].toString(), "foo"_L1); + QCOMPARE(responseJsonDocument[1].toString(), "bar"_L1); + networkReply->deleteLater(); + networkReply = nullptr; + } } #define VERIFY_TEXT_REPLY_OK \ - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); \ - QTRY_VERIFY(replyFromServer); \ - responseString = replyFromServer->text(); \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ + QRestReply restReply(networkReply); \ + responseString = restReply.text(); \ QCOMPARE(responseString, sourceString); \ - replyFromServer->deleteLater(); \ - replyFromServer = nullptr; \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} #define VERIFY_TEXT_REPLY_ERROR(WARNING_MESSAGE) \ - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); \ - QTRY_VERIFY(replyFromServer); \ +{ \ + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); \ + QTRY_VERIFY(networkReply); \ QTest::ignoreMessage(QtWarningMsg, WARNING_MESSAGE); \ - responseString = replyFromServer->text(); \ + QRestReply restReply(networkReply); \ + responseString = restReply.text(); \ QVERIFY(responseString.isEmpty()); \ - replyFromServer->deleteLater(); \ - replyFromServer = nullptr; \ + networkReply->deleteLater(); \ + networkReply = nullptr; \ +} void tst_QRestAccessManager::text() { // Test using QRestReply::text() data accessor with various text encodings - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); QNetworkRequest request(server.url()); - QRestReply *replyFromServer = nullptr; + QNetworkReply *networkReply = nullptr; QJsonObject responseJsonObject; QStringEncoder encUTF8("UTF-8"); @@ -889,15 +747,18 @@ void tst_QRestAccessManager::text() serverSideResponse.body = encUTF32(sourceString); VERIFY_TEXT_REPLY_OK; - // Unsuccessful UTF-32, wrong encoding indicated (indicated charset UTF-32 but data is UTF-8) - serverSideResponse.headers.insert("Content-Type:"_ba, "text/plain; charset=UTF-32"_ba); - serverSideResponse.body = encUTF8(sourceString); - manager.get(request, this, [&](QRestReply *reply) { replyFromServer = reply; }); - QTRY_VERIFY(replyFromServer); - responseString = replyFromServer->text(); - QCOMPARE_NE(responseString, sourceString); - replyFromServer->deleteLater(); - replyFromServer = nullptr; + { + // Unsuccessful UTF-32, wrong encoding indicated (indicated UTF-32 but data is UTF-8) + serverSideResponse.headers.insert("Content-Type:"_ba, "text/plain; charset=UTF-32"_ba); + serverSideResponse.body = encUTF8(sourceString); + manager.get(request, this, [&](QRestReply &reply) { networkReply = reply.networkReply(); }); + QTRY_VERIFY(networkReply); + QRestReply restReply(networkReply); + responseString = restReply.text(); + QCOMPARE_NE(responseString, sourceString); + networkReply->deleteLater(); + networkReply = nullptr; + } // Unsupported encoding serverSideResponse.headers.insert("Content-Type:"_ba, "text/plain; charset=foo"_ba); @@ -913,10 +774,11 @@ void tst_QRestAccessManager::text() void tst_QRestAccessManager::textStreaming() { // Tests textual data received in chunks - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); + QNetworkAccessManager qnam; + QRestAccessManager manager(&qnam); HttpTestServer server; QTRY_VERIFY(server.isListening()); + QNetworkRequest request(server.url()); // Create long text data const QString expectedData = u"사랑abcd€fghiklmnΩpqrstuvwx愛사랑A사랑BCD€FGHIJKLMNΩPQRsTUVWXYZ愛"_s; @@ -935,143 +797,37 @@ void tst_QRestAccessManager::textStreaming() control.responseChunkSize = 5; // tell testserver to send data in chunks of this size }); - QNetworkRequest request(server.url()); - QRestReply *reply = manager.get(request); - QObject::connect(reply, &QRestReply::readyRead, this, [&](QRestReply *reply) { - cumulativeReceivedText += reply->text(); - // Tell testserver that test is ready for next chunk - responseControl->readyForNextChunk = true; - }); - QTRY_VERIFY(reply->isFinished()); - QCOMPARE(cumulativeReceivedText, expectedData); + { + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + cumulativeReceivedText += restReply.text(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, expectedData); + restReply.networkReply()->deleteLater(); + } - cumulativeReceivedText.clear(); - // Broken UTF-8 characters after first five ok characters - serverSideResponse.body = - "12345"_ba + "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0" + "abcde"_ba; - reply = manager.get(request); - QObject::connect(reply, &QRestReply::readyRead, this, [&](QRestReply *reply) { - static bool firstTime = true; - if (!firstTime) // First text part is without warnings - QTest::ignoreMessage(QtWarningMsg, "text() Decoding error occurred"); - firstTime = false; - cumulativeReceivedText += reply->text(); - // Tell testserver that test is ready for next chunk - responseControl->readyForNextChunk = true; - }); - QTRY_VERIFY(reply->isFinished()); - QCOMPARE(cumulativeReceivedText, "12345"_ba); -} - -void tst_QRestAccessManager::download() -{ - // Test case where data is received in chunks. - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - HttpData serverSideResponse; // The response data the server responds with - constexpr qsizetype dataSize = 1 * 1024 * 1024; // 1 MB - QByteArray expectedData{dataSize, 0}; - for (qsizetype i = 0; i < dataSize; ++i) // initialize the data we download - expectedData[i] = i % 100; - QByteArray cumulativeReceivedData; - qsizetype cumulativeReceivedBytesAvailable = 0; - - serverSideResponse.body = expectedData; - ResponseControl *responseControl = nullptr; - serverSideResponse.status = 200; - // Set content-length header so that underlying QNAM is able to report bytesTotal correctly - serverSideResponse.headers.insert("Content-Length: ", - QString::number(expectedData.size()).toLatin1()); - server.setHandler([&](HttpData, HttpData &response, ResponseControl &control) { - response = serverSideResponse; - responseControl = &control; // store for later - control.responseChunkSize = 1024; // tell testserver to send data in chunks of this size - }); - - QRestReply* reply = manager.get(request, this, [&responseControl](QRestReply */*reply*/){ - responseControl = nullptr; // all finished, no more need for controlling the response - }); - - QObject::connect(reply, &QRestReply::readyRead, this, [&](QRestReply *reply) { - static bool testOnce = true; - if (!reply->isFinished() && testOnce) { - // Test once that reading json of an unfinished reply will not work - testOnce = false; - QTest::ignoreMessage(QtWarningMsg, "Attempt to read json() of an unfinished" - " reply, ignoring."); - reply->json(); - } - - cumulativeReceivedBytesAvailable += reply->bytesAvailable(); - cumulativeReceivedData += reply->body(); - // Tell testserver that test is ready for next chunk - responseControl->readyForNextChunk = true; - }); - - qint64 totalBytes = 0; - qint64 receivedBytes = 0; - QObject::connect(reply, &QRestReply::downloadProgress, this, - [&](qint64 bytesReceived, qint64 bytesTotal) { - if (totalBytes == 0 && bytesTotal > 0) - totalBytes = bytesTotal; - receivedBytes = bytesReceived; - }); - QTRY_VERIFY(reply->isFinished()); - reply->deleteLater(); - reply = nullptr; - // Checks specific for readyRead() and bytesAvailable() - QCOMPARE(cumulativeReceivedData, expectedData); - QCOMPARE(cumulativeReceivedBytesAvailable, expectedData.size()); - // Checks specific for downloadProgress() - QCOMPARE(totalBytes, expectedData.size()); - QCOMPARE(receivedBytes, expectedData.size()); -} - -void tst_QRestAccessManager::upload() -{ - // This test tests uploadProgress signal - QRestAccessManager manager; - manager.setDeletesRepliesOnFinished(false); - HttpTestServer server; - QTRY_VERIFY(server.isListening()); - QNetworkRequest request(server.url()); - request.setRawHeader("Content-Type"_ba, "text/plain"); // To silence missing content-type warn - QByteArray expectedData{1 * 1024 * 1024, 0}; // 1 MB - server.setHandler([&](HttpData, HttpData &, ResponseControl &) {}); - - QRestReply* reply = manager.post(request, expectedData); - QSignalSpy uploadProgressSpy(reply, &QRestReply::uploadProgress); - QTRY_VERIFY(reply->isFinished()); - QVERIFY(!uploadProgressSpy.isEmpty()); - reply->deleteLater(); - - // Check that bytesTotal is correct already in the first signal - const QList first = uploadProgressSpy.first(); - QCOMPARE(first.size(), 3); - QCOMPARE(first.at(1).toLongLong(), expectedData.size()); - - // Check that we sent all bytes - const QList last = uploadProgressSpy.last(); - QCOMPARE(last.size(), 3); - QEXPECT_FAIL("", "Fails due to QTBUG-44782", Continue); - QCOMPARE(last.at(0).toLongLong(), expectedData.size()); -} - -void tst_QRestAccessManager::timeout() -{ - constexpr auto defaultTimeout = 0ms; - constexpr auto timeout = 150ms; - - QRestAccessManager manager; - QCOMPARE(manager.transferTimeout(), defaultTimeout); - QCOMPARE(manager.networkAccessManager()->transferTimeoutAsDuration(), defaultTimeout); - - manager.setTransferTimeout(timeout); - QCOMPARE(manager.transferTimeout(), timeout); - QCOMPARE(manager.networkAccessManager()->transferTimeoutAsDuration(), timeout); + { + cumulativeReceivedText.clear(); + // Broken UTF-8 characters after first five ok characters + serverSideResponse.body = + "12345"_ba + "\xF0\x28\x8C\x28\xA0\xB0\xC0\xD0" + "abcde"_ba; + QRestReply restReply(manager.get(request)); + QObject::connect(restReply.networkReply(), &QNetworkReply::readyRead, this, [&]() { + static bool firstTime = true; + if (!firstTime) // First text part is without warnings + QTest::ignoreMessage(QtWarningMsg, "text() Decoding error occurred"); + firstTime = false; + cumulativeReceivedText += restReply.text(); + // Tell testserver that test is ready for next chunk + responseControl->readyForNextChunk = true; + }); + QTRY_VERIFY(restReply.networkReply()->isFinished()); + QCOMPARE(cumulativeReceivedText, "12345"_ba); + restReply.networkReply()->deleteLater(); + } } QTEST_MAIN(tst_QRestAccessManager)