Пишем Rest API клиент на qt5

от автора

Введение

Последнее время я занимаюсь разработкой настольного Rest API клиента. Довольно большая часть работы во взаимодействие с сервером, для оптимизации обработки запросов был написан класс Requester, обладающий следующими особенностями:

  • возможность отправлять как https так и http запросы
  • использование одной функции для всех типов запросов
  • возможность получить все данные по запросу с сервера, а не одну страницу(n записей)

Программисту, использующему этот класс, придется работать тремя функциями:

    void initRequester(const QString& host, int port, QSslConfiguration *value);      // функция посылает один запрос     void sendRequest(const QString &apiStr,                        const handleFunc &funcSuccess,                        const handleFunc &funcError,                        int type = Type::GET,                        const QVariantMap &data = QVariantMap());       //функция будет посылать GET запрос пока не будет достигнута последня старница     void sendMulishGetRequest(                        const QString &apiStr,                        const handleFunc &funcSuccess,                        const handleFunc &funcError,                        const finishFunc &funcFinish);

funcSuccess — callback, вызываемый в случае, если запрос выполнился успешно
funcError — callback в случае ошибки

    typedef std::function<void(const QJsonObject &)> handleFunc;     typedef std::function<void()> finishFunc;     enum class Type {         POST,         GET,         PATCH,         DELET          };

DELET — не опечатка, так как с DELETE не собирается под WINDOWS.

Реализация

Для взаимодействия с сервером будем использовать три Qt класса: QNetworkAccessManager — реализует механизм запросов на сервер, QNetworkReply — ответ сервера на наш запрос и QNetworkRequest — собственно сам запрос.

Я не вижу смысла описывать реализацию функции initRequester, поэтому перейдём сразу к SendRequest. Идея состоит в том, что мы создаем объект класса QNetworkRequest. В зависимости от типа запроса, передаем его с дополнительными данными (тело запроса, если есть) в объект класса QNetworkAccessManager. Ответ записывается в reply(объект класса QNetworkReply). Так как запросы выполняются асинхронно, то по сигналу finished от reply будем вызывать лямбду, проверяющую были ли ошибки и вызывающую соответсвующий callback, она же занимается высвобождением ресурсов.
Для создания request`а используется следующий код:

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

А вот и код самой функции для запросов на сервер:

void Requester::sendRequest(const QString &apiStr,                             const handleFunc &funcSuccess,                             const handleFunc &funcError,                             int 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();     } ); }

Внимательный читатель заметил, что для некоторых запросов DELETE и всех PATCH для создания объекта QNetworkReply используется функция sendCustomRequest. Это объясняется тем, что QNetworkAccessManager не умеет из коробки посылать DELETE запросы с телом, и совсем не умеет PATCH. Для решения этой проблемы напишем небольшую функцию обертку:

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

Подробно рассматривать функцию sendMulishGetRequest не вижу смысла, так как она похожа на рассмотренную выше. Отличия заключаются в том, что после первого успешного выполнения запроса, из ответа будет вытащена ссылка на следующую страницу, после чего произойдет рекурсивный вызов. Если следующей страницы нет, то будет выполнена функция funcFinish.

Заключение

Получившийся в итоге, класс имеет очень простой интерфейс для использования, а так же избавляет нас от многих рутинных действий.

ссылка на оригинал статьи https://habrahabr.ru/post/314932/