Введение
Последнее время я занимаюсь разработкой настольного 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/
Добавить комментарий