[Из песочницы] 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.

Я вынес всё это в отдельную библиотеку, которая уже не раз меня выручала, так как она сильно упрощает мне работу, когда нужна отправка запросов.

На этом всё. Надеюсь, кому-то это будет полезным.

© Habrahabr.ru