HTTP-GET-Anfragen mit Qt und in Qml (async)

 C Programming >> C-Programmierung >  >> Tags >> Qt
HTTP-GET-Anfragen mit Qt und in Qml (async)

Mit Qt ist es sehr einfach, mit (asynchronen) HTTP-Anfragen zu arbeiten. Diese Anleitung zeigt Ihnen, wie Sie dies mit Qt Core und in Qml tun. Die beiden Beispiele geben die Ausgabe einer HTTP-GET-Anforderung nach dem Drücken einer Schaltfläche auf dem Bildschirm aus. Die Qml-Methode verwendet JavaScript, das ist also ein bisschen Betrug, die andere Methode verwendet einfaches C++ mit Qt-Bibliotheken für die Vernetzung (QNetworkAccessManager ) und Signale und Slots für den asynchronen Teil.

Diese Anleitung wurde hauptsächlich geschrieben, weil ich dies oft mache und immer wieder in anderen Projekten nachschaue, in denen ich dies bereits getan habe, um den Code zu kopieren. Sogar meine Kollegen drüben bei workpeeken auf meinem GitHub für diese spezielle Sache, wurde mir kürzlich gesagt, also stellen Sie es besser online.

Ohne Qt würde ich Netzwerkanfragen wahrscheinlich mit curl behandeln oder so etwas wie cpp-httplib, ein reiner Header-HTTP-Client/Server. Ich habe zuvor einfache alte C++-Netzwerk-HTTP-Anfragen durchgeführt und hier darüber geschrieben, indem ich HackerNews- und Lobste.rs-APIs analysiert habe.

Den vollständigen Code für diese Anleitung finden Sie auf meinem GitHub.

Grundlegende Einrichtung

Führen Sie mit Qt Creator einen File aus , New Project . Wählen Sie eine leere Qt Quick (QML)-Anwendung aus und beenden Sie den Assistenten. Ich verwende Qt 5.15, aber das Beispiel funktioniert auch mit Qt 6.3.

Das ist die main.qml Dateilayout, 2 Zeilen mit einem Button und einem Textfeld:

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-Anforderung

Das einfache alte C++ HTTP Get verwendet ein paar von Qt bereitgestellte Klassen, nämlich QNetworkAccessManager , QNetworkRequest und QNetworkReply , einschließlich einiger Signale und Slots, um die Anforderung asynchron zu verarbeiten.

Wir beginnen damit, etwas Arbeit zu leisten, indem wir die von QObject abgeleitete Klasse erstellen und sie für die QML-Engine registrieren. Wenn Sie schon einmal mit Qt gearbeitet haben, wissen Sie, dass Sie dies viele Male tun werden, und betrachten Sie es wie ich als fleißige Arbeit. Welche Form von qRegister auch immer /qmlRegister Sie brauchen hängt von der Form des Mondes ab, aber Qt 6 hat dieses Spektrum verbessert und verwendet jetzt cmake und nur noch 1 Ort, um Objekte zu registrieren.

Klassen erstellen und Qml-Registrierung

Erstellen Sie eine neue Klasse namens NetworkExample basierend auf QObject, entweder indem Sie die Dateien selbst erstellen oder den Qt Creator Add New verwenden Wählen Sie in diesem Fall eine neue C++-Klasse aus und geben Sie ihr QObject als Basis:

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}
{

}

Die Datei tut noch nichts. In main.cpp , erstellen Sie eine Instanz und registrieren Sie sie bei der Qml-Engine, damit wir sie in Qml importieren können:

#include "NetworkExample.h"
[...] // below the QGuiApplication line
NetworkExample* networkExample = new NetworkExample();
qmlRegisterSingletonInstance<NetworkExample>("org.raymii.NetworkExample", 1, 0, "NetworkExample", networkExample);

Ändern Sie am Ende der Datei den return app.exec() Zeile, also speichern wir diesen Wert, zerstören aber auch unser Objekt vor dem Beenden:

auto result = app.exec();
networkExample->deleteLater();
return result;

Auch wenn dies ein einfaches Beispiel ist, hoffe ich, Ihnen ein wenig Hygiene beizubringen, indem ich diesen Teil ausdrücklich hinzufüge.

In main.qml , unter dem anderen import Zeilen:

import org.raymii.NetworkExample 1.0

Netzwerkanfrage

Endlich Zeit für die eigentliche Anfrage. Fügen Sie <QNetworkAccessManager> hinzu Header zu Ihren Includes und fügen Sie einen QNetworkAccessManager* _manager = nullptr; hinzu im private: Abschnitt Ihrer Kopfzeile. Innerhalb des Konstruktors new es:

_manager = new QNetworkAccessManager(this);

Da wir ein übergeordnetes Objekt bereitstellen, new ist gut. Einmal der übergeordnete QObject zerstört wird, wird auch diese zerstört.

Fügen Sie eine Methode hinzu, um die eigentliche Anfrage auszuführen. Deklarieren und markieren Sie ihn in Ihrem Header als Q_INVOKABLE Qml kann es also aufrufen:

Q_INVOKABLE void doGetRequest(const QString& url);

Die Funktionsdefinition:

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);
}

Vergessen Sie nicht, den <QNetworkReply> einzufügen Kopfzeile.

Der erste Teil ist ein intelligenter Zeiger im Qt-Stil, also müssen wir diesen QNetworkRequest nicht löschen uns selbst. Sobald es den Geltungsbereich verlässt, wird es zerstört. Die allererste Zeile löscht alle vorherigen Antwortdaten in unserem Q_PROPERTY , wir werden das später definieren.

Als nächstes setzen wir ein paar Parameter, der wichtigste ist die URL, und als Bonus habe ich das Setzen eines User-Agent-Headers und eines Request-Timeouts von 5 Sekunden eingefügt.

Verwenden Sie unseren QNetworkAccessManager Wir senden die Anfrage ab und verbinden dann finished Signal zur Antwort. Um diese Anleitung einfach zu halten, verbinde ich errorOccured nicht oder readyRead Signale, aber Sie sollten wahrscheinlich die Dokumentation bezüglich der Signale QNetworkReply lesen emittieren kann.

Fügen Sie einen neuen Slot hinzu (normale Methode, unterhalb der Zeile public slots: ) für unsere slotFinished Methode:

public slots:
    void slotFinished();

Inhalt:

void NetworkExample::slotFinished()
{
    QNetworkReply *reply = dynamic_cast<QNetworkReply*>(sender());
    if(reply != nullptr) {
        setResponse(reply->readAll());
        reply->deleteLater();
    }
}

Alle signal/slot Die Verbindung hat eine Methode, die einen Zeiger auf das Objekt zurückgibt, das das Signal gesendet hat, QObject::sender() . Ich verwende es mit einem dynamic_cast um sicherzustellen, dass es kein nullptr und der richtige Typ ist. Mit QNetworkReply::readAll() , ist die gesamte Antwort verfügbar. Wenn slotFinished () direkt aufgerufen wird (nicht über ein Signal/Slot), der reply Objekt wird ein nullptr sein. Bei QObject::sender() sind noch einige weitere Überlegungen zu beachten wie wenn das Ursprungsobjekt zerstört ist und DirectConnection , aber für unser Beispiel wird das gut funktionieren.

Die Dokumentation erwähnt explizit den Aufruf von deleteLater() im NetzwerkAntworten, also machen wir das statt regelmäßig zu löschen.

Der letzte Teil unserer Methode ist ein neuer Q_PROPERTY mit dem Namen response . Fügen Sie es in der Kopfzeile direkt unter der Zeile Q_OBJECT hinzu :

Q_PROPERTY(QString response READ response WRITE setResponse NOTIFY responseChanged)

In neueren Versionen von Qt Creator können Sie mit der rechten Maustaste auf Q_PROPERTY klicken Teil und wählen Sie Refactor , Generate Missing Q_PROPERTY Members . Tun Sie das, sonst nichts Besonderes an dieser Eigenschaft. Wenn Ihre Qt-Creator-Version diese praktische Option nicht zeigt, fügen Sie Signal/Slot und Mitgliedsvariable selbst manuell hinzu.

Binden Sie diese Eigenschaft in Qml an TextField text Eigenschaft:

TextField {
    id: cppResult
    text: NetworkExample.response
}

Machen Sie den Button Rufen Sie die gerade definierte Funktion auf:

Button {
    text: "C++ HTTP GET "
    onClicked: NetworkExample.doGetRequest("http://httpbin.org/ip")
}

Diese URL sendet eine JSON-Antwort zurück, die die sendende IP enthält.

Drücken Sie die große grüne Play (Run)-Taste und testen Sie es:

Das war einfach richtig? Kein Herumspielen mit einem CURL* oder curl_easy_setopt() und standardmäßig asynchron. Der QML/JavaScript-Teil ist sogar noch einfacher, so einfach, dass es sich anfühlt wie typunsicheres Schummeln.

QML-HTTP-GET-Anfrage

Der QML-Teil ist einfach nur altes JavaScript mit einer Eigenschaftsbindung. Im main.qml Datei, definieren Sie einen property var die die Antwortdaten enthält, innerhalb des Window{} , direkt über unserem Column :

property var response: undefined

Fügen Sie direkt unter der neuen Eigenschaft eine Funktion hinzu, die die Anfrage erledigt:

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()
}

Wenn die Methode aufgerufen wird, führt sie einen XMLHttpRequest aus , mit einer Callback-Funktion, die den Statuscode überprüft, wenn die Anfrage erfolgreich war, aktualisiert sie den response Eigentum. Binden Sie die Response-Eigenschaft an unseren TextField :

TextField {
    id: qmlResult
    text: response
}

Fügen Sie die neue Funktion zum onClicked des Buttons hinzu :

Button {
    text: "Qml HTTP GET"
    onClicked: {
        response = ""
        doGetRequest("http://httpbin.org/ip")
    }
}

Gehen Sie weiter, drücken Sie die große grüne Play-Taste und testen Sie es aus:

Im Fall von JSON könnten Sie natürlich einen JSON.parse(xmlhttp.responseText) hinzufügen , dann können Sie direkt in QML auf JSON zugreifen (text: response.origin ) oder weitere Fehlerbehandlung hinzufügen.

Wie Sie sehen können, ist dies noch einfacher als der bereits sehr einfache C++-Teil, da es sich nur um JavaScript handelt.

Wenn Sie den async testen möchten -ness, insbesondere den GUI-Thread nicht blockieren, verwenden Sie die URL https://httpbin.org/delay/4 , das 4 Sekunden wartet, bevor es antwortet. Sie sollten immer noch in der Lage sein, auf die Schaltflächen zu klicken und zu sehen, was passiert.

Bitte senden Sie mir Ihre Meinung dazu, was Ihnen am besten gefällt, C++ oder Qml für diesen Zweck.