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,quint16 port = 80, QObject * parent = 0 )
  17. :QHttp(hostName,port,parent),requestID(-1),status(false){}
  18.  
  19. virtual ~SyncHTTP(){}
  20.  

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

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.

  • Abhi

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

  • Juan Navarro

    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!

  • http://www.erata.net eti

    Hi,
    Glad to know this code was useful.

  • http://www.roccoangeloni.it Roberto

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

    Thanks a lot!!!!!

  • danipellex

    Excellent piece of code!
    really useful 2 years later!

    best wishes for you and thank you very much
    pellex

  • Pingback: C# HTTP Request | Erata.NET

  • Yasir Hussain

    Thank you very much for this Excellent piece of concept.

  • Grzegorz.W

    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 ?

  • http://www.erata.net eti

    @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.

  • Marc

    Kudos for this…thanks!!

  • http://www.facebook.com/profile.php?id=1307224276 Fedorov Evgeni

    Thanks a lot!