We write Rest API client on Qt5

Introduction


Recently, I have been developing a desktop Rest API client. Quite a lot of the work consists in interacting with the server. To optimize request processing, the Requester class was written, which has the following features:


  • the ability to send both https and http requests
  • use of one function for all types of requests
  • the ability to get all the data on request from the server, and not one page (n records)


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 an error


    typedef std::function handleFunc;
    typedef std::function finishFunc;
    enum class Type {
        POST,
        GET,
        PATCH,
        DELET     
    };

DELET is not a typo, as with DELETE it is not built under WINDOWS.


Implementation


To interact with the server, we will use three Qt classes: QNetworkAccessManager - implements the server request mechanism, QNetworkReply - the server’s response to our request, and QNetworkRequest - the request itself.


I see no reason to describe the implementation of the initRequester function, so let's move on to SendRequest right away. The idea is that we create an object of the QNetworkRequest class. Depending on the type of request, we pass it with additional data (request body, if any) to the object of the QNetworkAccessManager class. The response is written to reply (object of the QNetworkReply class). Since the requests are executed asynchronously, then we will call a lambda on the signal finished from reply, which checks whether there were errors and calls the corresponding callback, it also frees up resources.
To create a 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 server requests:


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();
    } );
}

An attentive reader noticed that for some DELETE queries and all PATCHs, the sendCustomRequest function is used to create the QNetworkReply object. This is because QNetworkAccessManager does not know how to send DELETE requests with the body out of the box, and does not know how to PATCH. To solve this problem, we will 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 response, after which a recursive call will occur. If there is no next page, the funcFinish function will be executed.


Conclusion


The resulting class has a very simple interface to use, and also saves us from many routine actions.


https://github.com/Bragaman/QtRestApiRequester


Also popular now: