Qt および Qml での HTTP GET リクエスト (非同期)

Qt を使用すると、(非同期の) HTTP リクエストを簡単に処理できます。このガイドでは、Qt コアと Qml でそれを行う方法を示します。 2 つの例では、ボタンを押した後に HTTP GET 要求の出力を画面に出力します。 Qml メソッドは JavaScript を使用するため、少しごまかしています。もう 1 つのメソッドは、ネットワーク用の Qt のライブラリでプレーンな C++ を使用します (QNetworkAccessManager ) および非同期部分のシグナルとスロット。

このガイドは主に、私がこれを頻繁に行っていることに気づき、コードをコピーするために既にこれを行った他のプロジェクトを探し続けているために書かれています。仲間の同僚でさえ、この特定のことについて私の GitHub を覗き見していました。

Qt を使用しない場合、おそらく curl を使用してネットワーク リクエストを処理するでしょう。 またはヘッダーのみの http クライアント/サーバーである cpp-httplib のようなもの。私は以前に古い C++ ネットワーク http リクエストをプレーン化したことがあり、HackerNews と Lobste.rs API を解析して、ここでそれについて書いています。

このガイドの完全なコードは、私の github にあります。

基本設定

Qt Creator を使用して File を実行します 、 New Project . emptyQt Quick (QML) アプリケーションを選択し、ウィザードを終了します。 Qt 5.15 を使用していますが、この例は Qt 6.3 でも動作します。

これは main.qml です ファイル レイアウト、ボタンとテキスト フィールドを含む 2 行:

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 リクエスト

単純な古い C++ HTTP Get は、Qt が提供するいくつかのクラス、つまり QNetworkAccessManager を使用します。 、 QNetworkRequestQNetworkReply 、リクエストの非同期を処理するためのいくつかのシグナルとスロットを含みます。

QObject から派生したクラスを作成し、それを QML Engine に登録することから始めます。 Qtbefore を実行したことがある場合は、これを何度も実行することを知っているでしょう。 qRegister のどの形式でも /qmlRegister 月の形に依存する必要がありますが、Qt 6 ではその範囲が改善され、cmake を使用してオブジェクトを登録する場所が 1 つだけになりました。

クラスの作成と Qml の登録

NetworkExample という名前の新しいクラスを作成します 自分でファイルを作成するか、Qt Creator Add New を使用して、QObject に基づいています。 その場合、新しい C++ クラスを選択し、ベースとして QObject を指定します:

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

}

ファイルはまだ何もしません。 main.cpp で 、インスタンスを作成して Qml エンジンに登録し、Qml にインポートできるようにします。

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

ファイルの下部にある return app.exec() を変更します その値を保存するだけでなく、終了する前にオブジェクトを破棄します:

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

これは単純な例ですが、この部分を明示的に追加することで、少し衛生的な方法を教えたいと思っています.

main.qml で 、他の import の下 行:

import org.raymii.NetworkExample 1.0

ネットワーク リクエスト

最後に、実際のリクエストを実行します。 <QNetworkAccessManager> を追加します ヘッダーをインクルードに追加し、 QNetworkAccessManager* _manager = nullptr; を追加します private: で ヘッダーのセクション。コンストラクタ内 new それ:

_manager = new QNetworkAccessManager(this);

親オブジェクトを提供しているので、new 結構です。一度親 QObject が破壊された場合、これも破壊されます。

実際のリクエストを行うメソッドを追加します。ヘッダーで、宣言して Q_INVOKABLE としてマークします だからQmlはそれを呼び出すことができます:

Q_INVOKABLE void doGetRequest(const QString& url);

関数定義:

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

<QNetworkReply> を含めることを忘れないでください ヘッダー。

最初の部分は Qt スタイルのスマート ポインターなので、QNetworkRequest を削除する必要はありません。 私たち自身。範囲外になると、破棄されます。最初の行は、Q_PROPERTY の以前の応答データを消去します。 、後で定義します。

