Писал я как-то мессенджер для «ВКонтакте» и пришлось мне разбираться с отправкой 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.
Я вынес всё это в отдельную библиотеку, которая уже не раз меня выручала, так как она сильно упрощает мне работу, когда нужна отправка запросов.
На этом всё. Надеюсь, кому-то это будет полезным.
ссылка на оригинал статьи http://habrahabr.ru/post/270649/
Добавить комментарий