QMLアプリケーションとWebリソースの統合

こんにちは、ホーク! 私は、新しい言語QMLのプログラムをWebリソースと統合する方法を教えたいと思います。

QML自体は、Qtフレームワークの一部である宣言型JavaScriptのようなプログラミング言語です。 Qt開発者は真面目で、インターフェイスを作成するための主要なツールとして宣伝しています。 さらに、Webサーバーを操作する機能など、C ++に頼ることなく、非常に多くのことを実行できます。

Webテクノロジーは私たちの生活にますます浸透しており、多くの場合、さまざまなWebリソースを使用しています。 このためにブラウザーを起動することは必ずしも便利ではありません。別のクライアントアプリケーションがはるかに便利な場合があります。これは、たとえばモバイルプラットフォームなど、さまざまなソーシャルネットワークのクライアント数によって雄弁に示されます。

先週リリースされたアルファ版のQt 5.1にはAndroidとiOSの初期サポートが含まれていることを考えると、このトピックはQtを見ているか、積極的にマスターしている人にとって特に興味深いものになります。 この記事では、VK APIの例を使用してQMLアプリケーションからWebリソースを使用して作業を整理する方法を説明します。

念のため、Qt 5.0.2の最新の安定バージョンを検討していることに注意してください。 以前のバージョンでは、一部の機能がそうでない場合があります。

XMLHttpRequestとは何か、なぜ必要なのか

確かに、読者の多くはAJAX(Asynchronous JavaScript And XML)などのテクノロジーについて聞いたことがあるでしょう。 これにより、非同期リクエストをサーバーに送信し、リロードせずにページのコンテンツを更新できます。 最近のブラウザにはこれのためのさまざまなツールがあり、XMLHttpRequestもその1つです。 QMLはJavaScriptに似た言語であり、その中のJavaScript環境はブラウザーに似ているため、XMLHttpRequestも存在します。 テキストの後半では、その名前を省略形-XHRで書き留めます。

実際、それは何であり、私たちに何を与えますか? これは、非同期(ブラウザーで同期ブラウザーもサポートされている)HTTP要求用のツールです。 その名前にもかかわらず、元々はこの目的のために意図されていましたが、XML形式だけでなくデータを転送することができます。 QMLエンジンの実装は、GET、POST、HEAD、PUT、およびDELETEのHTTP要求をサポートしています。 基本的に、最初の2つを使用します。

QMLでのXHRの実装の特徴的な機能は、リクエストを任意のホストに送信できることです。ブラウザのような制限はありません。

XMLHttpRequestプロシージャ

XHRを使用するプロセスは次のとおりです。

1. XHRオブジェクトを作成します。

var request = new XMLHttpRequest() 


2.オブジェクトを初期化して、リクエストのタイプ(別名HTTPメソッド)、アドレス、および必要に応じてリクエストパラメータ[1]を示し、サーバーに送信する必要があります。

 request.open('GET', 'http://site.com?param1=value1&param2=value2') 


最初のパラメーターはリクエストのタイプ、2番目はURLです。 GETリクエストの場合、パラメーターはここに渡され、記号「?」でアドレスから分離されている必要があります。 パラメーターは「&」文字で区切られます。

POSTリクエストの場合、コンテンツのタイプを指定する必要があります。 クエリパラメータでデータを渡す場合、これは次のように行われます。

 request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') 


3.ハンドラーを設定して、リクエストの状態を変更します。 ほとんどの場合、リクエストが完了するまで待ってから、結果またはエラーを処理するだけです。 リクエストの最後で、readyStateパラメーターはXMLHttpRequest.DONEと等しくなります(値の詳細については、 [2]を参照してください)。

 request.onreadystatechange = function () { if (request.readyState === XMLHttpRequest.DONE) { if (request.status === 200) { console.log(request.responseText) } else { console.log("HTTP request failed", request.status) } } } 


匿名関数は、変更のたびにreadyStateを呼び出します。 リクエストを完了することに関心があります。その後、リクエストが正常に完了したかどうかを確認します。 これを行うには、成功コード(200)でステータスコードを確認します。 HTTPはテキストプロトコルであり、コードの数値に加えて、テキストの説明も送信されるため、statusTextプロパティをこのステータスに対応する文字列と比較できます。この場合、これは文字列「OK」です。

 if (request.statusText === 'OK') 


エラーの場合、statusおよびstatusTextには、HTTPステータスコードのコードとテキストの説明が含まれます(たとえば、それぞれ404および「Not Found」)。

