[Из песочницы] GET/POST запросы в Qt или обёртка над QNetworkAccessManager
Писал я как-то мессенджер для «ВКонтакте» и пришлось мне разбираться с отправкой GET/POST запросов. Оказалось, для этих целей в Qt можно (и нужно) использовать класс из модуля QtNetwork — QNetworkAccessManager, а также QNetworkReply и QNetworkRequest. Для GET/POST запросов у этого класса есть соответствующие методы get, который принимает QNetworkRequest и post, который помимо QNetworkRequest принимает еще и QByteArray (собственно данные, которые нужно отправить). В итоге я написал свою обертку над QNetworkAccessManager, которая полностью скрывает работу с этими классами. Кому интересно — прошу под кат.
Мне нужен был простой интерфейс для отправки GET/POST запросов на указанный url, с возможностью добавления параметров, установки прокси и таймаута. Для этих целей были созданы всего два класса Request и RequestSender.
#ifndef NETWORK_REQUEST_H
#define NETWORK_REQUEST_H
namespace Network
{
class Request
{
public:
Request(QString address = QString());
QString address() const;
void setAddress(QString address);
void addParam(QString name, QVariant value);
bool removeParam(QString name);
QStringList paramsNames() const;
QMap<QString, QString> params() const;
QUrl url(bool withParams = true) const;
QNetworkRequest request(bool withParams = true) const;
QByteArray data() const;
private:
QString _address;
QMap<QString, QString> _params;
};
}
#endif // NETWORK_REQUEST_H
#include "stdafx.h"
#include "request.h"
namespace Network
{
Request::Request(QString address /*= QString()*/)
{
setAddress(address);
}
QString Request::address() const
{
return _address;
}
void Request::setAddress(QString address)
{
for (QPair<QString, QString> value : QUrlQuery(QUrl(address)).queryItems())
addParam(value.first, value.second);
_address = address;
}
void Request::addParam(QString name, QVariant value)
{
_params[name] = value.toString();
}
bool Request::removeParam(QString name)
{
if (false == _params.contains(name))
return false;
_params.remove(name);
return true;
}
QStringList Request::paramsNames() const
{
return _params.keys();
}
QMap<QString, QString> Request::params() const
{
return _params;
}
QUrl Request::url(bool forGetRequest /*= true*/) const
{
QUrl url(address());
if (forGetRequest)
url.setQuery(data());
return url;
}
QNetworkRequest Request::request(bool forGetRequest /*= true*/) const
{
QNetworkRequest r(url(forGetRequest));
if (!forGetRequest)
{
r.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
}
return r;
}
QByteArray Request::data() const
{
auto b = _params.begin();
auto e = _params.end();
QByteArray byteArrayData;
while (b != e)
{
byteArrayData.append(b.key());
byteArrayData.append('=');
byteArrayData.append(b.value());
byteArrayData.append('&');
b++;
}
byteArrayData.chop(1);
return byteArrayData;
}
}
В объяснении, я думаю, не нуждается поэтому идём дальше — к классу RequestSender.
#ifndef NETWORK_REQUESTSENDER_H
#define NETWORK_REQUESTSENDER_H
#include "request.h"
namespace Network
{
class RequestSender : public QObject
{
Q_OBJECT
public:
enum RequestError
{
NoError,
TimeoutError
};
RequestSender(qint64 maxWaitTime = 35000);
~RequestSender();
void setProxy(const QNetworkProxy& proxy);
QByteArray get(Request& request);
QByteArray post(Request& request);
QByteArray getWhileSuccess(Request& request, int maxCount = 2);
QByteArray postWhileSuccess(Request& request, int maxCount = 2);
void setMaxWaitTime(qint64 max);
qint64 maxWaitTime() const;
RequestError error() const;
private:
QByteArray sendRequest(Request& request, bool getRequest = true);
QByteArray sendWhileSuccess(Request& request, int maxCount = 2, bool getRequest = true);
private:
qint64 _maxWaitTime;
RequestError _error;
QNetworkProxy _proxy;
};
}
#endif // NETWORK_REQUESTSENDER_H
#include "stdafx.h"
#include "requestsender.h"
namespace Network
{
RequestSender::RequestSender(qint64 maxWaitTime /*= 35000*/)
{
setMaxWaitTime(maxWaitTime);
_error = NoError;
}
RequestSender::~RequestSender()
{
}
void RequestSender::setProxy(const QNetworkProxy& proxy)
{
_proxy = proxy;
}
QByteArray RequestSender::get(Request& request)
{
return sendRequest(request, true);
}
QByteArray RequestSender::post(Request& request)
{
return sendRequest(request, false);
}
QByteArray RequestSender::getWhileSuccess(Request& request, int maxCount /*= 2*/)
{
return sendWhileSuccess(request, maxCount, true);
}
QByteArray RequestSender::postWhileSuccess(Request& request, int maxCount /*= 2*/)
{
return sendWhileSuccess(request, maxCount, false);
}
void RequestSender::setMaxWaitTime(qint64 max)
{
_maxWaitTime = max;
}
qint64 RequestSender::maxWaitTime() const
{
return _maxWaitTime;
}
RequestSender::RequestError RequestSender::error() const
{
return _error;
}
QByteArray RequestSender::sendRequest(Request& request, bool getRequest /*= true*/)
{
QTimer timer;
timer.setInterval(_maxWaitTime);
timer.setSingleShot(true);
QEventLoop loop;
QSharedPointer<QNetworkAccessManager> manager(new QNetworkAccessManager);
manager->setProxy(_proxy);
QNetworkReply* reply = getRequest ? manager->get(request.request()) :
manager->post(request.request(false), request.data());
#if defined(NETWORK_SHOW_SEND_REQUESTS)
if (getRequest)
qDebug() << "[GET] " << request.request().url().toString();
else
qDebug() << "[POST]" << request.request(false).url().toString() << request.data();
#endif
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
QObject::connect(&timer, &QTimer::timeout, reply, &QNetworkReply::abort);
timer.start();
loop.exec();
QByteArray data;
if (reply->isFinished() && reply->error() == QNetworkReply::NoError)
{
data = reply->readAll();
_error = RequestSender::NoError;
}
else
{
_error = RequestSender::TimeoutError;
}
reply->deleteLater();
#if defined(NETWORK_SHOW_SEND_REQUESTS)
qDebug() << "[ANSWER]" << data;
#endif
return data;
}
QByteArray RequestSender::sendWhileSuccess(Request& request, int maxCount /*= 2*/, bool getRequest /*= true*/)
{
if (maxCount < 0)
throw QString(__LINE__ + " " __FILE__);
int c = 0;
QByteArray answer;
while (c < maxCount)
{
c++;
answer = getRequest ? get(request) : post(request);
if (error() == NoError)
break;
qDebug() << "Ошибка при отправке запроса. Код ошибки - " << error() << ". Повторная отправка запроса через 2 секунды\n";
QThread::currentThread()->msleep(2000);
}
return answer;
}
}
Тут наибольший интерес должен представлять метод sendRequest, в котором отправляется запрос, который имеет таймаут. После вызова этого метода можно узнать, прошел ли запрос удачно. Для этого есть метод error(), который вернет значение типа RequestError — NoError или TimeoutError.
В методе sendRequest создается таймер, который сработает только один раз, и ему устанавливается интервал равный таймауту. После создается QEventLoop и QNetworkAccessManager, в зависимости от типа запроса (POST/GET) вызывается соответствующий метод, после связываем сигналы, запускаем таймер и переходим в цикл обработки событий созданного нами QEventLoop. Этот цикл прервется в одном случае — reply отправил сигнал finished. Этот сигнал он отправит в двух случаях — либо запрос выполнился, либо был отменен в связи с наступлением таймаута.
После идёт проверка — закончился ли запрос успешно, если да то считываем данные из ответа, устанавливаем _error в NoError, удаляем reply и возвращаем считанные данные. Иначе устанавливаем _error в TimeoutError. Для упрощения отладки был добавлен вывод отправляемых запросов и получаемых данных.
Иногда есть необходимость дать запросу несколько шансов, для этого есть методы getWhileSuccess и postWhileSuccess, которые сводятся к вызову sendWhileSuccess.
Я вынес всё это в отдельную библиотеку, которая уже не раз меня выручала, так как она сильно упрощает мне работу, когда нужна отправка запросов.
На этом всё. Надеюсь, кому-то это будет полезным.