Recently, I have been developing a desktop Rest Client API. Quite a big part of the job is interacting with the server. To optimize the processing of requests, the Requester class was written, which has the following features:
A programmer using this class will have to work with three functions:
void initRequester(const QString& host, int port, QSslConfiguration *value); // void sendRequest(const QString &apiStr, const handleFunc &funcSuccess, const handleFunc &funcError, Type type = Type::GET, const QVariantMap &data = QVariantMap()); // GET void sendMulishGetRequest( const QString &apiStr, const handleFunc &funcSuccess, const handleFunc &funcError, const finishFunc &funcFinish);
funcSuccess - callback, called if the request was successful
funcError - callback in case of error
typedef std::function<void(const QJsonObject &)> handleFunc; typedef std::function<void()> finishFunc; enum class Type { POST, GET, PATCH, DELET };
DELET is not a typo, since DELETE is not collected under WINDOWS.
To interact with the server, we will use three Qt classes: QNetworkAccessManager - implements the server request mechanism, QNetworkReply - the server response to our request and QNetworkRequest - the request itself.
I do not see the point of describing the implementation of the function initRequester, so let's go straight to SendRequest. The idea is that we create an object of the QNetworkRequest class. Depending on the type of request, we transfer it with additional data (the request body, if any) to an object of the QNetworkAccessManager class. The reply is written to the reply (an object of the QNetworkReply class). Since the requests are executed asynchronously, we will call a lambda on the signal received from the reply that checks for errors and causes a corresponding callback, it also frees up resources.
To create the request, the following code is used:
QNetworkRequest Requester::createRequest(const QString &apiStr) { QNetworkRequest request; QString url = pathTemplate.arg(host).arg(port).arg(apiStr); request.setUrl(QUrl(url)); request.setRawHeader("Content-Type","application/json"); // if (sslConfig != nullptr) request.setSslConfiguration(*sslConfig); return request; }
And here is the code of the function itself for requests to the server:
void Requester::sendRequest(const QString &apiStr, const handleFunc &funcSuccess, const handleFunc &funcError, Requester::Type type, const QVariantMap &data) { QNetworkRequest request = createRequest(apiStr); QNetworkReply *reply; switch (type) { case Type::POST: { QByteArray postDataByteArray = variantMapToJson(data); reply = manager->post(request, postDataByteArray); break; } case Type::GET: { reply = manager->get(request); break; } case Type::DELET: { if (data.isEmpty()) reply = manager->deleteResource(request); else reply = sendCustomRequest(manager, request, "DELETE", data); // break; } case Type::PATCH: { reply = sendCustomRequest(manager, request, "PATCH", data); break; } default: reply = nullptr; } connect(reply, &QNetworkReply::finished, this, [this, funcSuccess, funcError, reply]() { // , json QJsonObject obj = parseReply(reply); if (onFinishRequest(reply)) { if (funcSuccess != nullptr) funcSuccess(obj); } else { if (funcError != nullptr) { handleQtNetworkErrors(reply, obj); funcError(obj); } } reply->close(); reply->deleteLater(); } ); }
The attentive reader noted that for some DELETE requests and all PATCHs, the sendCustomRequest function is used to create a QNetworkReply object. This is explained by the fact that QNetworkAccessManager does not know how to send DELETE requests with the body out of the box, and PATCH does not know how to do it at all. To solve this problem, we write a small wrapper function:
QNetworkReply* Requester::sendCustomRequest(QNetworkAccessManager* manager, QNetworkRequest &request, const QString &type, const QVariantMap &data) { request.setRawHeader("HTTP", type.toUtf8()); QByteArray postDataByteArray = variantMapToJson(data); QBuffer *buff = new QBuffer; buff->setData(postDataByteArray); buff->open(QIODevice::ReadOnly); QNetworkReply* reply = manager->sendCustomRequest(request, type.toUtf8(), buff); buff->setParent(reply); return reply; }
I don’t see the point of considering the sendMulishGetRequest function in detail, since it is similar to the one discussed above. The differences are that after the first successful execution of the request, a link to the next page will be pulled out of the answer, after which a recursive call will occur. If there is no next page, the funcFinish function will be executed.
The resulting class has a very simple interface for use, and also saves us from many routine actions.
Source: https://habr.com/ru/post/314932/
All Articles