の続きです。
前回の記事で、以下実装概要の1~2までとりあげました。
今回の記事では実装概要3を取り上げます。
実装概要
- WSHオブジェクトを使用して、クローム起動
- XMLHttpオブジェクトを使用して「localhost:9222/json」Http通信(Get)を行う
VBAの文字列操作関数を使用して、レスポンス(JSON)から必要情報取得 - WindowsAPI(WinHttp関連のAPI)を使用して「WebSocketDebuggerURL」にHttp通信を行う。
- WindowsAPI(WinHttp関連のAPI)を使用して、HTTP通信をWebSockt通信にUpGradeさせる。
- WindowsAPI(WebSocket関連のAPI)を使用して、CDPメソッドの送信、レスポンスの受信を行う。
※注意※
コードは本来、全てクラスで実装しますが、わかりやすさのため、
以下のような記述をしています。
- 該当箇所のみを標準モジュールに記述したような形式でコード例を記載
- リテラルは本来は定数宣言しますが、そのままリテラルで記載
- グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
ここからが続き
以下のような記述をしています。
- 該当箇所のみを標準モジュールに記述したような形式でコード例を記載
- リテラルは本来は定数宣言しますが、そのままリテラルで記載
- グローバル変数なんてもちろん本来は使用しませんが、コード例では使用。
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
ここからが続き
(クラス内でメンバ変数宣言、およびプロパティ設定するものとお考え下さい)
<実装3.「WebSocketDebuggerURL」にHTTP通信を行う。>
ここから難易度が上がりはじめます。
※ここからはまず全体コードを掲載して、それに続けて個別箇所を説明します。
※各所でWinAPIを使用していますが、先にWinAPIの宣言を
だらだらと並べ立てられてもわかりづらいだけだと思うので、
個別箇所の説明時にWinAPIの宣言も掲載します。
Public hSessionHandle_ As LongPtr
Public hConnectionHandle_ As LongPtr
Public hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
' 1 WinHttp初期化
hSessionHandle_ = WinHttpOpen(StrPtr("Client"), _
WINHTTP_ACCESS_TYPE_NO_PROXY, 0, 0, WINHTTP_FLAG_SYNC )
If hSessionHandle_ = 0 Then GoTo quit
' 2 Httpコネクション情報設定
hConnectionHandle_ = WinHttpConnect(hSessionHandle_, StrPtr("127.0.0.1"), 9222, 0)
If hConnectionHandle_ = 0 Then GoTo quit
' 3 HTTPリクエストを作成
hRequestHandle_ = WinHttpOpenRequest(hConnectionHandle_, _
StrPtr("GET"), StrPtr(Path), StrPtr("HTTP/1.1"), 0, 0, 0)
If hRequestHandle_ = 0 Then GoTo quit
' 4 HTTPリクエスト送信
Dim result As Long: result = 0
result = WinHttpSendRequest(hRequestHandle_, WINHTTP_NO_ADDITIONAL_HEADERS, _
0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)
If (result = 0) Then GoTo quit
' 5 HTTPレスポンス受信
result = WinHttpReceiveResponse(hRequestHandle_, 0)
If (result = 0) Then GoTo quit
' 6 ステータスコード 200確認。
If IsResponseStatus(200) = False Then GoTo quit
HTTPConnect = True
Exit Function
quit:
HTTPConnect = False
End Function
Public Function IsResponseStatus(CheckStatusNumber As Long) As Boolean
Dim statusCodeBuffer As Long 'ステータスコード受信バッファ
Dim bufferSize As Long: bufferSize = 4 ' 受信バッファサイズ(Long=4バイト)
Dim result As Long
result = WinHttpQueryHeaders(hRequestHandle_, _
(WINHTTP_QUERY_STATUS_CODE Or WINHTTP_QUERY_FLAG_NUMBER), _
WINHTTP_HEADER_NAME_BY_INDEX, _
statusCodeBuffer , bufferSize , WINHTTP_NO_HEADER_INDEX)
If (result = False) Then GoTo quit
'ステータスコード確認。
If statusCodeBuffer <> CheckStatusNumber Then
Debug.Print "ステータスコード:" & statusCodeBuffer & "/" & CheckStatusNumber & "以外"
GoTo quit
End If
IsResponseStatus = True
Exit Function
quit:
IsResponseStatus = False
End Function
Private Sub CloseHInternetHandles()
If hwebsockethandle_ <> 0 Then
WinHttpCloseHandle (hwebsockethandle_)
hwebsockethandle_ = 0
End If
If hRequestHandle_ <> 0 Then
WinHttpCloseHandle (hRequestHandle_)
hRequestHandle_ = 0
End If
If hConnectionHandle_ <> 0 Then
WinHttpCloseHandle (hConnectionHandle_)
hConnectionHandle_ = 0
End If
If hSessionHandle_ <> 0 Then
WinHttpCloseHandle (hSessionHandle_)
hSessionHandle_ = 0
End If
End Sub
概要
このコードでやっていることは、WebDebuggingURLとHTTP通信を確立することだけです。
(その処理をHTTPConnectという自作関数にまとめています。)
処理内容としては、以下の順でWinHTTP関連のAPIを呼び出しています。
WinHttpOpen
→WinHttpConnect
→WinHttpOpenRequest
(HTTPリクエストの準備)
→WinHttpSendRequest
(HTTPリクエスト送信)
→WinHttpReceiveResponse
(HTTPレスポンス受信)
→WinHttpQueryHeaders
(レスポンスのヘッダーを確認し、ステータスコードが200であるか確認)
上記が全て成功すればHTTP接続は完了なので、処理を進め次の実装4に進みます。
※もし途中で失敗した場合は、HTTPConnectをFalseで抜け、呼び出し側で、
CloseHInternetHandles()というハンドル解放処理をまとめた自作関数を呼び、
そこでHTTPConnectが失敗するまでの間に取得したハンドルをまとめて解放してから、
接続失敗として処理を終了します。
<以下コード内容の個別箇所説明>
ここからWinAPIの宣言も一緒に掲載します
コメントで、そのAPI関数の機能、戻り値、引数の説明も記載します。
※注意※
説明内容はMSのレファレンスをDeepLで翻訳し、わかりづらいところは意訳し、
全体的にかなり端折って抜粋したものです。(端折っても長いです・・・)
あくまで大枠をつかむこと、および今回の実装に焦点をあてた説明です。
そのため正確な情報はMSの各レファレンスをご参照ください。
コメント箇所1:WinHttp初期化
'API説明: WinHTTPの内部の構造体を初期化する。
'※この関数の戻り値であるセッションハンドルは、アプリ終了時に
' WinHttpCloseHandle関数を呼び出して解放が必要です。
'戻り値:成功した場合は有効なセッションハンドル。失敗の場合は0。
Public Declare PtrSafe Function WinHttpOpen Lib "WinHttp" ( _
ByVal pszAgentW As LongPtr, _
ByVal dwAccessType As Long, _
ByVal pszProxyW As LongPtr, _
ByVal pszProxyBypassW As LongPtr, _
ByVal dwFlags As Long _
) As LongPtr
'引数
'引数1:HTTP通信を行うクライアント名を格納したString変数へのポインタ。
'この名前は、HTTPヘッダーのユーザーエージェントとして設定される。→なんでもよい
'引数2:アクセスの種類(プロキシの使用について定数を指定する)→今回は以下を使用
Public Const WINHTTP_ACCESS_TYPE_NO_PROXY = 1
'引数3:引数2がWINHTTP_ACCESS_TYPE_NAMED_PROXYの場合には
'使用するプロキシ名を格納したString変数へのポインタ。
'それ以外の場合、WINHTTP_NO_PROXY_NAME(0)に設定する。→今回それ以外なので0を指定。
'引数4:引数2がWINHTTP_ACCESS_TYPE_NAMED_PROXYの場合にはプロキシを通さない
'ホスト名またはIPアドレス、あるいはそれらをセミコロンで区切ったリストを格納する
'文字列変数へのポインタ。プロキシ経由のリストとして使用のため空文字列は指定不可。
'それ以外の場合、WINHTTP_NO_PROXY_BYPASS(0) に設定する。
'→今回はそれ以外なので0を指定。
'引数5:セッション同期か非同期かを示す定数を指定。
'→今回は以下定数を指定(同期設定)
Public Const WINHTTP_FLAG_SYNC = 0
Public hSessionHandle_ As LongPtr
Public hConnectionHandle_ As LongPtr
Public hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
' 1 WinHttp初期化
hSessionHandle_ = WinHttpOpen(StrPtr("Client"), _
WINHTTP_ACCESS_TYPE_NO_PROXY, 0, 0, WINHTTP_FLAG_SYNC )
If hSessionHandle_ = 0 Then GoTo quit
コメント箇所1の説明
HTTPリクエストの準備として、まずはWinHttpOpen
関数を呼び出して、初期設定を行っています。
引数に与えているのは、HTTPヘッダーのUserAgentに設定するクライアント名や、プロキシ使用に関する定数、セッション同期を表す定数だけですね。
成功すると引数に与えた内容が設定されたセッションハンドルが返ってきます。
MSのレファレンスを読むと、プロキシ情報を設定する引数について細かく記載されていますが、今回の通信相手はローカル内のChromeであり、Chromeが外部とやり取りする際のプロキシ設定ではないので、あまり気にしなくとも良いと思います。
コメント箇所2:Httpコネクション情報設定
'説明:HTTPリクエストの通信先を指定し、HTTPコネクション情報の設定を行う。
'※この関数は、特定のリソースへの要求が行われるまで、HTTPサーバーへの実際の接続にはならない。
'戻り値:成功の場合は有効なコネクションハンドル。失敗の場合は0。
Public Declare PtrSafe Function WinHttpConnect Lib "WinHttp" ( _
ByVal hSession As LongPtr, _
ByVal pswzServerName As LongPtr, _
ByVal nServerPort As Long, _
ByVal dwReserved As Long _
) As LongPtr
引数
'引数1:WinHttpOpenで取得したセッションハンドル。
'引数2:HTTPサーバーのホスト名を格納した文字列変数へのポインタを指定。
'(ホスト名とあるが、URLのホスト名~ドメイン部分まで。パスは含まず)
'文字列変数には、例えば 10.0.1.45 のように、サイトのIPアドレスを格納してもOK。
'→通信先であるクロームはローカルにあるのでループバックアドレス"127.0.0.1"を指定。
'引数3:接続先サーバーのTCP/IPポートを指定する整数。
'→クロームのDebuggingPortの9222を指定
'引数4:予約領域。0指定。
Public hSessionHandle_ As LongPtr
Public hConnectionHandle_ As LongPtr
Public hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
・・・
' 2 Httpコネクション情報設定
hConnectionHandle_ = WinHttpConnect(hSessionHandle_, StrPtr("127.0.0.1"), 9222, 0)
If hConnectionHandle_ = 0 Then GoTo quit
コメント箇所2の説明
HTTPリクエストの準備として次にWinHttpConnect
関数を呼び出しています。
(Connectという名前が紛らわしいですが、まだ通信は行われません。)
引数に与えているのは、WinHttpOpne
で取得したセッションハンドル、通信先であるクロームはローカルにあるのでループバックアドレス、それとクロームのデバッグポート9222です。
要するにセッションハンドルと通信先の情報を引数に設定しています。
成功するとコネクションハンドルが戻り値として取得できます。
このコネクションハンドルは設定した通信先の情報とセッションハンドルをラップして保持しているものといったイメージをもつと分かりやすいかもしれません。
コメント箇所3:Httpリクエストを作成
'説明:HTTPリクエストハンドルを作成し、そのハンドルに指定されたパラメータを格納。
'戻り値:成功した場合は有効なリクエストハンドル、失敗した場合は0。
Public Declare PtrSafe Function WinHttpOpenRequest Lib "WinHttp" ( _
ByVal hConnect As LongPtr, _
ByVal pwszVerb As LongPtr, _
ByVal pwszObjectName As LongPtr, _
ByVal pwszVersion As LongPtr, _
ByVal pwszReferrer As LongPtr, _
ByVal ppwszAcceptTypes As LongPtr, _
ByVal dwFlags As Long _
) As LongPtr
'引数
'引数1:WinHttpConnect関数で取得したコネクションハンドル。
'引数2:リクエストに使用するHTTPメソッドを指定する文字列変数へのポインタを指定。
'GETメソッドなら"GET"、POSTメソッドなら"POST"を指定。※文字列はすべて大文字。
'引数3:リソースパスを指定する文字列へのポインタ。
'引数4:HTTP のバージョンを指定する文字列へのポインタ。
'引数5:Refererを指定する文字列へのポインタを指定。
'このパラメータが WINHTTP_NO_REFERER(0) に設定されている場合、
'参照元のドキュメントは指定されない。→今回は0を指定
'引数6:MIMEタイプを指定する文字列ポインタの配列へのポインタ。
'このパラメータが WINHTTP_DEFAULT_ACCEPT_TYPES(0)に設定されている場合、
'"text/*" タイプのドキュメントのみを受け付ける→今回は0を指定
'引数7:インターネットフラグの値を含む定数値。
'HTTPの場合には0を指定→HTTPなので0を指定
Public hSessionHandle_ As LongPtr
Public hConnectionHandle_ As LongPtr
Public hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
・・・
' 3 HTTPリクエストを作成
hRequestHandle_ = WinHttpOpenRequest(hConnectionHandle_, _
StrPtr("GET"), StrPtr(Path), StrPtr("HTTP/1.1"), 0, 0, 0)
If hRequestHandle_ = 0 Then GoTo quit
コメント箇所3の説明
httpリクエスト準備の最後として、WinHttpOpenRequest
関数を呼び出しています。
引数に与えているのは、WinHttpConnect
で取得したコネクションハンドル、HTTPメソッド種類、WebDebuggerUrlのリソースパス、HTTPバージョン、(今回は指定しませんがリファラー、MIMEタイプ、特定のフラグ定数)です。
※WebDebuggerUrlのリソースパスは<実装2.>で文字列操作し取得したものを、
HTTPConnectの呼び出し元が引数に設定する前提です。
成功するとリクエストハンドルが戻り値として取得できます。
このリクエストハンドルは、コネクションハンドルにさらにここで設定するリクエスト情報もラップしてまとめて保持するものというイメージをもつと分かりやすいかもしれません。
一旦ここまでの処理をまとめると、Httpリクエストを作成するために、WinHttpOpne
,WinHttpConnect
,WinHttpOpenRequest
の順にAPIが呼ばれ、
各APIごとに情報を設定して返されるハンドルが順番にラップされて、
最終的にリクエストハンドルができあがるといったイメージですね。
コメント箇所4:Httpリクエスト送信
'説明:HTTPリクエストをサーバーに送信する。
'WinHttpOpen関数の引数5で同期処理を指定している場合は、
'この関数を使用・成功後に、WinHttpReceiveResponseを呼び出すことができる。
'(同期処理ならSend→Receiveの順番を守れといっているだけ)
'この関数使用時に、リクエストヘッダーを追加することも可能。
'追加指定する場合、追加指定したリクエストヘッダーの名前と値は検証され、
'追加したヘッダーが正しく形成されておらず、'無効なヘッダーである場合、
'関数は失敗し、GetLastErrorはERROR_INVALID_PARAMETER(87) を返す。
'戻り値:成功した場合に TRUE を、そうでない場合に FALSE を返す。
Public Declare PtrSafe Function WinHttpSendRequest Lib "WinHttp" ( _
ByVal hRequest As LongPtr, _
ByVal lpszHeaders As LongPtr, _
ByVal dwHeadersLength As Long, _
ByVal lpOptional As LongPtr, _
ByVal dwOptionalLength As Long, _
ByVal dwTotalLength As Long, _
ByVal dwContext As Long _
) As Long
'引数
'引数1:WinHttpOpenRequestで取得したリクエストハンドル。
'引数2:リクエストにヘッダを追加する場合、追加ヘッダを指定する文字列へのポインタ。
'ヘッダ追加しない場合は、このパラメータはWINHTTP_NO_ADDITIONAL_HEADERS(0)でOK。
Public Const WINHTTP_NO_ADDITIONAL_HEADERS = 0
'引数3:ヘッダー追加する場合、その追加ヘッダーの文字数を指定。
'※引数2で追加ヘッダー指定している場合にこの引数に-1を指定すると、
'この関数は引数2の文字列数を自動で計算してくれる。
'→今回は追加ヘッダーは指定しないので0文字=0を指定。
'引数4:HTTPリクエストのBodyを格納するバッファへのポインタ。
'送信するBodyがなければ、このパラメータは WINHTTP_NO_REQUEST_DATA(0)でOK。
'※このバッファは、リクエストハンドルが閉じられるか、
'WinHttpReceiveResponseの呼出完了まで、利用可能な状態を維持する必要がある。
Public Const WINHTTP_NO_REQUEST_DATA = 0
'引数5:引数4のバイト単位のサイズ。引数4に指定するデータがない場合、0でOK。→0指定
'引数6:送信する全データのバイト単位のサイズ。
'この引数に設定する値はHTTPリクエストのContent-Lengthヘッダに設定される。
'この引数の値が引数5の値よりも大きい場合(body部を分割して送信する場合?)、
' WinHttpWriteDataを使用して追加のデータを送信することができる。
'→ヘッダ追加なし、Bodyなしなので0を指定
'引数7:アプリで定義された値を含むポインターサイズの変数へのポインターで、
'リクエストハンドルとともにコールバック関数に渡される。
'→今回コールバック関数の設定はしていないので0を指定。
Public hSessionHandle_ As LongPtr
Public hConnectionHandle_ As LongPtr
Public hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
・・・
'HTTPリクエスト送信
Dim result As Long: result = 0
result = WinHttpSendRequest(hRequestHandle_, _
WINHTTP_NO_ADDITIONAL_HEADERS, _
0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)
If (result = 0) Then GoTo quit
コメント箇所4の説明
HTTPリクエスト送信をするためにWinHttpSendRequest
関数を呼び出しています。
引数に与えているのは、リクエストハンドルだけです。
この関数でヘッダーを追加しようとしたり、PostメソッドなどでBodyがあると、引数設定が面倒になりますが、今回は何もないのでリクエストハンドル以外全て0です。
成功するとTrue,失敗するとFalse返ってきます。
コメント箇所5:Httpレスポンス受信
'説明:WinHttpSendRequestで送信されたリクエストに対するレスポンスを受信する。
'WinHttpReceiveResponseが正常に完了すると、ステータスコードと
'レスポンスヘッダーが受信され、WinHttpQueryHeadersを使用してレスポンスを
'確認することができるようになる。
'戻り値:成功した場合にTRUEを、失敗の場合FALSEを返す。
Public Declare PtrSafe Function WinHttpReceiveResponse Lib "WinHttp" ( _
ByVal hRequest As LongPtr, _
ByVal lpReserved As LongPtr _
) As Long
'引数1:WinHttpSendRequestでHTTPリクエスト送信時に使用したリクエストハンドル。
'引数2:予約領域。0を指定。
Private hSessionHandle_ As LongPtr
Private hConnectionHandle_ As LongPtr
Private hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
・・・
' 5 HTTPレスポンス受信
result = WinHttpReceiveResponse(hRequestHandle_, 0)
If (result = 0) Then GoTo quit
コメント箇所5の説明
HTTPレスポンスの受信をするためにWinHttpReceiveResponse
関数を呼び出しています。
引数に与えているのは、リクエスト送信に使用したリクエストハンドルだけです。
楽ですね。
成功するとTrue、失敗するとFalseが返ってきます。
コメント箇所6:ステータスコード200確認
'説明:HTTPレスポンスのヘッダー情報を取得。
'戻り値:成功した場合に TRUE を、失敗の場合にFALSEを返す。
Public Declare PtrSafe Function WinHttpQueryHeaders Lib "WinHttp" ( _
ByVal hRequest As LongPtr, _
ByVal dwInfoLevel As Long, _
ByVal pwszName As LongPtr, _
ByRef lpBuffer As Long, _
ByRef lpdwBufferLength As Long, _
ByRef lpdwIndex As Long _
) As Long
'引数1:レスポンス受信に使用したリクエストハンドル。
'引数2:属性フラグ(定数)と修飾子フラグ(定数)の組み合わせにより、
'どのヘッダー情報を取得するか、またその情報をどのフォーマットで取得するかを
'指示する。(この関数のデフォルトのフォーマットは文字列)
'ステータスコードを取得したい場合、
'WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBERを指定する。
Public Const WINHTTP_QUERY_STATUS_CODE = 19
'レスポンスヘッダの中からステータスコードを取得。
Public Const WINHTTP_QUERY_FLAG_NUMBER = &H20000000
'ステータスコードなど値が数値であるヘッダのデータを32ビットの数値で取得する指示。
'引数3:引数2に指定したフラグが WINHTTP_QUERY_CUSTOM(65535)の場合、
'取得したいヘッダーを指定する文字列へのポインタを設定。
'それ以外の場合、WINHTTP_HEADER_NAME_BY_INDEX(0)を設定する。
Public Const WINHTTP_HEADER_NAME_BY_INDEX = 0
'引数4:情報を受け取るバッファへのポインタを指定。
'引数5:引数4のバッファサイズをバイト単位で指定する値へのポインタ。
'関数が戻ったとき、この引数には、バッファに書き込まれた情報の
'サイズを示す値へのポインタが設定される。
'※この関数が文字列を返す場合には、以下のルールが適用される。
'関数が成功した場合、この引数には文字列の長さをバイト数で測定した値から
'終端NULLの2を差し引いた値となる。
'関数が失敗し、ERROR_INSUFFICIENT_BUFFERが返された場合、この引数には、
'アプリケーションが文字列を受け取るために割り当てる必要のあるバイト数が返される。
'引数6:同じ名前の複数のヘッダーを列挙するために使用される、base0の
'ヘッダーインデックスへのポインタを指定します。
'この関数を呼び出すと、この引数には指定されたヘッダのインデックスの
'次のインデックスが返される。
'次のインデックスが見つからない場合、
'ERROR_WINHTTP_HEADER_NOT_FOUND(12150)が返されます。
'このパラメータをWINHTTP_NO_HEADER_INDEX(0)に設定すると、
'ヘッダの最初の出現分のみを返すように指定することができます。
Public Const WINHTTP_NO_HEADER_INDEX = 0
Public hSessionHandle_ As LongPtr
Public hConnectionHandle_ As LongPtr
Public hRequestHandle_ As LongPtr
Public Function HTTPConnect(Path As String) As Boolean
・・・
' 6 ステータスコード 200確認。
If IsResponseStatus(200) = False Then GoTo quit
Public Function IsResponseStatus(CheckStatusNumber As Long) As Boolean
Dim statusCodeBuffer As Long 'ステータスコード受信バッファ
Dim bufferSize As Long: bufferSize = 4 ' 受信バッファサイズ(Long=4バイト)
Dim result As Long
result = WinHttpQueryHeaders(hRequestHandle_, _
(WINHTTP_QUERY_STATUS_CODE Or WINHTTP_QUERY_FLAG_NUMBER), _
WINHTTP_HEADER_NAME_BY_INDEX, _
StatusCodeBuffer , bufferSize , WINHTTP_NO_HEADER_INDEX)
If (result = False) Then GoTo quit
'ステータスコード確認。
If StatusCodeBuffer <> CheckStatusNumber Then
Debug.Print "ステータスコード:" & StatusCodeBuffer & "/" & CheckStatusNumber & "以外"
GoTo quit
End If
IsResponseStatus = True
Exit Function
quit:
IsResponseStatus = False
End Function
コメント箇所6の説明
最後にレスポンスのステータスコード確認のため、WinHttpQueryHeaders
関数を呼び出しますが、ここでは処理をIsResponseStatusという自作関数にまとめて、IsResponseStatus(200)と呼び出すことでチェックをしています。
ステータスコード200ならTrueが帰ってきてこれでHTTP接続が完了です。
WinHttpQueryHeaders
の引数には、ステータスコードを返すように指示する定数とステータスコードをLong型数値で返すように指示する定数をORで指定、取得したいヘッダー情報を文字列で指定するわけではないのでその旨を指示する定数、あとはステータスコードを格納するバッファおよびそのバッファサイズ、最後に同じ名前のヘッダ云々の話は関係ないので0を表す定数を指定しています。
実装3は以上です
また記事がかなり長くなってきてしまったので、いったんここで切ります。
続きはこちらです