summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@qt.io>2023-06-12 11:23:19 +0300
committerJuha Vuolle <juha.vuolle@qt.io>2023-12-08 15:53:33 +0200
commite560adef213301318dcc13d4db155624846e0420 (patch)
tree237ffa17c837ee0f270885641b781d9bb47c6fd6 /src/network/access
parentf587ba1036164691a0981897397bdcc8f3472438 (diff)
Add REST client convenience wrappers
[ChangeLog][QtNetwork][QRestAccessManager] Added new convenience classes QRestAccessManager and QRestReply for typical RESTful client application usage Task-number: QTBUG-114637 Task-number: QTBUG-114701 Change-Id: I65057e56bf27f365b54bfd528565efd5f09386aa Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Mate Barany <mate.barany@qt.io> Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/qrestaccessmanager.cpp819
-rw-r--r--src/network/access/qrestaccessmanager.h110
-rw-r--r--src/network/access/qrestaccessmanager_p.h87
-rw-r--r--src/network/access/qrestreply.cpp364
-rw-r--r--src/network/access/qrestreply.h55
-rw-r--r--src/network/access/qrestreply_p.h39
6 files changed, 1474 insertions, 0 deletions
diff --git a/src/network/access/qrestaccessmanager.cpp b/src/network/access/qrestaccessmanager.cpp
new file mode 100644
index 0000000000..c4efa846a2
--- /dev/null
+++ b/src/network/access/qrestaccessmanager.cpp
@@ -0,0 +1,819 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrestaccessmanager.h"
+#include "qrestaccessmanager_p.h"
+#include "qrestreply.h"
+
+#include <QtNetwork/qhttpmultipart.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkreply.h>
+
+#if QT_CONFIG(ssl)
+#include <QtNetwork/qsslsocket.h>
+#endif
+
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qthread.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcQrest, "qt.network.access.rest")
+
+/*!
+
+ \class QRestAccessManager
+ \brief The QRestAccessManager is a networking convenience class for RESTful
+ client applications.
+ \since 6.7
+
+ \ingroup network
+ \inmodule QtNetwork
+ \reentrant
+
+ QRestAccessManager provides a networking API for typical REST client
+ applications. It provides the means to issue HTTP requests such as GET
+ and POST. The responses to these requests can be handled with traditional
+ Qt signal and slot mechanisms, as well as by providing callbacks
+ directly - see \l {Issuing Network Requests and Handling Replies}.
+
+ The class is a wrapper on top of QNetworkAccessManager, and it both amends
+ convenience methods and omits typically less used features. These
+ features are still accessible by configuring the underlying
+ QNetworkAccessManager directly. QRestAccessManager is closely related to
+ the QRestReply class, which it returns when issuing network requests.
+
+ QRestAccessManager and related QRestReply classes can only be used in the
+ thread they live in. For further information see
+ \l {QObject#Thread Affinity}{QObject thread affinity} documentation.
+
+ \section1 Issuing Network Requests and Handling Replies
+
+ Network requests are initiated with a function call corresponding to
+ the desired HTTP method, such as \c get() and \c post().
+
+ \section2 Using Signals and Slots
+
+ The function returns a QRestReply* object, whose signals can be used
+ to follow up on the completion of the request in a traditional
+ Qt-signals-and-slots way.
+
+ Here's an example of how you could send a GET request and handle the
+ response:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 0
+
+ \section2 Using Callbacks and Context Objects
+
+ The functions also take a context object of QObject (subclass) type
+ and a callback function as parameters. The callback takes one QRestReply*
+ as a parameter. The callback can be any callable, incl. a
+ pointer-to-member-function..
+
+ These callbacks are invoked when the QRestReply has finished processing
+ (also in the case the processing finished due to an error).
+
+ The context object can be \c nullptr, although, generally speaking,
+ this is discouraged. Using a valid context object ensures that if the
+ context object is destroyed during request processing, the callback will
+ not be called. Stray callbacks which access a destroyed context is a source
+ of application misbehavior.
+
+ Here's an example of how you could send a GET request and check the
+ response:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 1
+
+ Many of the functions take in data for sending to a server. The data is
+ supplied as the second parameter after the request.
+
+ Here's an example of how you could send a POST request and check the
+ response:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 2
+
+ \section2 Supported data types
+
+ The following table summarizes the methods and the supported data types.
+ \c X means support.
+
+ \table
+ \header
+ \li Data type
+ \li \c get()
+ \li \c post()
+ \li \c put()
+ \li \c head()
+ \li \c deleteResource()
+ \row
+ \li No data
+ \li X
+ \li -
+ \li -
+ \li X
+ \li X
+ \row
+ \li QByteArray
+ \li X
+ \li X
+ \li X
+ \li -
+ \li -
+ \row
+ \li QJsonObject *)
+ \li X
+ \li X
+ \li X
+ \li -
+ \li -
+ \row
+ \li QJsonArray *)
+ \li -
+ \li X
+ \li X
+ \li -
+ \li -
+ \row
+ \li QVariantMap **)
+ \li -
+ \li X
+ \li X
+ \li -
+ \li -
+ \row
+ \li QHttpMultiPart
+ \li -
+ \li X
+ \li X
+ \li -
+ \li -
+ \row
+ \li QIODevice
+ \li X
+ \li X
+ \li X
+ \li -
+ \li -
+ \endtable
+
+ *) QJsonObject and QJsonArray are sent in \l QJsonDocument::Compact format,
+ and the \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply, QNetworkRequestFactory, QNetworkAccessManager
+*/
+
+/*!
+ \fn void QRestAccessManager::authenticationRequired(QRestReply *reply,
+ QAuthenticator *authenticator)
+
+ This signal is emitted when the final server requires authentication.
+ The authentication relates to the provided \a reply instance, and any
+ credentials are to be filled in the provided \a authenticator instance.
+
+ See \l QNetworkAccessManager::authenticationRequired() for details.
+*/
+
+/*!
+ \fn void QRestAccessManager::proxyAuthenticationRequired(
+ const QNetworkProxy &proxy, QAuthenticator *authenticator)
+
+ This signal is emitted when a proxy authentication requires action.
+ The proxy details are in \a proxy object, and any credentials are
+ to be filled in the provided \a authenticator object.
+
+ See \l QNetworkAccessManager::proxyAuthenticationRequired() for details.
+*/
+
+/*!
+ \fn void QRestAccessManager::requestFinished(QRestReply *reply)
+
+ This signal is emitted whenever a pending network reply is
+ finished. \a reply parameter will contain a pointer to the
+ reply that has just finished. This signal is emitted in tandem
+ with the QRestReply::finished() signal. QRestReply provides
+ functions for checking the status of the request, as well as for
+ acquiring any received data.
+
+ \note Do not delete \a reply object in the slot connected to this
+ signal. Use deleteLater() if needed. See also \l deletesRepliesOnFinished().
+
+ \sa QRestReply::finished()
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::get(
+ const QNetworkRequest &request,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP GET} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 3
+
+ Alternatively the signals of the returned QRestReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \sa QRestReply, QRestReply::finished(),
+ QRestAccessManager::requestFinished()
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::get(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP GET} based on \a request and provided \a data.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 4
+
+ Alternatively the signals of the returned QRestReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \sa QRestReply, QRestReply::finished(),
+ QRestAccessManager::requestFinished()
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::get(
+ const QNetworkRequest &request, const QJsonObject &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::get(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QJsonObject &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP POST} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 5
+
+ Alternatively, the signals of the returned QRestReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ The \c post() method always requires \a data parameter. The following
+ data types are supported:
+ \list
+ \li QByteArray
+ \li QJsonObject *)
+ \li QJsonArray *)
+ \li QVariantMap **)
+ \li QHttpMultiPart*
+ \li QIODevice*
+ \endlist
+
+ *) Sent in \l QJsonDocument::Compact format, and the
+ \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply, QRestReply::finished(),
+ QRestAccessManager::requestFinished()
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QJsonArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QVariantMap &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::post(
+ const QNetworkRequest &request, QHttpMultiPart *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::post(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QJsonObject &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP PUT} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 6
+
+ Alternatively the signals of the returned QRestReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ The \c put() method always requires \a data parameter. The following
+ data types are supported:
+ \list
+ \li QByteArray
+ \li QJsonObject *)
+ \li QJsonArray *)
+ \li QVariantMap **)
+ \li QHttpMultiPart*
+ \li QIODevice*
+ \endlist
+
+ *) Sent in \l QJsonDocument::Compact format, and the
+ \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply, QRestReply::finished(), QRestAccessManager::requestFinished()
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QJsonArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QVariantMap &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::put(
+ const QNetworkRequest &request, QHttpMultiPart *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::put(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::head(
+ const QNetworkRequest &request,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP HEAD} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 7
+
+ Alternatively the signals of the returned QRestReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \c head() request does not support providing data.
+
+ \sa QRestReply, QRestReply::finished(),
+ QRestAccessManager::requestFinished()
+*/
+
+/*!
+ \fn template<typename Functor, if_compatible_callback<Functor>> QRestReply *QRestAccessManager::deleteResource(
+ const QNetworkRequest &request,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP DELETE} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 8
+
+ Alternatively the signals of the returned QRestReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \c deleteResource() request does not support providing data.
+
+ \sa QRestReply, QRestReply::finished(),
+ QRestAccessManager::requestFinished()
+*/
+
+/*
+ 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)
+ : QObject(*new QRestAccessManagerPrivate, parent)
+{
+ Q_D(QRestAccessManager);
+ d->ensureNetworkAccessManager();
+}
+
+/*!
+ Destroys the QRestAccessManager object and frees up any
+ resources, including any unfinished QRestReply objects.
+*/
+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();
+}
+
+/*!
+ Returns the underlying QNetworkAccessManager instance. The instance
+ can be used for accessing less-frequently used features and configurations.
+
+ \sa QNetworkAccessManager
+*/
+QNetworkAccessManager *QRestAccessManager::networkAccessManager() const
+{
+ Q_D(const QRestAccessManager);
+ return d->qnam;
+}
+
+QRestAccessManagerPrivate::QRestAccessManagerPrivate()
+ = default;
+
+QRestAccessManagerPrivate::~QRestAccessManagerPrivate()
+{
+ if (!activeRequests.isEmpty()) {
+ qCWarning(lcQrest, "Access manager destroyed while %lld requests were still in progress",
+ qlonglong(activeRequests.size()));
+ }
+}
+
+QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ const QJsonObject &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto req, auto json) { return d->qnam->post(req, json); },
+ data, request, context, slot);
+}
+
+QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ const QJsonArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto req, auto json) { return d->qnam->post(req, json); },
+ data, request, context, slot);
+}
+
+QRestReply *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,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->post(request, data); }, context, slot);
+}
+
+QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ QHttpMultiPart *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->post(request, data); }, context, slot);
+}
+
+QRestReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ QIODevice *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->post(request, data); }, context, slot);
+}
+
+QRestReply *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,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->get(request, data); }, context, slot);
+}
+
+QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request,
+ const QJsonObject &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto req, auto json) { return d->qnam->get(req, json); },
+ data, request, context, slot);
+}
+
+QRestReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request,
+ QIODevice *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->get(request, data); }, context, slot);
+}
+
+QRestReply *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,
+ 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,
+ const QJsonObject &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto req, auto json) { return d->qnam->put(req, json); },
+ data, request, context, slot);
+}
+
+QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request,
+ const QJsonArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto req, auto json) { return d->qnam->put(req, json); },
+ data, request, context, slot);
+}
+
+QRestReply *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,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->put(request, data); }, context, slot);
+}
+
+QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request,
+ QHttpMultiPart *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->put(request, data); }, context, slot);
+}
+
+QRestReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, QIODevice *data,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&]() { return d->qnam->put(request, data); }, context, slot);
+}
+
+QRestReply *QRestAccessManagerPrivate::createActiveRequest(QNetworkReply *networkReply,
+ const QObject *contextObject,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_Q(QRestAccessManager);
+ Q_ASSERT(networkReply);
+ auto restReply = new QRestReply(networkReply, q);
+ QtPrivate::SlotObjSharedPtr slotPtr(QtPrivate::SlotObjUniquePtr{slot}); // adopts
+ activeRequests.insert(restReply, CallerInfo{contextObject, slotPtr});
+
+ // 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);
+ });
+ // Safe guard in case reply is destroyed before it's finished
+ QObject::connect(restReply, &QRestReply::destroyed, q, [restReply, this]() {
+ activeRequests.remove(restReply);
+ });
+ // 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);
+ });
+ }
+ return restReply;
+}
+
+void QRestAccessManagerPrivate::verifyThreadAffinity(const QObject *contextObject)
+{
+ Q_Q(QRestAccessManager);
+ if (QThread::currentThread() != q->thread()) {
+ qCWarning(lcQrest, "QRestAccessManager can only be called in the thread it belongs to");
+ Q_ASSERT(false);
+ }
+ if (contextObject && (contextObject->thread() != q->thread())) {
+ qCWarning(lcQrest, "QRestAccessManager: the context object must reside in the same thread");
+ Q_ASSERT(false);
+ }
+}
+
+void QRestAccessManagerPrivate::ensureNetworkAccessManager()
+{
+ 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
+ }
+}
+
+void QRestAccessManagerPrivate::handleReplyFinished(QRestReply *restReply)
+{
+ Q_Q(QRestAccessManager);
+
+ auto request = activeRequests.find(restReply);
+ if (request == activeRequests.end()) {
+ qCWarning(lcQrest, "Unexpected reply received, ignoring");
+ return;
+ }
+
+ CallerInfo caller = request.value();
+ 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
+ void *argv[] = { nullptr, &restReply };
+ QObject *context = caller.contextObject
+ ? const_cast<QObject*>(caller.contextObject) : 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
+
+#include "moc_qrestaccessmanager.cpp"
diff --git a/src/network/access/qrestaccessmanager.h b/src/network/access/qrestaccessmanager.h
new file mode 100644
index 0000000000..7b67486a2e
--- /dev/null
+++ b/src/network/access/qrestaccessmanager.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTACCESSMANAGER_H
+#define QRESTACCESSMANAGER_H
+
+#include <QtNetwork/qnetworkaccessmanager.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRestReply;
+
+#define QREST_METHOD_WITH_DATA(METHOD, DATA) \
+public: \
+template <typename Functor, if_compatible_callback<Functor> = true> \
+QRestReply *METHOD(const QNetworkRequest &request, DATA data, \
+ const ContextTypeForFunctor<Functor> *context, \
+ Functor &&callback) \
+{ \
+ return METHOD##WithDataImpl(request, data, context, \
+ QtPrivate::makeCallableObject<CallbackPrototype>(std::forward<Functor>(callback))); \
+} \
+QRestReply *METHOD(const QNetworkRequest &request, DATA data) \
+{ \
+ return METHOD##WithDataImpl(request, data, nullptr, nullptr); \
+} \
+private: \
+QRestReply *METHOD##WithDataImpl(const QNetworkRequest &request, DATA data, \
+ const QObject *context, QtPrivate::QSlotObjectBase *slot); \
+/* end */
+
+
+#define QREST_METHOD_NO_DATA(METHOD) \
+public: \
+template <typename Functor, if_compatible_callback<Functor> = true> \
+QRestReply *METHOD(const QNetworkRequest &request, \
+ const ContextTypeForFunctor<Functor> *context, \
+ Functor &&callback) \
+{ \
+ return METHOD##NoDataImpl(request, context, \
+ QtPrivate::makeCallableObject<CallbackPrototype>(std::forward<Functor>(callback))); \
+} \
+QRestReply *METHOD(const QNetworkRequest &request) \
+{ \
+ return METHOD##NoDataImpl(request, nullptr, nullptr); \
+} \
+private: \
+QRestReply *METHOD##NoDataImpl(const QNetworkRequest &request, \
+ const QObject *context, QtPrivate::QSlotObjectBase *slot); \
+/* end */
+
+class QRestAccessManagerPrivate;
+class Q_NETWORK_EXPORT QRestAccessManager : public QObject
+{
+ Q_OBJECT
+
+ using CallbackPrototype = void(*)(QRestReply*);
+ template <typename Functor>
+ using ContextTypeForFunctor = typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType;
+ template <typename Functor>
+ using if_compatible_callback = std::enable_if_t<
+ QtPrivate::AreFunctionsCompatible<CallbackPrototype, Functor>::value, bool>;
+public:
+ explicit QRestAccessManager(QObject *parent = nullptr);
+ ~QRestAccessManager() override;
+
+ QNetworkAccessManager *networkAccessManager() const;
+
+ bool deletesRepliesOnFinished() const;
+ void setDeletesRepliesOnFinished(bool autoDelete);
+
+ void abortRequests();
+
+ QREST_METHOD_NO_DATA(deleteResource)
+ QREST_METHOD_NO_DATA(head)
+ QREST_METHOD_NO_DATA(get)
+ QREST_METHOD_WITH_DATA(get, const QByteArray &)
+ QREST_METHOD_WITH_DATA(get, const QJsonObject &)
+ QREST_METHOD_WITH_DATA(get, QIODevice *)
+ QREST_METHOD_WITH_DATA(post, const QJsonObject &)
+ QREST_METHOD_WITH_DATA(post, const QJsonArray &)
+ QREST_METHOD_WITH_DATA(post, const QVariantMap &)
+ QREST_METHOD_WITH_DATA(post, const QByteArray &)
+ QREST_METHOD_WITH_DATA(post, QHttpMultiPart *)
+ QREST_METHOD_WITH_DATA(post, QIODevice *)
+ QREST_METHOD_WITH_DATA(put, const QJsonObject &)
+ QREST_METHOD_WITH_DATA(put, const QJsonArray &)
+ QREST_METHOD_WITH_DATA(put, const QVariantMap &)
+ QREST_METHOD_WITH_DATA(put, const QByteArray &)
+ QREST_METHOD_WITH_DATA(put, QHttpMultiPart *)
+ QREST_METHOD_WITH_DATA(put, QIODevice *)
+
+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:
+ Q_DECLARE_PRIVATE(QRestAccessManager)
+ Q_DISABLE_COPY(QRestAccessManager)
+};
+
+#undef QREST_METHOD_NO_DATA
+#undef QREST_METHOD_WITH_DATA
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qrestaccessmanager_p.h b/src/network/access/qrestaccessmanager_p.h
new file mode 100644
index 0000000000..91c6f89dd0
--- /dev/null
+++ b/src/network/access/qrestaccessmanager_p.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTACCESSMANAGER_P_H
+#define QRESTACCESSMANAGER_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 "qrestaccessmanager.h"
+#include "private/qobject_p.h"
+
+#include <QtNetwork/qnetworkaccessmanager.h>
+
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRestReply;
+class QRestAccessManagerPrivate : public QObjectPrivate
+{
+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);
+
+ template<typename Functor>
+ QRestReply *executeRequest(Functor requestOperation,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+ {
+ verifyThreadAffinity(context);
+ QNetworkReply *reply = requestOperation();
+ return createActiveRequest(reply, context, slot);
+ }
+
+ template<typename Functor, typename Json>
+ QRestReply *executeRequest(Functor requestOperation, Json jsonData,
+ const QNetworkRequest &request,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+ {
+ verifyThreadAffinity(context);
+ QNetworkRequest req(request);
+ if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
+ req.setHeader(QNetworkRequest::ContentTypeHeader,
+ QLatin1StringView{"application/json"});
+ }
+ QJsonDocument json(jsonData);
+ QNetworkReply *reply = requestOperation(req, json.toJson(QJsonDocument::Compact));
+ return createActiveRequest(reply, context, slot);
+ }
+
+ void verifyThreadAffinity(const QObject *contextObject);
+
+ struct CallerInfo {
+ const QObject *contextObject = nullptr;
+ QtPrivate::SlotObjSharedPtr slot;
+ };
+ QHash<QRestReply*, CallerInfo> activeRequests;
+
+ QNetworkAccessManager *qnam = nullptr;
+ bool deletesRepliesOnFinished = true;
+ Q_DECLARE_PUBLIC(QRestAccessManager)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qrestreply.cpp b/src/network/access/qrestreply.cpp
new file mode 100644
index 0000000000..efff394f83
--- /dev/null
+++ b/src/network/access/qrestreply.cpp
@@ -0,0 +1,364 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrestreply.h"
+#include "qrestreply_p.h"
+
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qlatin1stringmatcher.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstringconverter.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+Q_DECLARE_LOGGING_CATEGORY(lcQrest)
+
+/*!
+ \class QRestReply
+ \since 6.7
+ \brief QRestReply is the class for following up the requests sent with
+ QRestAccessManager.
+
+ \reentrant
+ \ingroup network
+ \inmodule QtNetwork
+
+ QRestReply is a convenience class for typical RESTful client
+ applications. It wraps the more detailed QNetworkReply and provides
+ convenience methods for data and status handling.
+
+ \sa QRestAccessManager, QNetworkReply
+*/
+
+/*!
+ \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)
+{
+ Q_D(QRestReply);
+ Q_ASSERT(reply);
+ d->networkReply = reply;
+ // Reparent so that destruction of QRestReply destroys QNetworkReply
+ reply->setParent(this);
+}
+
+/*!
+ Destroys this QRestReply object.
+
+ \sa abort()
+*/
+QRestReply::~QRestReply()
+ = default;
+
+/*!
+ 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();
+}
+
+/*!
+ Returns the received data as a QJsonObject. Requires the reply to be
+ finished.
+
+ The returned value is wrapped in \c std::optional. If the conversion
+ from the received data fails (empty data or JSON parsing error),
+ \c std::nullopt is returned.
+
+ Calling this function consumes the received data, and any further calls
+ to get response data will return empty.
+
+ This function returns \c {std::nullopt} and will not consume
+ any data if the reply is not finished.
+
+ \sa jsonArray(), body(), text(), finished(), isFinished()
+*/
+std::optional<QJsonObject> QRestReply::json()
+{
+ Q_D(QRestReply);
+ if (!isFinished()) {
+ qCWarning(lcQrest, "Attempt to read json() of an unfinished reply, ignoring.");
+ return std::nullopt;
+ }
+ const QJsonDocument json = d->replyDataToJson();
+ return json.isObject() ? std::optional{json.object()} : std::nullopt;
+}
+
+/*!
+ Returns the received data as a QJsonArray. Requires the reply to be
+ finished.
+
+ The returned value is wrapped in \c std::optional. If the conversion
+ from the received data fails (empty data or JSON parsing error),
+ \c std::nullopt is returned.
+
+ Calling this function consumes the received data, and any further calls
+ to get response data will return empty.
+
+ This function returns \c {std::nullopt} and will not consume
+ any data if the reply is not finished.
+
+ \sa json(), body(), text(), finished(), isFinished()
+*/
+std::optional<QJsonArray> QRestReply::jsonArray()
+{
+ Q_D(QRestReply);
+ if (!isFinished()) {
+ qCWarning(lcQrest, "Attempt to read jsonArray() of an unfinished reply, ignoring.");
+ return std::nullopt;
+ }
+ const QJsonDocument json = d->replyDataToJson();
+ return json.isArray() ? std::optional{json.array()} : std::nullopt;
+}
+
+/*!
+ Returns the received data as a QByteArray.
+
+ Calling this function consumes the data received so far, and any further
+ calls to get response data will return empty until further data has been
+ received.
+
+ \sa json(), text()
+*/
+QByteArray QRestReply::body()
+{
+ Q_D(QRestReply);
+ return d->networkReply->readAll();
+}
+
+/*!
+ Returns the received data as a QString. Requires the reply to be finished.
+
+ The received data is decoded into a QString (UTF-16). The decoding
+ uses the \e Content-Type header's \e charset parameter to determine the
+ source encoding, if available. If the encoding information is not
+ available or not supported by \l QStringConverter, UTF-8 is used as a
+ default.
+
+ Calling this function consumes the received data, and any further calls
+ to get response data will return empty.
+
+ This function returns a default-constructed value and will not consume
+ any data if the reply is not finished.
+
+ \sa json(), body(), isFinished(), finished()
+*/
+QString QRestReply::text()
+{
+ Q_D(QRestReply);
+ if (!isFinished()) {
+ qCWarning(lcQrest, "Attempt to read text() of an unfinished reply, ignoring.");
+ return {};
+ }
+ QByteArray data = d->networkReply->readAll();
+ if (data.isEmpty())
+ return {};
+
+ const QByteArray charset = d->contentCharset();
+ QStringDecoder decoder(charset);
+ if (!decoder.isValid()) { // the decoder may not support the mimetype's charset
+ qCWarning(lcQrest, "Charset \"%s\" is not supported, defaulting to UTF-8",
+ charset.constData());
+ decoder = QStringDecoder(QStringDecoder::Utf8);
+ }
+ return decoder(data);
+}
+
+/*!
+ Returns the HTTP status received in the server response.
+ The value is \e 0 if not available (the status line has not been received,
+ yet).
+
+ \note The HTTP status is reported as indicated by the received HTTP
+ response. It is possible that an error() occurs after receiving the status,
+ for instance due to network disconnection while receiving a long response.
+ These potential subsequent errors are not represented by the reported
+ HTTP status.
+
+ \sa isSuccess(), hasError(), error()
+*/
+int QRestReply::httpStatus() const
+{
+ Q_D(const QRestReply);
+ return d->networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+}
+
+/*!
+ \fn bool QRestReply::isSuccess() const
+
+ Returns whether the HTTP status is between 200..299 and no
+ further errors have occurred while receiving the response (for example
+ abrupt disconnection while receiving the body data). This function
+ is a convenient way to check whether the response is considered successful.
+
+ \sa httpStatus(), hasError(), error()
+*/
+
+/*!
+ Returns whether the HTTP status is between 200..299.
+
+ \sa isSuccess(), httpStatus(), hasError(), error()
+*/
+bool QRestReply::isHttpStatusSuccess() const
+{
+ const int status = httpStatus();
+ return status >= 200 && status < 300;
+}
+
+/*!
+ Returns whether an error has occurred. This includes errors such as
+ network and protocol errors, but excludes cases where the server
+ successfully responded with an HTTP error status (for example
+ \c {500 Internal Server Error}). Use \l httpStatus() or
+ \l isHttpStatusSuccess() to get the HTTP status information.
+
+ \sa httpStatus(), isSuccess(), error(), errorString()
+*/
+bool QRestReply::hasError() const
+{
+ Q_D(const QRestReply);
+ return d->hasNonHttpError();
+}
+
+/*!
+ Returns the last error, if any. The errors include
+ errors such as network and protocol errors, but exclude
+ cases when the server successfully responded with an HTTP status.
+
+ \sa httpStatus(), isSuccess(), hasError(), errorString()
+*/
+QNetworkReply::NetworkError QRestReply::error() const
+{
+ Q_D(const QRestReply);
+ if (!hasError())
+ return QNetworkReply::NetworkError::NoError;
+ return d->networkReply->error();
+}
+
+/*!
+ Returns a human-readable description of the last network error.
+
+ \sa httpStatus(), isSuccess(), hasError(), error()
+*/
+QString QRestReply::errorString() const
+{
+ Q_D(const QRestReply);
+ if (hasError())
+ return d->networkReply->errorString();
+ return {};
+}
+
+/*!
+ Returns whether the network request has finished.
+*/
+bool QRestReply::isFinished() const
+{
+ Q_D(const QRestReply);
+ return d->networkReply->isFinished();
+}
+
+QRestReplyPrivate::QRestReplyPrivate()
+ = default;
+
+QRestReplyPrivate::~QRestReplyPrivate()
+ = default;
+
+QByteArray QRestReplyPrivate::contentCharset() const
+{
+ // 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
+ // and RFC 2045 section 5.1
+ //
+ // text/plain; charset=utf-8
+ // 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"};
+
+ QList<QByteArray> parameters = contentTypeValue.split(';');
+ if (parameters.size() >= 2) { // Need at least one parameter in addition to the mimetype itself
+ parameters.removeFirst(); // Exclude the mimetype itself, only interested in parameters
+ QLatin1StringMatcher matcher("charset="_L1, Qt::CaseSensitivity::CaseInsensitive);
+ qsizetype matchIndex = -1;
+ for (auto &parameter : parameters) {
+ // Remove whitespaces and parantheses
+ const QByteArray curated = parameter.replace(" ", "").replace("\"","");
+ // Check for match
+ matchIndex = matcher.indexIn(QLatin1String(curated.constData()));
+ if (matchIndex >= 0) {
+ charset = curated.sliced(matchIndex + 8); // 8 is size of "charset="
+ break;
+ }
+ }
+ }
+ 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;
+}
+
+QJsonDocument QRestReplyPrivate::replyDataToJson()
+{
+ QJsonParseError parseError;
+ const QByteArray data = networkReply->readAll();
+ const QJsonDocument json = QJsonDocument::fromJson(data, &parseError);
+
+ if (parseError.error != QJsonParseError::NoError) {
+ qCDebug(lcQrest) << "Response data not JSON:" << parseError.errorString()
+ << "at" << parseError.offset << data;
+ }
+ return json;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qrestreply.cpp"
diff --git a/src/network/access/qrestreply.h b/src/network/access/qrestreply.h
new file mode 100644
index 0000000000..6e35b45003
--- /dev/null
+++ b/src/network/access/qrestreply.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTREPLY_H
+#define QRESTREPLY_H
+
+#include <QtNetwork/qnetworkreply.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRestReplyPrivate;
+class Q_NETWORK_EXPORT QRestReply : public QObject
+{
+ Q_OBJECT
+
+public:
+ ~QRestReply() override;
+
+ QNetworkReply *networkReply() const;
+
+ std::optional<QJsonObject> json();
+ std::optional<QJsonArray> jsonArray();
+ QByteArray body();
+ QString text();
+
+ bool isSuccess() const
+ {
+ return !hasError() && isHttpStatusSuccess();
+ }
+ int httpStatus() const;
+ bool isHttpStatusSuccess() const;
+
+ bool hasError() const;
+ QNetworkReply::NetworkError error() const;
+ QString errorString() const;
+
+ bool isFinished() const;
+
+public Q_SLOTS:
+ void abort();
+
+Q_SIGNALS:
+ void finished(QRestReply *reply);
+ void errorOccurred(QRestReply *reply);
+
+private:
+ friend class QRestAccessManagerPrivate;
+ explicit QRestReply(QNetworkReply *reply, QObject *parent = nullptr);
+ Q_DECLARE_PRIVATE(QRestReply)
+ Q_DISABLE_COPY_MOVE(QRestReply)
+};
+
+QT_END_NAMESPACE
+
+#endif // QRESTREPLY_H
diff --git a/src/network/access/qrestreply_p.h b/src/network/access/qrestreply_p.h
new file mode 100644
index 0000000000..4b156b3793
--- /dev/null
+++ b/src/network/access/qrestreply_p.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTREPLY_P_H
+#define QRESTREPLY_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 "private/qobject_p.h"
+#include <QtNetwork/qnetworkreply.h>
+#include <QtCore/qjsondocument.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRestReplyPrivate : public QObjectPrivate
+{
+public:
+ QRestReplyPrivate();
+ ~QRestReplyPrivate() override;
+
+ QNetworkReply *networkReply = nullptr;
+
+ QByteArray contentCharset() const;
+ bool hasNonHttpError() const;
+ QJsonDocument replyDataToJson();
+};
+
+QT_END_NAMESPACE
+
+#endif