Qt4 Synchronous HTTP Request

Problem

Creating a wrapper over QHttp that can perform GET and POST requests in a synchronous way.

Solution

In Qt4 QHttp can perform HTTP requests, but the API only allows asynchronous requests. This means that you need to specify a set of slots to handle the signals that can be emitted while the request if performed. Now don't get me wrong, this is event driven programming and most of the times is the right way to go especially when writing a Qt based application. The cases when event driven operations prove difficult to use are rare and usually can be programmed using events.

Recently I’ve developed a small application that had to do a number of requests in a specified order and show a message if an error was to occur. The algorithm was simple:

1 foreach ( Request r , requestList )
2   if(! runRequest(r) ) return error;

For writing something like this I would like to not split the algorithm across a set of slots. So I would like to have a possibility to run a request and wait for it to finish.

Implementation

We start by creating a SyncHTTP class that is derived from QHttp:

 1 class SyncHTTP: public QHttp{
 2   Q_OBJECT
 3   /// id of current request
 4   int requestID;
 5   /// error status of current request
 6   bool status;
 7   /// event loop used to block until request finished
 8   QEventLoop loop; 
 9 
10 public:
11   /// structors
12   SyncHTTP( QObject * parent = 0 )
13   :QHttp(parent),requestID(-1),status(false){}
14 
15   SyncHTTP( const QString & hostName,quint16 port = 80, QObject * parent = 0 )
16   :QHttp(hostName,port,parent),requestID(-1),status(false){}
17 
18   virtual ~SyncHTTP(){}
19 }

Now we need to implement the methods for syncGet and syncPost to mimic the get and post methods from QHttp:

 1 /// send GET request and wait until finished
 2 bool syncGet ( const QString & path, QIODevice * to = 0 )
 3 {
 4   ///connect the requestFinished signal to our finished slot
 5   connect(this,SIGNAL(requestFinished(int,bool)),
 6   SLOT(finished(int,bool)));
 7   /// start the request and store the requestID
 8   requestID = get(path, to );
 9   /// block until the request is finished
10   loop.exec();
11   /// return the request status
12   return status;
13 }
14 /// send POST request and wait until finished
15 bool syncPost ( const QString & path, QIODevice * data, QIODevice * to = 0 )
16 {
17   ///connect the requestFinished signal to our finished slot
18   connect(this,SIGNAL(requestFinished(int,bool)),
19   SLOT(finished(int,bool)));
20   /// start the request and store the requestID
21   requestID = post(path, data , to );
22   /// block until the request is finished
23   loop.exec();
24   /// return the request status
25   return status;
26 }
27 
28 bool syncPost ( const QString & path, const QByteArray& data, QIODevice * to = 0 )
29 {
30   /// create io device from QByteArray
31   QBuffer buffer;
32   buffer.setData(data);
33   return syncPost(path,&buffer,to);
34 }

The syncGet and syncPost functions both use the same strategy. They connect the requestFinished signal from QHttp to a finished slot, they ask the QHttp object to perform the request. The QHttp's get/post method returns immediately with the request id that has been assigned to the current request. Now the nice part: After asking the QHttp object to perform the request we need to wait for it to finish, so we create an event loop and start it. The main event loop is now blocked and we are nor risking being called aging while executing the request. The blocking code is not using tricks like sleep or infinite loops so we don't end up using 100% CPU or wasting time sleeping. Now we just need to implement the finished slot to save the result and exit the loop:

 1 protected slots:
 2 virtual void finished(int idx, bool err)
 3 {
 4   /// check to see if it's the request we made
 5   if(idx!=requestID) return;
 6   /// set status of the request
 7   status = !err;
 8   /// end the loop
 9   loop.exit();
10 }

Now this slot first checks to see if it's being called for our request and returns if not, after that saves the error status returned by the QHttp object and exits the loop. The execution now will return in the syncGet/syncPost function that will return the status to the caller.

Testing

Ok, let's write a small test application. Writing real automated test cases for this kind of class is possible but not trivial so will just write a small test app.

 1 #include
 2 #include
 3 #include "synchttp.h"
 4 
 5 int main(int argc, char *argv[])
 6 {
 7   QApplication a(argc,argv);
 8   /// create object
 9   SyncHTTP h("www.google.com");
10 
11   ///prepare output buffer
12   QBuffer getOutput;
13   h.syncGet( "/search?q=erata.net",&getOutput );
14   qDebug() << getOutput.data();
15 
16   QByteArray data(QString("q=data").toLatin1());
17   QBuffer postOutput;
18 
19   h.syncPost( "/search",data, &postOutput);
20 
21   qDebug() << postOutput.data();
22 
23   return 0;
24 }

Now in conclusion i would like to say that i recommend using this class only when you simply need to perform a request and know if it was successful or not. On any other case, where you need to do more complicated things learn to use Qt's event driven programming model.

Download synchttp.h

PS: this is my first programming HOWTO so be merciful :)

The code and ideas from this article can be used without any license restrictions. You can consider it under BSD,MIT,BSL or whatever suites you best. Providing my name or a link to this website would be nice but is not required.

Comments