の続きです。
前回の記事で、以下実装概要の3をとりあげました。
今回の記事では実装概要4を取り上げます。
実装概要
- WSHオブジェクトを使用して、クローム起動
- XMLHttpオブジェクトを使用して「localhost:9222/json」Http通信(Get)を行う
VBAの文字列操作関数を使用して、レスポンス(JSON)から必要情報取得 - WindowsAPI(WinHttp関連のAPI)を使用して「WebSocketDebuggerURL」にHttp通信を行う。
- WindowsAPI(WinHttp関連のAPI)を使用して、HTTP通信をWebSockt通信にUpGradeさせる。
- WindowsAPI(WebSocket関連のAPI)を使用して、CDPメソッドの送信、レスポンスの受信を行う。
※注意※
以下コードは本来、全てクラスで実装しますが、わかりやすさのため、以下のような記述をしています。
-
該当箇所のみを標準モジュールに記述したような形式でコード例を記載
-
リテラルは本来は定数宣言しますが、そのままリテラルで記載
-
グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
-
WinAPIのコメントでの説明内容はMSのレファレンスをDeepLで翻訳し、
わかりづらいところは意訳し、全体的にかなり端折って抜粋したものです。
あくまで大枠をつかむこと、および今回の実装に焦点をあてた説明です。
そのため正確な情報はMSのレファレンスをご参照ください。
ここからが続き
-
該当箇所のみを標準モジュールに記述したような形式でコード例を記載
-
リテラルは本来は定数宣言しますが、そのままリテラルで記載
-
グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
-
WinAPIのコメントでの説明内容はMSのレファレンスをDeepLで翻訳し、
わかりづらいところは意訳し、全体的にかなり端折って抜粋したものです。
あくまで大枠をつかむこと、および今回の実装に焦点をあてた説明です。
そのため正確な情報はMSのレファレンスをご参照ください。
ここからが続き
該当箇所のみを標準モジュールに記述したような形式でコード例を記載
リテラルは本来は定数宣言しますが、そのままリテラルで記載
グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
WinAPIのコメントでの説明内容はMSのレファレンスをDeepLで翻訳し、
わかりづらいところは意訳し、全体的にかなり端折って抜粋したものです。
あくまで大枠をつかむこと、および今回の実装に焦点をあてた説明です。
そのため正確な情報はMSのレファレンスをご参照ください。
<実装4. HTTP通信をWebSockt通信にUpGradeさせる>
※前回と同じく、まず全体コードを掲載し、それに続けて個別箇所を説明します。
※各所でWinAPIを使用していますが、API宣言については個別箇所の説明時に掲載します。
Public hwebsockethandle_ As LongPtr
Public Function UpgradeToWebSocket() As Boolean
'1 プロトコルをhttpからwebsocketにUpGradeするためのoptionをリクエストハンドルに設定)
Dim result As Long
result = WinHttpSetOption(hRequestHandle_, _
WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, 0, 0)
If (result = 0) Then GoTo quit
'2 サーバーにリクエスト送信
result = WinHttpSendRequest(hRequestHandle_, _
WINHTTP_NO_ADDITIONAL_HEADERS, _
0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)
If (result = 0) Then GoTo quit
'3 サーバーからのレスポンス受信
result = WinHttpReceiveResponse(hRequestHandle_, 0)
If (result = 0) Then GoTo quit
'4 レスポンスのステータスコード確認。101でアップグレード確認。
If IsResponseStatus(101) = False Then GoTo quit
'5 WebSocketハンドル取得
hwebsockethandle_ = WinHttpWebSocketCompleteUpgrade(hRequestHandle_, 0)
If hwebsockethandle_ = 0 Then GoTo quit
' Debug.Print "Upgrade成功"
UpgradeToWebSocket = True
Exit Function
quit:
UpgradeToWebSocket = False
End Function
概要
このコードでは、ChromeのWebDebuggingURLとのHTTP接続について、
プロトコルをHTTP→WebSoketにUpgradeする処理を行っています。
(その処理をUpgradeToWebSocketという自作関数にまとめています。)
補足ですが、プロトコルをHTTP→WebSocketにUpgradeするためには
以下処理が必要です。
- HTTPリクエストにUpgradeするのに必要なヘッダー情報を追加する
- HTTPリクエストを投げる
- HTTPレスポンスでステータスコードが101になっている事を確認する
- Upgradeを完了させる
そのため処理内容としては、以下の順でWinHTTP関連のAPIを呼び出す内容となっています。
WinHttpSetOption
(HTTPリクエストにHTTP→WebSocketへUpgradeするのに必要なヘッダー情報を追加)
→WinHttpSendRequest
(HTTPリクエスト送信)
→WinHttpReceiveResponse
(HTTPレスポンス受信)
→WinHttpQueryHeaders
(レスポンスのヘッダーを確認し、ステータスコードが101であるか確認)
→WinHttpWebSocketCompleteUpgrade
(Upgradeを完了させ、今後のWebSocket通信に必要なウェブソケットハンドルを取得)
上記が全て成功すればHTTP→WebSocketへのアップグレードは完了なので、処理を進め次の実装5に進みます。
※もし途中で失敗した場合は、UpgradeToWebSocketをFalseで抜け、
<実装3.(前回の記事参照)>同様、呼び出し側で、CloseHInternetHandles()という
ハンドル解放処理をまとめた自作関数を呼び、
取得済みのハンドルをまとめて解放してから、接続失敗として処理を終了します。
<以下、コード内容の個別箇所説明>
ここからWinAPIの宣言も一緒に掲載します。
コメントで、そのAPI関数の機能、戻り値、引数の説明も記載します。
コメント箇所1:プロトコルをhttpからwebsocketにUpgradeするためのoptionをリクエストハンドルに設定)
'説明:インターネットオプションを設定
'戻り値:成功した場合TRUE、失敗した場合FALSEを返す。
Public Declare PtrSafe Function WinHttpSetOption Lib "WinHttp" ( _
ByVal HINTERNET As LongPtr, _
ByVal dwOption As Long, _
ByVal lpBuffer As LongPtr, _
ByVal dwBufferLength As Long _
) As Long
'引数1:データを設定するハンドルを指定。設定するオプションによって、
'セッションハンドルまたはリクエストハンドルのいずれかを指定する。
'→今回はリクエストハンドルにオプション設定を行う。=リクエストハンドルを指定。
'引数2:設定するオプションを示す定数。
'プロトコルをHTTP→WebSoketにUpgradeさせるためには以下の定数を指定する。
Public Const WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114
'引数3:オプションに関する設定情報を格納するバッファへのポインタ。
'→WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKETは別途の設定情報は不要=0を指定。
'引数4:引数3のバッファーのサイズを指定。引数2で指定するオプションにより
'文字数で指定するか、バイト数で指定するかが異なる。
'→今回はそもそも引数3のバッファ指定していないので0を指定。
Public hwebsockethandle_ As LongPtr
Public Function UpgradeToWebSocket() As Boolean
'1 プロトコルをhttpからwebsocketにUpgradeするためのoptionをリクエストハンドルに設定
Dim result As Long
result = WinHttpSetOption(hRequestHandle_, _
WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, 0, 0)
If (result = 0) Then GoTo quit
コメント箇所1の説明
ここでは、「HTTPリクエストに必要なヘッダー情報を追加する」ために
WinHttpSetOption
関数を呼び出しています。
この関数の第二引数に
WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET(114)
という定数を指定するだけで、Upgradeに必要なヘッダーは全て自動で追加されます。
そのため、WinHttpSetOption
関数の引数に与えているのは、
<実装.3(前回の記事参照)>で取得済みのリクエストハンドルとWINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET定数だけです。
(この定数は引数3,4が不要)
成功するとTrue、失敗するとFalseが戻ります。
参考:HTTP→WebSocketにUpgradeするために必要なヘッダー
- Host: 例)example.com
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Version: 13
※現時点では「13」を指定 - Sec-WebSocket-Key: 例)AQIDBAUGBwgJCgsMDQ4PEA==
※値は一回限り有効な長さ16のランダムなバイト列をbase64に符号化した値。
復号した結果は長さ16バイトになるもの。
※RFC(4. opening ハンドシェイクー4.1. クライアントに課される要件ー5の「サーバへの接続が確立されたなら~次に挙げるすべての要件を満たさなければならない」という箇所の後のくだり )を読むと上記5つが必須ヘッダで、他にも存在するWebSocket関連のヘッダーは任意だと思われます。
https://triple-underscore.github.io/RFC6455-ja.html#section-7.1.2
※WinHttpSetOptionの第二引数に
WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKETを指定すれば
上記5つのヘッダがリクエストに自動で追加されます。
MSのレファレンスではそういった情報が見つけられず、
最初は「自分で追加する必要あるのだろうか?」と悩んだのですが、
パケットキャプチャしてみたら、追加されていることが確認できました。
コメント箇所2,3,4:
サーバーにリクエスト送信
サーバーからのレスポンス受信
レスポンスのステータスコード確認。101でアップグレード確認。
'使用しているAPIは、<実装3.>で宣言済みのため省略
Public hwebsockethandle_ As LongPtr
Public Function UpgradeToWebSocket() As Boolean
・・・
'2 サーバーにリクエスト送信、websocketハンドシェイクを行う。
result = WinHttpSendRequest(hRequestHandle_, _
WINHTTP_NO_ADDITIONAL_HEADERS, _
0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)
If (result = 0) Then GoTo quit
'3 サーバーからのレスポンス受信
result = WinHttpReceiveResponse(hRequestHandle_, 0)
If (result = 0) Then GoTo quit
'4 レスポンスのステータスコード確認。101でアップグレード確認。
If IsResponseStatus(101) = False Then GoTo quit
コメント箇所2,3,4の説明
ここではHTTPリクエストの送信→レスポンス受信→ステータスコード確認を行っています。
使用するWinAPIおよび引数に与えている内容は<実装3.(前回の記事参照)>の内容と同じであるため、説明は省略します。
一点だけ補足として、HTTP→WebSocketにUpgradeするためのリクエストを送信し、サーバー側がUpgradeに応じると、サーバーはレスポンスのステータスコードを101に設定して返してきます。
ステータスコード200が返ってきてもUpgradeはできないのでご注意ください。
コメント箇所5:WebSocketハンドル取得
'説明:WinHttpSendRequestによって開始されたWebSocketハンドシェイクを完了させ、
'WebSocket通信に必要なWebSocketハンドルを取得。
'この関数は、HTTPレスポンスのステータスコードが101であることを確認した上で
'呼び出すこと。それ以外のステータスコードで呼び出すと失敗する。
'戻り値:成功の場合、WebSocketハンドル。失敗の場合0。
Public Declare PtrSafe Function WinHttpWebSocketCompleteUpgrade Lib "WinHttp" ( _
ByVal hRequest As LongPtr, _
ByVal pContext As LongPtr _
) As LongPtr
'引数1:Http→WebSocketへのUpgradeに使用したリクエストハンドル。
'引数2:新しいハンドルに関連付けられるコンテキスト。
'※この引数は正直理解しきれていません。
'MSのレファレンスにもこの一文しか説明がありません。
'もし「コンテキスト」=「APサーバのWebアプリのこと、もしくはWebアプリの配置場所や呼び出し方」
'のようなことを指しているのであれば、今回のクローム操作には関係ないだろうと考えたこと、
'及びレファレンスではoptional扱いの引数のため、一旦0を設定しています。
'もし正確にわかる方がいれば教えてほしいです・・・。
Public hwebsockethandle_ As LongPtr
Public Function UpgradeToWebSocket() As Boolean
・・・
'5 WebSocketハンドル取得
hwebsockethandle_ = WinHttpWebSocketCompleteUpgrade(hRequestHandle_, 0)
If hwebsockethandle_ = 0 Then GoTo quit
コメント箇所5の説明
最後にUpgradeを完了させ、今後のWebSocket通信で使用するウェブソケットハンドルを取得するために、WinHttpWebSocketCompleteUpgrade
関数を呼び出しています。
引数の設定は、リクエストハンドルのみです。
成功するとWebSocketハンドル、失敗の場合0が戻ります。
実装4は以上です
また記事が長くなってきたので、いったんここで切ります。
続きはこちらです。