Введение
Большинство современных Qt-приложений взаимодействуют с веб-сервисами через REST API, обмениваясь данными в формате JSON. Основной инструмент для этого в Qt — QNetworkAccessManager, который умеет выполнять HTTP-запросы асинхронно, поддерживает редиректы, куки, прокси и SSL. В статье рассмотрим типичные GET и POST запросы, отправку JSON в теле запроса и разбор JSON-ответа.
Концепция
QNetworkAccessManager принимает объект QNetworkRequest (URL, заголовки) и возвращает QNetworkReply — объект, который сигнализирует о завершении запроса через finished(). Важно: QNetworkReply — это QIODevice, и его нужно явно удалять после обработки (обычно через deleteLater() в самом обработчике finished), иначе реплаи будут накапливаться в памяти.
Для отправки JSON в POST-запросе тело формируется как QByteArray через QJsonDocument::toJson(), а заголовок Content-Type: application/json указывается явно — Qt не делает этого автоматически. Один и тот же QNetworkAccessManager следует создавать один раз на приложение (или на модуль) и переиспользовать для всех запросов — он держит пул соединений и кеш, и создание нового экземпляра на каждый запрос неэффективно.
Пример кода: QNetworkAccessManager
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
class ApiClient : public QObject
{
Q_OBJECT
public:
explicit ApiClient(QObject *parent = nullptr) : QObject(parent) {}
void fetchUser(int userId)
{
QUrl url(QString("https://jsonplaceholder.typicode.com/users/%1").arg(userId));
QNetworkRequest request(url);
QNetworkReply *reply = m_manager.get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
handleReply(reply);
});
}
void createPost(const QString &title, const QString &body)
{
QUrl url("https://jsonplaceholder.typicode.com/posts");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject payload;
payload["title"] = title;
payload["body"] = body;
payload["userId"] = 1;
QNetworkReply *reply = m_manager.post(request, QJsonDocument(payload).toJson());
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
handleReply(reply);
});
}
private:
void handleReply(QNetworkReply *reply)
{
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Ошибка запроса:" << reply->errorString();
return;
}
const QByteArray data = reply->readAll();
QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Ошибка разбора JSON:" << parseError.errorString();
return;
}
qDebug() << "Ответ сервера:" << doc.toJson(QJsonDocument::Compact);
}
QNetworkAccessManager m_manager;
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ApiClient client;
client.fetchUser(1);
client.createPost("Заголовок", "Текст сообщения");
return app.exec();
}
#include "main.moc"
Пояснения к коду
Метод fetchUser() выполняет простой GET-запрос, а createPost() формирует JSON-объект через QJsonObject, сериализует его в QByteArray и отправляет POST-запросом с явно указанным заголовком Content-Type. Общий обработчик handleReply() проверяет код ошибки через reply->error(), читает тело ответа и парсит его в QJsonDocument, отдельно обрабатывая ошибку разбора JSON.
Вызов reply->deleteLater() в начале обработчика гарантирует, что объект будет корректно удалён даже если далее произойдёт ранний return при ошибке.
Подводные камни
- Не проверять
reply->error()перед чтением данных. При ошибке сервер может вернуть тело с описанием ошибки (например, HTML-страницу 404), и попытка распарсить его как JSON приведёт к путанице в диагностике. - Забыть
deleteLater()уQNetworkReply. Каждый невыполненный запрос держит выделенную память; в долгоживущем приложении с частыми запросами это приводит к утечке. - Создание
QNetworkAccessManagerна каждый запрос. Это разрывает переиспользование соединений (HTTP keep-alive) и кеша, что заметно снижает производительность при множестве запросов. - Лямбды, захватывающие
thisв объекте, который может быть удалён до завершения запроса. Если объект-владелец удаляется раньше ответа, обработчик обращается к освобождённой памяти — используйтеQPointerилиconnectс контекстным объектом для автоматического отключения сигнала.