Raymii.org
Quis custodiet ipsos custodes?Home | About | All pages | Cluster Status | RSS Feed
HTTP GET requests with Qt and in Qml (async)
Published: 29-04-2022 | Author: Remy van Elst | Text only version of this article
❗ This post is over two years old. It may no longer be up to date. Opinions may have changed.
Table of Contents
With Qt it's very easy to work with (async) HTTP requests. This guide shows you how to do it with Qt core and in Qml. The two examples print the output of a HTTP GET request on screen after pressing a button. The Qml method uses JavaScript, so that's cheating a bit, the other method uses plain C++ with Qt's libraries for networking (QNetworkAccessManager
) and signals and slots for the async part.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
This guide is written mainly because I find myself doing this often and keep looking in other projects where I've already did this to copy over the code. Even my fellow colleagues over at work peek at my GitHub for this specific thing, I was told recently, so better put it up online.
Screenshot of the demo app layout
Without using Qt, I'd probably handle network requests using curl
or
something like cpp-httplib, a header-only http client/server. I've done
plain old C++ network http requests before and written about it here, on
parsing HackerNews and Lobste.rs API's.
The full code for this guide can be found on my github.
Basic setup
Using Qt Creator, do a File
, New Project
. Select an empty
Qt Quick (QML) application and finish the wizard. I'm using Qt 5.15,
but the example also works with Qt 6.3.
This is the main.qml
file layout, 2 rows with a button and a
textfield:
Column {
spacing: 5
anchors.fill: parent
anchors.margins: 5
Row {
spacing: 5
Button {
text: "Qml HTTP GET"
}
TextField {
id: qmlResult
}
}
Row {
spacing: 5
Button {
text: "C++ HTTP GET "
}
TextField {
id: cppResult
}
}
}
C++ HTTP GET Request
The plain old C++ HTTP Get uses a few Qt provides classes, namely
QNetworkAccessManager
, QNetworkRequest
and QNetworkReply
, including a
few signals and slots to handle the request async.
We'll start by doing some busywork, creating the class derived from
QObject and registering it for the QML Engine. If you've done any Qt
before, you know that you'll do this many times and as I do, consider
it busywork. Whichever form of qRegister
/qmlRegister
you need depends
on the shape of the moon, but Qt 6 has made improvements on that spectrum,
now using cmake and only 1 place to register objects.
Create classes and Qml registration
Make a new class named NetworkExample
based off QObject, either by creating
the files yourself or by using the Qt Creator Add New
wizard, in that case
select a new C++ class and give it QObject as base:
NetworkExample.h
#ifndef NETWORKEXAMPLE_H
#define NETWORKEXAMPLE_H
#include <QObject>
class NetworkExample : public QObject
{
Q_OBJECT
public:
explicit NetworkExample(QObject *parent = nullptr);
signals:
};
#endif // NETWORKEXAMPLE_H
NetworkExample.cpp
#include "NetworkExample.h"
NetworkExample::NetworkExample(QObject *parent)
: QObject{parent}
{
}
The file does not do anything yet. In main.cpp
, create an
instance and register it to the Qml engine so we
can import it in Qml:
#include "NetworkExample.h"
[...] // below the QGuiApplication line
NetworkExample* networkExample = new NetworkExample();
qmlRegisterSingletonInstance<NetworkExample>("org.raymii.NetworkExample", 1, 0, "NetworkExample", networkExample);
At the bottom of the file, change the return app.exec()
line so we
save that value but also destroy our object before quitting:
auto result = app.exec();
networkExample->deleteLater();
return result;
Even though this is a simple example, I'm hoping to teach you a bit of hygiene by explicitly adding this part.
In main.qml
, below the other import
lines:
import org.raymii.NetworkExample 1.0
Network request
Finally, time to do the actual request. Add the <QNetworkAccessManager>
header
to your includes and add a QNetworkAccessManager* _manager = nullptr;
in
the private:
section of your header. Inside the constructor, new
it:
_manager = new QNetworkAccessManager(this);
Since we're providing a parent object, new
is fine. Once the parent QObject
is destroyed, this one will also be destroyed.
Add a method to do the actual request. In your header, declare and mark it as
Q_INVOKABLE
so Qml can call it:
Q_INVOKABLE void doGetRequest(const QString& url);
The function definition:
void NetworkExample::doGetRequest(const QString& url)
{
setResponse("");
auto _request = QScopedPointer<QNetworkRequest>(new QNetworkRequest());
_request->setUrl(url);
_request->setTransferTimeout(5000);
_request->setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0");
QNetworkReply *reply = _manager->get(*_request);
QObject::connect(reply, &QNetworkReply::finished, this, &NetworkExample::slotFinished);
}
Don't forget to include the <QNetworkReply>
header.
First part is a Qt style smart pointer, so we don't have to delete that
QNetworkRequest
ourselves. Once it goes out of scope, it is destroyed. The
very first line clears out any previous response data in our Q_PROPERTY
, we
will define that later.
Next up we set a few parameters, most important one being the URL, and as a bonus I've included setting a user agent header and request timeout of 5 seconds.
Using our QNetworkAccessManager
we send the request off, then connecting up
the finished
signal to out reply. To keep this guide simple, I'm not
connecting up the errorOccured
or readyRead
signals, but you probably
should read the docs regarding the signals QNetworkReply
can emit.
Add a new slot (regular method, below the line public slots:
) for
our slotFinished
method:
public slots:
void slotFinished();
Contents:
void NetworkExample::slotFinished()
{
QNetworkReply *reply = dynamic_cast<QNetworkReply*>(sender());
if(reply != nullptr) {
setResponse(reply->readAll());
reply->deleteLater();
}
}
Every signal/slot
connection has method that returns a pointer to the object
that sent the signal, QObject::sender()
. I'm using it with a dynamic_cast
to make sure it's not a nullptr and the correct type. Using
QNetworkReply::readAll()
, the entire reply is available. If slotFinished
()
is called directly (not via a signal/slot), the reply
object will be
a nullptr. There are a few more considerations to keep in mind with QObject::sender()
like if the origin object is destroyed and DirectConnection
, but for our example
this will work just fine.
The documentation mentions explicitly to call deleteLater()
on the networkReply,
so we do that instead of regular delete.
The last part of our method is a new Q_PROPERTY
named response
. Add it in
the header just below the line Q_OBJECT
:
Q_PROPERTY(QString response READ response WRITE setResponse NOTIFY responseChanged)
In recent versions of Qt Creator you can right-click the Q_PROPERTY
part and
select Refactor
, Generate Missing Q_PROPERTY Members
. Do that, nothing
special about this property otherwise. If your Qt Creator version does not show
that handy option, add the signal/slot and member variable yourself manually.
In Qml, bind this property to the TextField
text
property:
TextField {
id: cppResult
text: NetworkExample.response
}
Make the Button
call the function we've just defined:
Button {
text: "C++ HTTP GET "
onClicked: NetworkExample.doGetRequest("http://httpbin.org/ip")
}
This URL will send back a JSON response containing the sending IP.
Press the big green Play (run) button and test it out:
That was easy right? No messing around with a CURL*
or curl_easy_setopt()
and
async by default. The QML / JavaScript part is even easier, so easy it feels
like type-unsafe cheating.
QML HTTP GET request
The QML part is just plain old JavaScript with a property binding. In the main.qml
file, define a property var
which will hold the response
data, inside the Window{}
, just above our Column
:
property var response: undefined
Right below the new property, add a function that will do the request:
function doGetRequest(url) {
var xmlhttp = new XMLHttpRequest()
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === XMLHttpRequest.DONE
&& xmlhttp.status == 200) {
response = xmlhttp.responseText
}
}
xmlhttp.open("GET", url, true)
xmlhttp.send()
}
The method, when called, does a XMLHttpRequest
, with a callback function
that checks the status code, if the request was successful it updates the
response
property. Bind the response property to our TextField
:
TextField {
id: qmlResult
text: response
}
Add the new function to the Button's onClicked
:
Button {
text: "Qml HTTP GET"
onClicked: {
response = ""
doGetRequest("http://httpbin.org/ip")
}
}
Go forth, press the big green Play button and test it out:
You could of course, in the case of JSON, add a JSON.parse(xmlhttp.responseText)
, then
you can access the JSON right inside QML, (text: response.origin
), or add
more error handling.
As you can see, because it's just JavaScript, this is even easier than the already very simple C++ part.
If you want to test the async
-ness, specifically, not blocking the
GUI thread, use the url https://httpbin.org/delay/4
, which will
wait 4 seconds before responding. You should still be able to
click the buttons and see stuff happening.
Please send me your thoughts regarding what you like best, C++ or Qml for this purpose.
Tags: c++ , cpp , networking , qml , qt , qt5 , qt6 , seeed , tutorials