4.リクエストを送信します。

 request.send() 


POSTの場合、ここでリクエストパラメータを渡す必要があります。

 request.send('param1=value1&param2=value2') 


クエリパラメータですべての文字を送信できるわけではありません。 したがって、パラメーターと値の両方をエンコードし、必要に応じて、特別な関数encodeURIComponent()およびdecodeURIComponent()で適宜デコードする必要があります。 使用例:

 request.send('%1=%2'.arg(encodeURIComponent(param)).arg(encodeURIComponent(value))) 


エンコードされた文字列をさらに処理し、シーケンス「%20」(つまり、エンコードされたスペース)を「+」文字に置き換えることをお勧めします。 デコードする前に、それぞれ逆を行います。

通常、クエリパラメータは単純型の値を渡します。 配列を渡すこともできますが、構文はやや濁っています。 たとえば、2つの値のパラメーターの配列を送信すると、次のようになります。

 request.send('params[]=value1&params[]=value2') 


地獄から抜け出せば、オブジェクトを値として転送することさえできます(!)。しかし、これは完全に信頼できるとは限りません。受信側で配列に変換できるという意味で:)

POSTリクエストを使用すると、リクエストパラメータだけでなく、リクエスト本文にもデータを転送できます。 たとえば、JSON形式でデータを送信できます。 これを行うには、正しいContent-Typeとコンテンツサイズ(Content-Length)を設定します。 そのようなリクエストを送信する例:

 request.setRequestHeader('Content-Type', 'application/json') var params = { param1: value1, param2: value2 } var data = JSON.stringify(params) request.setRequestHeader('Content-Length', data.length) request.send(data) 


ここで、JSONはQMLで利用可能なグローバルオブジェクトであり、この形式で作業するためのツールを提供します[3]

実際、データを転送できる形式はサーバーによって決定されます。 JSONを受け入れる場合-罰金、JSONヘルメット。 データは要求パラメーターによって取得されることを期待しているため、送信する必要があります。

必要な理論的情報を学習したので、VKontakteでの練習と作業を開始します。

友達のリストを取得して表示する

まず、承認やその他の不要なジェスチャを必要としないメソッドを使用した簡単な例を検討してください。 友達リストを取得すると、このカテゴリに分類されます。 XHRを送信して起動時に友人のリストを取得し、受信後にユーザー名とアバターを表示する簡単なプログラムを作成します。

ほとんどのコードはディスプレイインターフェイスであり、具体的に説明する意味はありません。 JavaScriptオブジェクトまたは配列がモデルとして使用される場合、モデルの代わりにmodelDataがモデルデータを取得するために使用されることに注意してください。

ここで最も興味深い部分は、サーバーでの作業です。 VK APIにアクセスするには、特別なアドレスapi.vk.com/methodがあります。 メソッドの名前を受信したアドレスに追加します(メソッドのリストは[4]にあります )。この場合はfriends.getメソッドです。 このアドレスに、必要なパラメーターを指定してPOSTまたはGETリクエストを送信する必要があります。 答えはJSON形式です。 uidパラメーターでユーザーIDを渡す必要があります。 また、fieldsパラメーターで、別のphoto_mediumを渡して写真を取得します。これにより、写真が最小サイズになりません。

以下は、プログラムの実際のソースコードです。 mainのuserIdはユーザーIDです。

 import QtQuick 2.0 Rectangle { id: main property int userId: XXX property var friends width: 320 height: 640 color: 'skyblue' function getFriends() { var request = new XMLHttpRequest() request.open('POST', 'https://api.vk.com/method/friends.get') request.onreadystatechange = function() { if (request.readyState === XMLHttpRequest.DONE) { if (request.status && request.status === 200) { console.log("response", request.responseText) var result = JSON.parse(request.responseText) main.friends = result.response } else { console.log("HTTP:", request.status, request.statusText) } } } request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') request.send('fields=photo_medium&uid=%1'.arg(main.userId)) } ListView { id: view anchors.margins: 10 anchors.fill: parent model: friends spacing: 10 delegate: Rectangle { width: view.width height: 100 anchors.horizontalCenter: parent.horizontalCenter color: 'white' border { color: 'lightgray' width: 2 } radius: 10 Row { anchors.margins: 10 anchors.fill: parent spacing: 10 Image { id: image height: parent.height fillMode: Image.PreserveAspectFit source: modelData['photo_medium'] } Text { width: parent.width - image.width - parent.spacing anchors.verticalCenter: parent.verticalCenter elide: Text.ElideRight renderType: Text.NativeRendering text: "%1 %2".arg(modelData['first_name']).arg(modelData['last_name']) } } } } Component.onCompleted: { getFriends() } } 


私は答えに何が来るかについてコンソールに結論を出しました。この例を試してみたいという願望があれば便利です。

有効なIDが指定されている場合、プログラムを実行すると、次の図が表示されます。



ここでの最大の難点は、正確にXHRを操作することです。 これを理解して、コードを少し単純化してみましょう。

XMLHttpRequestを使用した作業の簡素化

XHRを使用するには2つの課題があります。

1.リクエストパラメータでデータを送信する場合、このリクエストをコンパイルする必要があります。 これらのパラメーターが変更される可能性がある場合、ほとんどの場合、要求パラメーターを断片から接着するコードに多くの操作があります。 さらに、上で書いたように、コンパイル時にencodeURIComponentを使用してキーと値をエンコードするのが良いことを忘れてはなりません。 全体として、これらのパラメーターを形成するコードは扱いにくく、あまり明確ではないことが判明する場合があります。 対応するフィールドがパラメータとして設定されているオブジェクトを使用する方がはるかに便利です。

オブジェクトをクエリパラメーターに変換する小さなJavaScriptライブラリを記述しました。すべてがエンコードすると、一般に、すぐに送信できる完成した文字列が生成されます。 クエリパラメータをデコードし、そこからオブジェクトを作成する関数もあります(ただし、単純な型のみをサポートし、パラメータ内の配列またはオブジェクトは解析されませんが、必要になることはほとんどありません)。 github.com/krnekit/qml-utils/blob/master/qml/URLQuery.jsで入手できます。

2.要求のタイプに応じて、データはさまざまな方法で送信する必要があり、追加のヘッダーを設定する必要がある場合もあります。 単一のインターフェイスを提供することで、XHRを簡単に送信できるライブラリを作成しました。 データを任意の形式で送信できます。このため、コンテンツタイプをパラメーターとして渡すことができます。デフォルトでは、同じ「application / x-www-form-urlencoded」と見なされますが、GETリクエストを使用して異なるタイプのデータを送信することはできません、この場合、POSTを使用する必要があります。 Content-Lengthも自動的に計算および設定されます。 リクエストタイプ、URL、コールバック関数(オプション)、リクエストの完了時に呼び出されるコールバック関数、およびデータタイプ(オプション)を受け入れます。 この関数は、要求オブジェクト自体を返すか、エラーの場合はnullを返します。 こちらで入手できます: github.com/krnekit/qml-utils/blob/master/qml/XHR.js

2つのライブラリデータを使用して、前の例を単純化しました。 ここではすべてのコードを提供するわけではありません。変更点のみを考慮します。
ファイルの先頭に、ライブラリを含めます(この例では、ライブラリファイルはqmlファイルと同じディレクトリにあります)。

 import 'URLQuery.js' as URLQuery import 'XHR.js' as XHR 


ライブラリをインポートし、ライブラリの関数にアクセスするためのライブラリの名前空間を設定します。

XHRを送信する関数は次のようになります。

 function getFriends() { var params = { fields: 'photo_medium', uid: main.userId } function callback(request) { if (request.status && request.status === 200) { console.log("response", request.responseText) var result = JSON.parse(request.responseText) main.friends = result.response } else { console.log("HTTP:", request.status, request.statusText) } } XHR.sendXHR('POST', 'https://api.vk.com/method/friends.get', callback, URLQuery.serializeParams(params)) } 


まず、クエリパラメータを使用してオブジェクトを定義します。 次に、要求が完了すると呼び出されるコールバック関数。 関数は、リクエスト自体をパラメーターとして受け取ります。 そして、リクエスト自体を送信し、serializeParams関数を使用してパラメーターでオブジェクトを変換します。
その結果、コードのサイズは変更されていないかもしれませんが、構造化され、理解しやすくなっています。

将来これらの関数を使用して、コードを単純にします。 誰かに役立つ場合は、MITライセンスを取得して使用できます。

QMLからの承認VK

すべてのメソッドが許可なく機能するわけではないため、おそらくログインする必要があります。 その結果、いわゆる 許可トークン。VKontakteにリクエストで転送します。 ログインできるように、VKontakteでアプリケーションを作成する必要があります。 これは、 vk.com / editapp?act = createで実行できます。 スタンドアロンアプリケーションのタイプを選択します。 次に、リクエストパラメータの1つでそのIDを転送します。

1.認証方法

スタンドアロンアプリケーション、つまり2つの認証方法を作成しているため、どちらにも独自の問題があります。そのため、最も害の少ないものを選択する必要があります。

1.直接認証。 HTTPリクエストは、ログイン情報とともに特定のアドレスに送信されます。 応答は、トークンまたはエラーの説明を含むJSON形式のデータになります。

利点:

短所:


2. OAuth認証。 次のように実装されます。 ユーザーに特別なログインページが表示されるプログラムにブラウザを組み込む必要があります。 承認後、別のページへのリダイレクトが発生し、現在のURLにトークンまたはエラーの説明が含まれます。 VKontakteはこの方法を主な方法として位置付けています。

利点:


ただし、欠点も重要です。



2.直接認証

もちろん、直接認証を有効にするように要求しましたが、VKontakteのサポートは最初に何が必要かをゆっくりと尋ね、それを完全にクランプしました:(したがって、純粋に理論的に考えます。次のようになります。

 function login() { var params = { grant_type: 'password', client_id: 123456, client_secret: 'XXX', username: 'XXX', password: 'XXX', scope: 'audio' } function callback(request) { if (request.status && request.status === 200) { console.log("response", request.responseText) var result = JSON.parse(request.responseText) if (result.error) { console.log("Error:", result.error, result.error_description) } else { main.authToken = result.auth_token // Now do requests with this token } } else { console.log("HTTP:", request.status, request.statusText) } } XHR.sendXHR('POST', 'https://oauth.vk.com/token', callback, URLQuery.serializeParams(params)) } 


最初に、パラメーターを作成します。パラメーターには、たとえば、ユーザーのオーディオレコードへのアクセスが必要であることを示しました(スコープパラメーター)。 次に、コールバック関数は、エラーが発生した場合にコンソールに書き込み、成功した場合はトークンを保存し、APIリクエストを続行できます。

念のため、ドキュメントへのリンクを残します: vk.com/dev/auth_direct

3. OAuthによる承認。

このタイプの認証では、ユーザーにログインWebページを表示する必要があります。 QtQuickには、WebKitエンジンをQMLアプリケーションに埋め込むことができるWebViewコンポーネントがあります。 ユーザーがログインすると、ブラウザーのURLが変更され、認証が成功した場合、リクエストパラメーターにトークンまたはアンカーのエラーの説明が含まれます[5]

このURLを解析してだまされないように、URLQueryのparseParams関数を使用します。 URL全体を一度に渡すことができ、出力時にパラメーター付きのオブジェクトを取得します。

この機能を実装するコンポーネントを以下に説明します。

LoginWindow.qml:
 import QtQuick 2.0 import QtQuick.Window 2.0 import QtWebKit 3.0 import "URLQuery.js" as URLQuery Window { id: loginWindow property string applicationId property string permissions property var finishRegExp: /^https:\/\/oauth.vk.com\/blank.html/ signal succeeded(string token) signal failed(string error) function login() { var params = { client_id: applicationId, display: 'popup', response_type: 'token', redirect_uri: 'http://oauth.vk.com/blank.html' } if (permissions) { params['scope'] = permissions } webView.url = "https://oauth.vk.com/authorize?%1".arg(URLQuery.serializeParams(params)) } width: 1024 height: 768 WebView { id: webView anchors.fill: parent onLoadingChanged: { console.log(loadRequest.url.toString()) if (loadRequest.status === WebView.LoadFailedStatus) { loginWindow.failed("Loading error:", loadRequest.errorDomain, loadRequest.errorCode, loadRequest.errorString) return } else if (loadRequest.status === WebView.LoadStartedStatus) { return } if (!finishRegExp.test(loadRequest.url.toString())) { return } var result = URLQuery.parseParams(loadRequest.url.toString()) if (!result) { loginWindow.failed("Wrong responce from server", loadRequest.url.toString()) return } if (result.error) { loginWindow.failed("Error", result.error, result.error_description) return } if (!result.access_token) { loginWindow.failed("Access token absent", loadRequest.url.toString()) return } succeeded(result.access_token) return } } } 


このコンポーネントを別のウィンドウに表示します。 login()メソッドを呼び出した後、ログインページがロードされます。



承認後、 oauth.vk.com / blank.htmlがアドレスとして使用されるURLにリダイレクトされ、「?」を介してリダイレクトされます。 または「#」の結果が表示されます。 permissionsパラメーターを使用して、必要なアクセス権を設定します。 そこに何かを指定すると、ウィジェットを介してログインするときに、アプリケーションにアクセス権を付与するためのダイアログが表示されます。

正しいアドレスに到達したタイミングを理解するために、onLoadingChangedハンドラーを設定します。 loadRequestオブジェクトを受け取り、そこから必要なすべての情報を取得します。 これは数回呼び出され、エラーが発生したとき、その場合は適切な信号を送信するとき、または目的のページがロードされたときの状況に関心があります。 この場合、トークンが到着したかどうかを確認し、到着した場合は承認の成功に関するシグナルを送信し、そうでない場合はエラーシグナルを送信します。

さて、このウィジェットを使用するプログラム自体を考えてみましょう。 認証に成功した場合、プログラムはユーザーステータスを「テスト」に設定します。 ユーザーIDは、mainのuserIdプロパティによって設定されます。

 import QtQuick 2.0 import 'URLQuery.js' as URLQuery import 'XHR.js' as XHR Rectangle { id: main property int userId: XXX property var authToken width: 640 height: 320 function processLoginSuccess(token) { loginWindow.visible = false authToken = token setStatus() } function setStatus() { var params = { access_token: main.authToken, text: 'test' } function callback(request) { if (request.status == 200) { console.log('result', request.responseText) var result = JSON.parse(request.responseText) if (result.error) { console.log('Error:', result.error.error_code,result.error.error_msg) } else { console.log('Success') } } else { console.log('HTTP:', request.status, request.statusText) } Qt.quit() } XHR.sendXHR('POST', 'https://api.vk.com/method/status.set', callback, URLQuery.serializeParams(params)) } LoginWindow { id: loginWindow applicationId: XXX permissions: 'status' visible: false onSucceeded: processLoginSuccess(token) onFailed: { console.log('Login failed', error) Qt.quit() } } Component.onCompleted: { loginWindow.visible = true loginWindow.login() } } 


ロード後、ログインウィンドウが表示されます。 ログイン後、非表示になり、ユーザーのステータスを変更する要求がサーバーに送信されます。 その後、プログラムは結果をコンソールに書き込み、終了します。

ログイン後、追加のアクセス権が不要であるか、その有効期限が切れていない場合は、トークンをリクエストする必要はありません(認証が成功した場合、トークンとともに返されます)。

XMLHttpRequestを他に使用できるもの

VKontakteではなくXHRに関連する私の経験からの短い話をお話しします。

どういうわけか私の同僚には、QMLでXMLデータを受信して​​処理するタスクがありました。

QtQuickには、XMLファイルをモデルとして引き出し、解析、レンダリングできる特別なタイプのXmlListModelがあります。 彼は、XPathタイプのクエリを設定する必要があり、それに応じてモデルが満たされます。 問題は、XMLファイルに、選択してモデルに配置する必要がある要素だけでなく、取得する必要がある追加情報も含まれていたことです。

いくつかの解決方法があります。 2つのXmlListModelオブジェクトを使用できますが、これは明確な松葉杖です。さらに、XMLファイルを2回ダウンロードしたくありません(チェックされます)。 この機能は、いくつかのパーサーオプションを含むQtを使用して実装できますが、純粋なQMLの問題を解決したいという要望がありました。

XMLHttpRequestは元々XMLを操作することを目的としていたため、XMLを操作するためのツールがあります。 したがって、ツールを使用してXMLを取得および解析し、必要な情報を選択できます。 次に、同じXMLをXmlListModelに転送できます(URIだけでなく、XMLファイルのコンテンツも渡すことができます)。

したがって、現在XMLHttpRequestがあらゆるものに使用されているという事実にもかかわらず、なぜ作成されたのか、XMLを操作するためのツールもあることを忘れてはなりません。

短い要約

QMLには、ブラウザでJavaScript用に使用できる多くのツールが含まれています。 XMLHttpRequestを使用すると、HTTP要求を送信できるため、QMLアプリケーションとWebリソースを確実に統合できます。 XHRを使用すると、多くの場合、C ++を使用せずにサーバーとデータを交換できるため、開発が簡単になります。

Source: https://habr.com/ru/post/J176665/


All Articles