トップ回答者
HTTPレスポンスをStream等で取得する方法について

質問
-
こんにちは。
.net framework 4.6が利用できる環境で
HTTPを使ってGETやPOSTなど、外部アクセスしたいと考えています。標準のライブラリでは
System.Net.WebRequet
System.Net.WebClient
System.Net.Http.HttpClientなど、多数のライブラリがあってそれを使えば可能なのですが
いずれもレスポンス内容が抽象化されています。
今回は、HTTPサーバーから返されたヘッダーやボディの内容をそのままStreamやStringで取得したいです。
System.Net.Socket.TcpClient を使ってTCPレベルでHTTPサーバーと通信すれば可能だとは思いますが
リダイレクトなどHTTP特有の仕様が多数あることを考えるとTcpClientで開発を始めるのは無謀な予感です。
HTTPサーバーから返されるヘッダーやボディの内容を出来るだけ生の状態でStreamやStringで取得できるような
ライブラリやメソッドはあるのでしょうか?
よろしくお願い致します。
- 編集済み CharAzurable 2017年2月18日 14:21
回答
-
もう一案。
.NET Core 用の HttpClient のソースコードが公開されている
https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
ので、どうしようもない場合はこれを改造する、という手もありますね。
- 回答としてマーク CharAzurable 2017年2月22日 3:28
すべての返信
-
ご回答下さりありがとうございます。
ご指摘のように、ライブラリにHTTP解析を任せると、解析された抽象型に変換されてしまい、
程よく生のデータを得られるものが意外と無い、というのが今回の質問の経緯です。
デバッグ用にネットワークトレースがしたい、という訳ではありませんが
Fidderや各ブラウザの開発ツール系のように、各リクエストに対するレスポンスが関連付けされていて
レスポンスは出来る限り生の状態で取得して、例えばログに記録する、等が出来れば良いと思います。
また、以降は自論となりますが、
HTTPレスポンスは本質的にMIMEなので、MIME文書としてヘッダとボディーを一括して取り扱いたいのです。
各ライブラリを利用すると、ヘッダとボディーがどうしても分断されてしまい、元のMIME文書に戻す工程が必要になってしまい、また、レスポンスとは異なるMIME文書となってしまいます。
-
ご回答ありがとうございます。
HttpClientHandlerもHttpClientもレスポンスが
HttpResponseMessageに抽象化されています。(HttpContent)
ここからヘッダ+ボディの生Streamが取れれば希望通りとなります。
System.Net.Httpは違いがわからない似たようなクラスが多いので
対処できるものがあると良いのですが。。。
- 編集済み CharAzurable 2017年2月20日 12:26
-
修正して再投稿します。
ご指摘の通り、かなり都合の良い「自称生Stream」が欲しいということになります。
なんでもかんでも生Streamが良い、ということであればTcpClientを使えばよい、ということになりますが、そこまでするつもりはありません。
具体的には、生のMIME文書が欲しい、という事になります。
Content-Encoding:~は、MIMEの仕様なので、そのままで構いません。
Transfer-Encoding:~関連はライブラリ側でStreamとして取得するところまでは勝手にやっておいてほしい、というイメージです。
主にサーバー間のWEBAPIでの連携を考えているので、HTTP/2を今のところ意識する必要はありませんが
最初に書いた通りリダイレクト等は勝手に処理しておいてほしいです。
- 編集済み CharAzurable 2017年2月20日 13:32
-
WinHTTP はどうでしょう。
Dim req As New WinHttp.WinHttpRequest() req.Open("GET", "https://social.msdn.microsoft.com/profile/charazurable", False) req.Send(Nothing) Dim headers As String = req.GetAllResponseHeaders() Dim bodyText As String = req.ResponseText 'Dim bodyStream = DirectCast(req.ResponseStream, System.Runtime.InteropServices.ComTypes.IStream) System.Runtime.InteropServices.Marshal.ReleaseComObject(req)
- 編集済み 魔界の仮面弁士MVP 2017年2月21日 1:38
-
WinHTTPというものは初めて知りました。
Winhttp.dll の API 群です。P/Invoke での呼び出しと、COM Interop での呼び出しの両方に対応しています。Windows Update で利用されている API でもあり、サービスからの利用にも向いています(WinHTTP = "Windows HTTP Services")。
同種のライブラリには、WinInet があります。こちらは P/Invoke なインターフェイスです。一応、COM 版の msinet.ocx というのもありますが、.NET から呼ぶなら P/Invoke の方が良いでしょう。こちらは、サービスからの利用には向きません (KB238425)。
先に挙がっていた Fiddler などは、WinInet による実装です。
GetAllResponseHeaders() と ResponseText,ResponseStream というメソッド名から推察するに
やはりHeaderとボディを分離して取得しているように見えます。どういう結果を望んでおられるのか分かりませんでした。生のストリームを必要としているわけではなかったはずですよね?
「Transfer-Encoding: chunked」は結合して受け取りたいのことでしたが、ということは分割送信されてきたチャンクを結合する必要があるのですから、どちらにせよ応答ボディの分割と再結合はどうしたって発生すると思いますし、その過程で応答メッセージのヘッダとボディを分けるのは至極自然だと思うのですが…。後からヘッダーと結合するのでは駄目なのですか?
- 編集済み 魔界の仮面弁士MVP 2017年2月22日 0:17 サービスでの利用について補足
-
たとえば下記のレスポンスの場合、Body のチャンクを結合すると「hello world」という 11 バイトになると思いますが、この場合、どのような Stream が得られるのが理想でしょうか。(特に代替案を持ち合わせているわけでは無いですが)
HTTP/1.1 200 OK<CRLF> Date: Tue, 21 Feb 2017 03:25:26 GMT<CRLF> Content-Type: text/plain; charset=us-ascii<CRLF> Transfer-Encoding: chunked<CRLF> Trailer: Content-MD5<CRLF> <CRLF> 6<CRLF> hello <CRLF> 5<CRLF> world<CRLF> 0<CRLF> <CRLF> Content-MD5: XrY7u+Ae7tCTyyK7j1rNww==<CRLF>
-
Transfer-Encoding:~関連はライブラリ側でStreamとして取得するところまでは勝手にやっておいてほしい、というイメージです。
「Transfer-Encoding: gzip, chunked」などが返されたとしたら、チャンクの結合や gzip のデコードを『勝手にやっておく』という意味に捕えていたのですが、もしかしてその必要は無いのでしょうか? だとすると、先の WinHTTP 案も NG です。
私が思い当たるのは、佐祐理さんも紹介されていた、Fiddler を使う手法ぐらいです。
Fiddler.FiddlerApplication.OnReadResponseBuffer += new EventHandler<Fiddler.RawReadEventArgs>((obj, arg) => { byte[] buf = new byte[arg.iCountOfBytes]; Buffer.BlockCopy(arg.arrDataBuffer, 0, buf, 0, arg.iCountOfBytes); // Console.Write(System.Text.Encoding.Default.GetString(buf)); }); Fiddler.FiddlerApplication.Startup(0, Fiddler.FiddlerCoreStartupFlags.ChainToUpstreamGateway); System.Net.HttpWebRequest req = System.Net.HttpWebRequest.CreateHttp(url); req.Proxy = new System.Net.WebProxy("localhost", Fiddler.FiddlerApplication.oProxy.ListenPort); /* ** res.GetResponse() 等の処理 */ Fiddler.FiddlerApplication.Shutdown();
受信データを直接得るような機能が無いか、reference source も探ってみましたが、生の通信データを取り込めるような機能は今のところ見つけられませんでした。
app.config で、System.Net.Sockets に自作のリスナーを仕掛ければ、Socket で受信されたデータの内容を下記のように得られるようになっていましたが、加工済みの文字列が void WriteLine(string) に渡されるだけの実装に過ぎず、直接 byte[] や Stream で受け取れる仕組みは見当たりませんでした。
[8700] 00000000 : 48 54 54 50 2F 31 2E 31-20 32 30 30 20 4F 4B 0D : HTTP/1.1 200 OK. [8700] 00000010 : 0A 44 61 74 65 3A 20 54-75 65 2C 20 32 31 20 46 : .Date: Tue, 21 F [8700] 00000020 : 65 62 20 32 30 31 37 20-30 33 3A 32 35 3A 32 36 : eb 2017 03:25:26 [8700] 00000030 : 20 47 4D 54 0D 0A 43 6F-6E 74 65 6E 74 2D 54 79 : GMT..Content-Ty [8700] 00000040 : 70 65 3A 20 74 65 78 74-2F 70 6C 61 69 6E 3B 20 : pe: text/plain; [8700] 00000050 : 63 68 61 72 73 65 74 3D-75 73 2D 61 73 63 69 69 : charset=us-ascii [8700] 00000060 : 0D 0A 54 72 61 6E 73 66-65 72 2D 45 6E 63 6F 64 : ..Transfer-Encod [8700] 00000070 : 69 6E 67 3A 20 63 68 75-6E 6B 65 64 0D 0A 54 72 : ing: chunked..Tr [8700] 00000080 : 61 69 6C 65 72 3A 20 43-6F 6E 74 65 6E 74 2D 4D : ailer: Content-M [8700] 00000090 : 44 35 0D 0A 0D 0A : D5....
[8700] 00000000 : 36 0D 0A 68 65 6C 6C 6F-20 0D 0A 35 : 6..hello ..5
[8700] 00000001 : 0D 0A 77 6F 72 6C 64 0D-0A 30 0D 0A : ..world..0..
[8700] 00000000 : 0D 0A 43 6F 6E 74 65 6E-74 2D 4D 44 35 3A 20 58 : ..Content-MD5: X [8700] 00000010 : 72 59 37 75 2B 41 65 37-74 43 54 79 79 4B 37 6A : rY7u+Ae7tCTyyK7j [8700] 00000020 : 31 72 4E 77 77 3D 3D 0D-0A : 1rNww==..
-
もし Fiddler でダメな場合、自力で(プログラム内部に) HTTP Proxy 的なものを作って、それを介して HTTP リクエスト→レスポンスを得るようにするくらいしか方法がないカモです。(理由は既に述べられている通りで、完全な生でも都合が悪いということなので)
割とハードルは高めですが、標準クラスライブラリの
- HttpListener
- HttpClient
の組み合わせで実現できるカモ。
あるいは、NuGet や GitHub に HTTP Proxy のライブラリ/コードが落ちているので、それを使ってみるとかでしょうか。
個人的には
>HTTPサーバーから返されるヘッダーやボディの内容を出来るだけ生の状態でStreamやStringで取得
が単なる趣味領域の話であるなら、保留・先送りにして先に進んだ方がよいのではないかと思います。
業務で、要件だとしたらある程度仕方ない場合もありますが、機能外要件に沢山工数かけてもコストがかさむだけで詮無いかなーと思います。
例えば、プロクシを挟んでそこでログがとれるんなら、それでもいいんじゃないかと思います。僕的には。
- 編集済み 渋木宏明 2017年2月21日 23:54
-
もう一案。
.NET Core 用の HttpClient のソースコードが公開されている
https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
ので、どうしようもない場合はこれを改造する、という手もありますね。
- 回答としてマーク CharAzurable 2017年2月22日 3:28
-
みなさま、ご回答下さりありがとうございます。
私自身、何がしたいのか何をするべきなのか不明瞭な部分が多かったですが
みなさまのご意見を聞き、かなりクリアになってきました。
Content-Encoding と Transfer-Encoding は
TCP と IP のように階層レベルで似て非なるものであることを再確認すれば
どの状態のStreamが必要なのか、明瞭になりそうです。
今回は、ご案内いただいた.net Coreの各ソースコードも追いながら、System.Net.Httpの
HttpContentからストリーム化する方法で対処しようと思っています。
派生クラスやメソッドが多数あるのですが、日本語ドキュメントが十分でないので
.net coreであれソースコードは参考にさせていただこうと思います。
FidderをコードレベルでProxyにセットする方法もとても興味深く参考になりました。
今回はトレースが目的ではありませんでしたが、活用してみたいと思っています。
最後のご意見を回答とさせていただきましたが、大変参考になりました。みなさまありがとうございました。