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.

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

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

ссылка на оригинал статьи http://habrahabr.ru/post/270649/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *