From a9d3360f7fd2159960f88c2113dbafdf4b151c44 Mon Sep 17 00:00:00 2001 From: LordGrey Date: Sat, 11 Feb 2023 19:05:44 +0100 Subject: [PATCH] Refactor ProviderRestApi, increase default timeout --- libsrc/leddevice/dev_net/ProviderRestApi.cpp | 212 ++++++++----------- libsrc/leddevice/dev_net/ProviderRestApi.h | 90 +++++++- 2 files changed, 165 insertions(+), 137 deletions(-) diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.cpp b/libsrc/leddevice/dev_net/ProviderRestApi.cpp index 0d318e56..62806cda 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.cpp +++ b/libsrc/leddevice/dev_net/ProviderRestApi.cpp @@ -20,25 +20,32 @@ enum HttpStatusCode { NoContent = 204, BadRequest = 400, UnAuthorized = 401, + Forbidden = 403, NotFound = 404 }; -constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 400 }; - } //End of constants -ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath) - :_log(Logger::getInstance("LEDDEVICE")) - , _networkManager(nullptr) +ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int port, const QString& basePath) + : _log(Logger::getInstance("LEDDEVICE")) + , _networkManager(nullptr) + , _requestTimeout(DEFAULT_REST_TIMEOUT) { _networkManager = new QNetworkAccessManager(); + _networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); - _apiUrl.setScheme("http"); + _apiUrl.setScheme(scheme); _apiUrl.setHost(host); _apiUrl.setPort(port); _basePath = basePath; } +ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int port) + : ProviderRestApi(scheme, host, port, "") {} + +ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath) +: ProviderRestApi("http", host, port, basePath) {} + ProviderRestApi::ProviderRestApi(const QString& host, int port) : ProviderRestApi(host, port, "") {} @@ -62,6 +69,12 @@ void ProviderRestApi::setBasePath(const QString& basePath) appendPath(_basePath, basePath); } +void ProviderRestApi::setPath(const QStringList& pathElements) +{ + _path.clear(); + appendPath(_path, pathElements.join(ONE_SLASH)); +} + void ProviderRestApi::setPath(const QString& path) { _path.clear(); @@ -73,6 +86,11 @@ void ProviderRestApi::appendPath(const QString& path) appendPath(_path, path); } +void ProviderRestApi::appendPath(const QStringList& pathElements) +{ + appendPath(_path, pathElements.join(ONE_SLASH)); +} + void ProviderRestApi::appendPath ( QString& path, const QString &appendPath) { if (!appendPath.isEmpty() && appendPath != ONE_SLASH) @@ -132,40 +150,7 @@ httpResponse ProviderRestApi::get() httpResponse ProviderRestApi::get(const QUrl& url) { - // Perform request - QNetworkRequest request(_networkRequestHeaders); - request.setUrl(url); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); -#endif - - QNetworkReply* reply = _networkManager->get(request); - - // Connect requestFinished signal to quit slot of the loop. - QEventLoop loop; - QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - -#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); -#endif - - // Go into the loop until the request is finished. - loop.exec(); - - httpResponse response; - if (reply->operation() == QNetworkAccessManager::GetOperation) - { - if(reply->error() != QNetworkReply::NoError) - { - Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() )); - } - response = getResponse(reply ); - } - // Free space. - reply->deleteLater(); - // Return response - return response; + return executeOperation(QNetworkAccessManager::GetOperation, url); } httpResponse ProviderRestApi::put(const QJsonObject &body) @@ -180,40 +165,7 @@ httpResponse ProviderRestApi::put(const QString &body) httpResponse ProviderRestApi::put(const QUrl &url, const QByteArray &body) { - // Perform request - QNetworkRequest request(_networkRequestHeaders); - request.setUrl(url); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); -#endif - - QNetworkReply* reply = _networkManager->put(request, body); - // Connect requestFinished signal to quit slot of the loop. - QEventLoop loop; - QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - -#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); -#endif - - // Go into the loop until the request is finished. - loop.exec(); - - httpResponse response; - if (reply->operation() == QNetworkAccessManager::PutOperation) - { - if(reply->error() != QNetworkReply::NoError) - { - Debug(_log, "PUT: [%s] [%s]", QSTRING_CSTR( url.toString() ),body.constData() ); - } - response = getResponse(reply); - } - // Free space. - reply->deleteLater(); - - // Return response - return response; + return executeOperation(QNetworkAccessManager::PutOperation, url, body); } httpResponse ProviderRestApi::post(const QJsonObject& body) @@ -228,76 +180,69 @@ httpResponse ProviderRestApi::post(const QString& body) httpResponse ProviderRestApi::post(const QUrl& url, const QByteArray& body) { - // Perform request - QNetworkRequest request(_networkRequestHeaders); - request.setUrl(url); - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); -#endif - - QNetworkReply* reply = _networkManager->post(request, body); - // Connect requestFinished signal to quit slot of the loop. - QEventLoop loop; - QEventLoop::connect(reply,&QNetworkReply::finished,&loop,&QEventLoop::quit); - -#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); -#endif - - // Go into the loop until the request is finished. - loop.exec(); - - httpResponse response; - if (reply->operation() == QNetworkAccessManager::PostOperation) - { - if(reply->error() != QNetworkReply::NoError) - { - Debug(_log, "POST: [%s] [%s]", QSTRING_CSTR( url.toString() ),body.constData() ); - } - response = getResponse(reply); - } - // Free space. - reply->deleteLater(); - - // Return response - return response; + return executeOperation(QNetworkAccessManager::PostOperation, url, body); } httpResponse ProviderRestApi::deleteResource(const QUrl& url) +{ + return executeOperation(QNetworkAccessManager::DeleteOperation, url); +} + +httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation operation, const QUrl& url, const QByteArray& body) { // Perform request QNetworkRequest request(_networkRequestHeaders); request.setUrl(url); + request.setOriginatingObject(this); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); + _networkManager->setTransferTimeout(_requestTimeout.count()); #endif - QNetworkReply* reply = _networkManager->deleteResource(request); + QDateTime start = QDateTime::currentDateTime(); + QString opCode; + QNetworkReply* reply; + switch (operation) { + case QNetworkAccessManager::GetOperation: + opCode = "GET"; + reply = _networkManager->get(request); + break; + case QNetworkAccessManager::PutOperation: + opCode = "PUT"; + reply = _networkManager->put(request, body); + break; + case QNetworkAccessManager::PostOperation: + opCode = "POST"; + reply = _networkManager->post(request, body); + break; + case QNetworkAccessManager::DeleteOperation: + opCode = "DELETE"; + reply = _networkManager->deleteResource(request); + break; + default: + Error(_log, "Unsupported operation"); + return httpResponse(); + } + // Connect requestFinished signal to quit slot of the loop. QEventLoop loop; QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - // Go into the loop until the request is finished. - loop.exec(); #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); + ReplyTimeout* timeout = ReplyTimeout::set(reply, _requestTimeout.count()); #endif - httpResponse response; - if (reply->operation() == QNetworkAccessManager::DeleteOperation) - { - if(reply->error() != QNetworkReply::NoError) - { - Debug(_log, "DELETE: [%s]", QSTRING_CSTR(url.toString())); - } - response = getResponse(reply); - } + // Go into the loop until the request is finished. + loop.exec(); + QDateTime end = QDateTime::currentDateTime(); + + httpResponse response = (reply->operation() == operation) ? getResponse(reply) : httpResponse(); + + Debug(_log, "%s took %lldms, HTTP %d: [%s] [%s]", QSTRING_CSTR(opCode), start.msecsTo(end), response.getHttpStatusCode(), QSTRING_CSTR(url.toString()), body.constData()); + // Free space. reply->deleteLater(); - // Return response return response; } @@ -338,7 +283,6 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) } else { - Debug(_log, "Reply.httpStatusCode [%d]", httpStatusCode ); QString errorReason; if (httpStatusCode > 0) { QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); @@ -350,21 +294,30 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) case HttpStatusCode::UnAuthorized: advise = "Check Authentication Token (API Key)"; break; + case HttpStatusCode::Forbidden: + advise = "No permission to access the given resource"; + break; case HttpStatusCode::NotFound: advise = "Check Resource given"; break; default: + advise = httpReason; break; } errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise); } else { - errorReason = reply->errorString(); + if (reply->error() == QNetworkReply::OperationCanceledError) { - response.setError(true); - response.setErrorReason(errorReason); + errorReason = "Network request timeout error"; } + else + { + errorReason = reply->errorString(); + } + response.setError(true); + response.setErrorReason(errorReason); } // Create valid body which is empty @@ -388,3 +341,8 @@ void ProviderRestApi::setHeader(QNetworkRequest::KnownHeaders header, const QVar } } } + +void ProviderRestApi::setHeader(const QByteArray &headerName, const QByteArray &headerValue) +{ + _networkRequestHeaders.setRawHeader(headerName, headerValue); +} diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.h b/libsrc/leddevice/dev_net/ProviderRestApi.h index a87c5f2c..0eec1ba1 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.h +++ b/libsrc/leddevice/dev_net/ProviderRestApi.h @@ -13,15 +13,20 @@ #include #include +constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 1000 }; + //Set QNetworkReply timeout without external timer //https://stackoverflow.com/questions/37444539/how-to-set-qnetworkreply-timeout-without-external-timer -class ReplyTimeout : public QObject { +class ReplyTimeout : public QObject +{ Q_OBJECT + public: enum HandleMethod { Abort, Close }; + ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) : - QObject(reply), m_method(method) + QObject(reply), m_method(method), m_timedout(false) { Q_ASSERT(reply); if (reply && reply->isRunning()) { @@ -29,20 +34,30 @@ public: connect(reply, &QNetworkReply::finished, this, &QObject::deleteLater); } } - static void set(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) + + bool isTimedout() const { - new ReplyTimeout(reply, timeout, method); + return m_timedout; } + static ReplyTimeout * set(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) + { + return new ReplyTimeout(reply, timeout, method); + } + +signals: + void timedout(); + protected: - QBasicTimer m_timer; - HandleMethod m_method; + void timerEvent(QTimerEvent * ev) override { if (!m_timer.isActive() || ev->timerId() != m_timer.timerId()) return; auto reply = static_cast(parent()); if (reply->isRunning()) { + m_timedout = true; + emit timedout(); if (m_method == Close) reply->close(); else if (m_method == Abort) @@ -50,6 +65,10 @@ protected: m_timer.stop(); } } + + QBasicTimer m_timer; + HandleMethod m_method; + bool m_timedout; }; /// @@ -104,11 +123,12 @@ private: /// ///@endcode /// -class ProviderRestApi +class ProviderRestApi : public QObject { + Q_OBJECT + public: - /// /// @brief Constructor of the REST-API wrapper /// ProviderRestApi(); @@ -121,6 +141,15 @@ public: /// explicit ProviderRestApi(const QString& host, int port); + /// + /// @brief Constructor of the REST-API wrapper + /// + /// @param[in] scheme + /// @param[in] host + /// @param[in] port + /// + explicit ProviderRestApi(const QString& scheme, const QString& host, int port); + /// /// @brief Constructor of the REST-API wrapper /// @@ -130,10 +159,20 @@ public: /// explicit ProviderRestApi(const QString& host, int port, const QString& basePath); + /// + /// @brief Constructor of the REST-API wrapper + /// + /// @param[in] scheme + /// @param[in] host + /// @param[in] port + /// @param[in] API base-path + /// + explicit ProviderRestApi(const QString& scheme, const QString& host, int port, const QString& basePath); + /// /// @brief Destructor of the REST-API wrapper /// - virtual ~ProviderRestApi(); + virtual ~ProviderRestApi() override; /// /// @brief Set an API's host @@ -177,6 +216,12 @@ public: /// void setPath(const QString& path); + /// @brief Set an API's path to address resources + /// + /// @param[in] pathElements to form a path, e.g. (lights,1,state) results in "/lights/1/state/" + /// + void setPath(const QStringList& pathElements); + /// /// @brief Append an API's path element to path set before /// @@ -184,6 +229,13 @@ public: /// void appendPath(const QString& appendPath); + /// + /// @brief Append API's path elements to path set before + /// + /// @param[in] pathElements + /// + void appendPath(const QStringList& pathElements); + /// /// @brief Set an API's fragment /// @@ -283,14 +335,28 @@ public: /// @param[in] The type of the header field. /// @param[in] The value of the header field. /// If the header field exists, the value will be combined as comma separated string. - void setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value); + /// + /// Set a header field. + /// + /// @param[in] The type of the header field. + /// @param[in] The value of the header field. + /// If the header field exists, the value will override the previous setting. + void setHeader(const QByteArray &headerName, const QByteArray &headerValue); + /// /// Remove all header fields. /// void removeAllHeaders() { _networkRequestHeaders = QNetworkRequest(); } + /// + /// Sets the timeout time frame after a request is aborted + /// Zero means no timer is set. + /// + /// @param[in] timeout in milliseconds. + void setTransferTimeout(std::chrono::milliseconds timeout = DEFAULT_REST_TIMEOUT) { _requestTimeout = timeout; } + /// /// @brief Set the common logger for LED-devices. /// @@ -308,10 +374,14 @@ private: /// static void appendPath (QString &path, const QString &appendPath) ; + + httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {}); + Logger* _log; // QNetworkAccessManager object for sending REST-requests. QNetworkAccessManager* _networkManager; + std::chrono::milliseconds _requestTimeout; QUrl _apiUrl;