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.  
  2. foreach ( Request r , requestList )
  3.     if(! runRequest(r) ) return error;
  4.  

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

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,
  16.         QIODevice * data, QIODevice * to = 0 )
  17. {
  18.     ///connect the requestFinished signal to our finished slot
  19.     connect(this,SIGNAL(requestFinished(int,bool)),
  20.         SLOT(finished(int,bool)));
  21.     /// start the request and store the requestID
  22.     requestID = post(path, data , to );
  23.     /// block until the request is finished
  24.     loop.exec();
  25.     /// return the request status
  26.     return status;
  27. }
  28. bool syncPost ( const QString & path,
  29.         const QByteArray& data, QIODevice * to = 0 )
  30. {
  31.     /// create io device from QByteArray
  32.     QBuffer buffer;
  33.     buffer.setData(data);
  34.     return syncPost(path,&buffer,to);
  35. }

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.     }
  11.  

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

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.

9 Comments

Abhi Windows XP Internet Explorer 6.0 February 16th, 2007 at 11:25 pm

Awesome – piece of code
It is helpful – thanks a lot

Juan Navarro SPAIN Ubuntu Linux Mozilla Firefox 3.0.3 October 21st, 2008 at 11:14 am

Hi! I found your SyncHTTP class some months ago and have been using it since then, it’s very useful thanks!
Today I have revisited this page to see if any updates to the code were available, and have seen the version I use is not exactly the same you have, but I don’t remember (well my memory is sooo bad :D ) if that is because I changed your code or because you have modified it from the original :)

Those changes are the two methods syncSetHost and syncSetUser, and the connect() calls are moved to the constructors (to avoid re-connecting the signals in each call to this class).

So the code is as follows:

/***************************************************************************
* Copyright (C) 2005 by Iulian M *
* eti@erata.net *
***************************************************************************/
#ifndef ETKSYNCHTTP_H
#define ETKSYNCHTTP_H

#include
#include
#include

/**
* Provide a synchronous api over QHttp
* Uses a QEventLoop to block until the request is completed
* @author Iulian M
*/
class SyncHTTP: public QHttp
{
Q_OBJECT
public:
/// constructors
SyncHTTP(QObject* parent=0)
: QHttp(parent), requestID(-1), status(false)
{
///connect the requestFinished signal to our finished slot
connect(this,SIGNAL(requestFinished(int,bool)),SLOT(finished(int,bool)));
}

SyncHTTP(const QString& hostName, quint16 port=80, QObject* parent=0)
: QHttp(hostName,port,parent), requestID(-1), status(false)
{
///connect the requestFinished signal to our finished slot
connect(this,SIGNAL(requestFinished(int,bool)),SLOT(finished(int,bool)));
}

virtual ~SyncHTTP() {}

/// send GET request and wait until finished
bool syncGet(const QString& path, QIODevice* to=0)
{
/// start the request and store the requestID
requestID = get(path, to);
/// block until the request is finished
loop.exec();
/// return the request status
return status;
}

/// send POST request and wait until finished
bool syncPost(const QString& path, QIODevice* data, QIODevice* to=0)
{
/// start the request and store the requestID
requestID = post(path, data, to);
/// block until the request is finished
loop.exec();
/// return the request status
return status;
}

bool syncPost(const QString& path, const QByteArray& data, QIODevice* to=0)
{
/// create io device from QByteArray
QBuffer buffer;
buffer.setData(data);
return syncPost(path, &buffer, to);
}

bool syncSetHost(const QString& hostName, quint16 port=80)
{
/// start the request and store the requestID
requestID = setHost(hostName, port);
/// block until the request is finished
loop.exec();
/// return the request status
return status;
}

bool syncSetUser(const QString& userName, const QString& password=QString())
{
/// start the request and store the requestID
requestID = setUser(userName, password);
/// block until the request is finished
loop.exec();
/// return the request status
return status;
}

protected slots:
virtual void finished(int idx, bool err)
{
/// check to see if it’s the request we made
if (idx != requestID)
return;
/// set status of the request
status = !err;
/// end the loop
loop.exit();
}

private:
/// id of current request
int requestID;
/// error status of current request
bool status;
/// event loop used to block until request finished
QEventLoop loop;
};

#endif

goodbye!

eti ROMANIA Windows XP Mozilla Firefox 3.0.3 October 21st, 2008 at 11:19 am

Hi,
Glad to know this code was useful.

Roberto ITALY Linux Mozilla Firefox 3.5.3 October 1st, 2009 at 5:28 pm

GREAT!!! I can do synchronous Xmlrpc calls now!!!

Thanks a lot!!!!!

danipellex SPAIN Windows XP Mozilla Firefox 3.5.3 October 16th, 2009 at 1:11 pm

Excellent piece of code!
really useful 2 years later!

best wishes for you and thank you very much
pellex

C# HTTP Request | Erata.NET WordPress abc November 21st, 2009 at 1:06 pm

[...] few years ago I’ve written an article on how to perform a synchronous HTTP request using Qt 4.2. I I’ve seen this article today and since now I’m mostly working [...]

Yasir Hussain PAKISTAN Windows XP Mozilla Firefox 3.5.6 January 17th, 2010 at 10:39 am

Thank you very much for this Excellent piece of concept.

Grzegorz.W Windows XP Mozilla Firefox 3.6 February 26th, 2010 at 11:58 am

I have question about (IHMO) mysterious line that says
‘if(idx!=requestID) return;’
How is it possible that the response could come with the Id other the was requested?
I’m also using the syncHttp code and I’ve noticed that when http request is done , it always assigns even number id’s . ‘requestFinished’ slot is fired twice, once for Id that is improper , and the second with one we want.
Can anyone explain where do the ‘bad’ reponses come from ?

eti ROMANIA Windows XP Mozilla Firefox 3.5.8 February 26th, 2010 at 2:50 pm

@Grzegorz
It’s been a while since I’ve written this code, and at the moment i don’t have access to a Qt installation so I’m only guessing but:

I think that you can use the QHttp class to make more than one request at the same time, each having it’s own request id and that’s probably why trolltech designed the interface this way.

As for the “bad” responses i remember that i was curious about this at the time and that i discovered that the QHttp class can make other internal, protocol specific, requests witch are handled the same way as normal requests. I think that braking with a debugger when the “bad” response arrives will give you a clearer answer.

Leave a comment

Your comment