Пишемо 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,
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, що викликається у разі, якщо запит виконався успішно
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,
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();
} );
}

Уважний читач помітив, що для деяких запитів 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.

Висновок

Вийшов у підсумку, клас має дуже простий інтерфейс для використання, а так само позбавляє нас від багатьох рутинних дій.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.