Make QRest* APIs non-owning and non-duplicating

Note: documentation will be updated in a follow-up commit

This commit makes QRestReply and QRestAccessManager
classes lighter, non-owning wrappers. Furthermore their
APIs don't duplicate the wrapped QNetwork* APIs.

This makes it easier to use / opt-in to these helpers
in pre-existing applications which are based on
QNetworkAccessManager and QNetworkReply.

Since APIs are no longer duplicated, the QRest
classes are more obviously a convenience _wrapper_,
as opposed to being an alternative vertical stack.

In practice this change consists of:
- QRestAM never instantiates QNetworkAccessManager,
  but accepts it via constructor. It does not take
  ownership of the QNetworkAccessManager.
- QRestReply accepts QNetworkReply via constructor. It
  does not take ownership of the QNetworkReply
- Signals and most duplicated functions are removed
  from both QRestAM and QRR.
- QRestReply is no longer a QObject
- Since QRestAM doesn't have much to report anymore,
  the debug operator is dropped.

Resulted from API-review

Change-Id: Ib62d9cc2df41cac631396a84bb7ec4d2d54b0c8c
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
(cherry picked from commit 9ba5c7ff6aa42c5701cf950d2137467a2d178833)
This commit is contained in:
Juha Vuolle 2024-01-19 11:33:39 +02:00
parent a57f89251c
commit afc1f2a6cc
7 changed files with 475 additions and 989 deletions

View File

@ -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<QObject*>(caller.contextObject) : nullptr;
? const_cast<QObject*>(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

View File

@ -6,8 +6,6 @@
#include <QtNetwork/qnetworkaccessmanager.h>
#include <chrono>
QT_BEGIN_NAMESPACE
class QDebug;
@ -16,57 +14,57 @@ 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, \
QNetworkReply *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) \
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 <typename Functor, if_compatible_callback<Functor> = true> \
QRestReply *METHOD(const QNetworkRequest &request, \
QNetworkReply *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) \
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 <typename Functor, if_compatible_callback<Functor> = true> \
QRestReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data, \
QNetworkReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data, \
const ContextTypeForFunctor<Functor> *context, \
Functor &&callback) \
{ \
return customWithDataImpl(request, method, data, context, \
QtPrivate::makeCallableObject<CallbackPrototype>(std::forward<Functor>(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 <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);
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)
};

View File

@ -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<typename Functor>
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<typename Functor, typename Json>
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<const QObject> contextObject = nullptr;
QtPrivate::SlotObjSharedPtr slot;
};
QHash<QRestReply*, CallerInfo> activeRequests;
QHash<QNetworkReply*, CallerInfo> activeRequests;
QNetworkAccessManager *qnam = nullptr;
bool deletesRepliesOnFinished = true;

View File

@ -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<QJsonDocument> 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<QJsonDocument> 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<QByteArray> 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"

View File

@ -6,7 +6,10 @@
#include <QtNetwork/qnetworkreply.h>
#include <QtCore/qpointer.h>
#include <optional>
#include <utility>
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<QJsonDocument> json(QJsonParseError *error = nullptr);
QByteArray body();
QString text();
Q_NETWORK_EXPORT QNetworkReply *networkReply() const;
Q_NETWORK_EXPORT std::optional<QJsonDocument> 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<QNetworkReply> wrapped;
QRestReplyPrivate *d = nullptr;
Q_DISABLE_COPY(QRestReply)
};
Q_DECLARE_SHARED(QRestReply)
QT_END_NAMESPACE
#endif // QRESTREPLY_H

View File

@ -15,27 +15,22 @@
// We mean it.
//
#include "private/qobject_p.h"
#include <QtNetwork/qnetworkreply.h>
#include <QtCore/qjsondocument.h>
#include <optional>
QT_BEGIN_NAMESPACE
class QNetworkReply;
class QStringDecoder;
class QRestReplyPrivate : public QObjectPrivate
class QRestReplyPrivate
{
public:
QRestReplyPrivate();
~QRestReplyPrivate() override;
~QRestReplyPrivate();
QNetworkReply *networkReply = nullptr;
std::optional<QStringDecoder> decoder;
QByteArray contentCharset() const;
bool hasNonHttpError() const;
static QByteArray contentCharset(const QNetworkReply *reply);
};
QT_END_NAMESPACE