Fire & Forget HTTP Requests in Qt

published at 05.07.2019 14:42 by Jens Weller

I've spend the past year often working with PHP instead of C++. And now its time to connect some of these systems with the C++ Backend that powers the conference it self.

For the past, this has often been a one way connection: I query data from the web, and then use this data to import speakers, talks or new orders for tickets. Now I wanted to call a quick script on the server to mark a ticket order as paid. And notify the buyer that the payment has arrived. That way I hope less folks email me out of the blue if ticket 1337 is paid. I can look this up of course, but already have a process where I could simply notify you. Which is live now, some of you might got an email through that today...

But in order to do a simple query to the web in Qt, its not that easy. The UI Code is in C++, mostly running synchronous from where I send the queries. But the queries it self are asynchronous, QNetworkAccessManager is the interface to do http queries in Qt. Its designed to do things in an async manner, one can apply a hack and have it run in a local QEventLoop which makes it execute in the local context. But I consider this an ugly hack, and also I am fine with its async nature. It just means a little bit more work and that a simple call to a function to fire a HTTP Request won't work that easy. So one needs a helper to wrap and handle the async nature of http calls in order to then just trigger them in the synchronous code.

A small wrapper class

In order to make this reusable I decided to put this into a small class, derived from QObject. The class has an instance of QNetworkAccessManager and then goes ahead to count the number of send queries. The caller side calls this in a loop, so that at any time multiple queries could be waiting for completion.

class NetworkFireNForget : public QObject
{
    Q_OBJECT
    QNetworkAccessManager manager;
    int requests = 0;//currently active requests
    bool destroy = false;//when true, and active requests are 0, call deleteLater
public:
    explicit NetworkFireNForget(QObject *parent = nullptr);
    QNetworkReply* sendGet(const QUrl& url);
    void setDestroy(bool value);
};

Well, maybe I could have spend some more time on naming, but its pretty straight forward what this class does. Once the last query returns and its set to delete it self, it will call QObjects deleteLater, so that its properly deleted from its context. Which is in the context calling the actual Dialog sending the requests. As these requests are async, they likely outlive the context of the code where the queries are fired from.

One detail, in the constructor the connection to the finished signal from QNetworkAccessManager is made to a lambda to simply handle the internal counting of active queries and call deleteLater() once everything is done:

connect(&manager,&QNetworkAccessManager::finished,[this](QNetworkReply*)
    {
        requests--;
        if(destroy && requests == 0)
            deleteLater();
    });

As this class is called FireNForget, errors etc. are not handled, but it would of course be a good addition to the class it self.

On the calling site the class needs to be created, handed into its context firing the queries, and then by hand be told that we're done:

NetworkFireNForget* fnf = new NetworkFireNForget(this);
dlg.transferData(*fnf);
fnf->setDestroy(true);

So, this would be a nice usecase for scope guards, but the standard does not offer these yet. There is a proposal for C++20 though. I will be in cologne and hope to see the debate on this there. Boost has a scope exit library, but as Qt does not use exceptions this code runs fine.

Update

Qt actually has a scope guard class, so that above code could also be expressed as the following:

NetworkFireNForget* fnf = new NetworkFireNForget(this);
auto guard = qScopeGuard([fnf](){fnf->setDestroy(true);}); dlg.transferData(*fnf);

This class is new in Qt, its available since Qt 5.12. So you have another good reason to update your version of Qt... :]

Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!