Pull to refresh

GET/POST запросы в Qt или обёртка над QNetworkAccessManager

Reading time5 min
Views21K
Писал я как-то мессенджер для «ВКонтакте» и пришлось мне разбираться с отправкой 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(const QString& address = QString());

		QString address() const;
		void setAddress(const QString& address);

		void addParam(const QString& name, const QVariant& value);
		bool removeParam(const 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(const QString& address /*= QString()*/)
	{
		setAddress(address);
	}

	QString Request::address() const
	{
		return _address;
	}

	void Request::setAddress(const QString& address)
	{
		for (QPair<QString, QString> value : QUrlQuery(QUrl(address)).queryItems())
			addParam(value.first, value.second);
		_address = address;
	}

	void Request::addParam(const QString& name, const QVariant& value)
	{
		_params[name] = value.toString();
	}

	bool Request::removeParam(const 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.

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

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

Articles

Change theme settings