次に、いくつかのパラメータを設定します。最も重要なものは URL です。おまけとして、ユーザー エージェント ヘッダーとリクエスト タイムアウトを 5 秒に設定しました。

QNetworkAccessManager の使用 リクエストを送信し、finished に接続します 応答する信号。このガイドをシンプルにするために、errorOccured には接続していません。 または readyRead 信号ですが、おそらく信号 QNetworkReply に関するドキュメントを読む必要があります

新しいスロットを追加します (通常の方法、public slots: 行の下) ) forour slotFinished メソッド:

public slots:
    void slotFinished();

内容:

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

signal/slotごと 接続には、シグナル QObject::sender() を送信したオブジェクトへのポインタを返すメソッドがあります . dynamic_cast で使用しています nullptr ではなく、正しいタイプであることを確認します。 QNetworkReply::readAll() の使用 、返信全体が利用可能です。 slotFinished () の場合 reply は (シグナル/スロット経由ではなく) 直接呼び出されます。 オブジェクトは nullptr になります。 QObject::sender() について留意すべき考慮事項がいくつかあります。 元のオブジェクトが破棄されて DirectConnection になった場合のように 、しかし、この例ではこれで問題なく動作します。

ドキュメントでは、 deleteLater() を呼び出すように明示的に言及しています ネットワーク上で返信するので、通常の削除の代わりにそれを行います。

メソッドの最後の部分は新しい Q_PROPERTY です response という名前 .行 Q_OBJECT のすぐ下のヘッダーに追加します :

Q_PROPERTY(QString response READ response WRITE setResponse NOTIFY responseChanged)

Qt Creator の最近のバージョンでは、Q_PROPERTY を右クリックして 一部を選択してRefactorを選択 、 Generate Missing Q_PROPERTY Members .それ以外の場合は、このプロパティについて特別なことは何もありません。お使いの Qt Creator バージョンに便利なオプションが表示されない場合は、手動でシグナル/スロットとメンバー変数を追加してください。

Qml で、このプロパティを TextField にバインドします。 text プロパティ:

TextField {
    id: cppResult
    text: NetworkExample.response
}

Button を作る 定義したばかりの関数を呼び出します:

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

この URL は、送信元 IP を含む JSON 応答を返します。

大きな緑色の再生 (実行) ボタンを押してテストします:

簡単でしたよね? CURL* をいじる必要はありません または curl_easy_setopt() デフォルトでは非同期です。 QML / JavaScript の部分はさらに簡単で、タイプセーフでないチートのように簡単です。

QML HTTP GET リクエスト

QML 部分は、プロパティ バインディングを備えた単純な古い JavaScript です。 main.qml で ファイルで、property var を定義します Window{} 内に応答データを保持します 、 Column のすぐ上 :

property var response: undefined

新しいプロパティのすぐ下に、リクエストを実行する関数を追加します:

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

このメソッドは、呼び出されると XMLHttpRequest を実行します 、ステータス コードをチェックするコールバック関数を使用して、リクエストが成功した場合は response を更新します 財産。応答プロパティを TextField にバインドします :

TextField {
    id: qmlResult
    text: response
}

ボタンの onClicked に新しい関数を追加します :

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

次に、大きな緑色の再生ボタンを押してテストします:

もちろん、JSON の場合は JSON.parse(xmlhttp.responseText) を追加できます の場合、QML 内で JSON に直接アクセスできます (text: response.origin )、またはエラー処理を追加します。

ご覧のとおり、これは単なる JavaScript であるため、すでに非常に単純な C++ の部分よりもさらに簡単です。

async をテストしたい場合 -ness、具体的には、GUI スレッドをブロックしないようにするには、URL https://httpbin.org/delay/4 を使用します 、応答する前に 4 秒間待機します。ボタンをクリックして、何が起こっているかを確認できるはずです。

この目的のために、C++ と Qml のどちらが一番好きかについての考えを私に送ってください。