none
HTTPレスポンスをStream等で取得する方法について RRS feed

  • 質問

  • こんにちは。

    .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で取得できるような
    ライブラリやメソッドはあるのでしょうか?


    よろしくお願い致します。


    2017年2月18日 14:20

回答

  • もう一案。

    .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
    2017年2月21日 23:58

すべての返信

  • HttpClient.GetStreamAsync() などではだめなのでしょうか?


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    2017年2月20日 2:31
  • ライブラリにHTTPの解析を任せるのであれば、解析結果が得られるというのは当然のことではありませんか? というのも1つのTCP接続で複数のRequest / Responseが流れます。生のStreamを見せるのは無理な話です。

    質問に至った背景を提示していただけたら、何か別のアプローチがあるかもしれません。例えばFiddlerとか。

    2017年2月20日 2:47
  • ご回答ありがとうございます。

    HttpClient.GetStreamAsync() 

    で得られるものは、レスポンスボディーの部分のみで、ヘッダーが含まれません。

    HeaderはHeaderプロパティを見て、逆に組み立てることは可能ですが

    出来ればヘッダーも含めた生の状態を取得したいと考えています。

    ただ、今のところ私の結論としては、Headerプロパティーからのリビルド + HttpClient.GetStreamAsync()

    が最も希望に近いものです。

    2017年2月20日 10:41
  • ご回答下さりありがとうございます。

    ご指摘のように、ライブラリにHTTP解析を任せると、解析された抽象型に変換されてしまい、

    程よく生のデータを得られるものが意外と無い、というのが今回の質問の経緯です。

    デバッグ用にネットワークトレースがしたい、という訳ではありませんが

    Fidderや各ブラウザの開発ツール系のように、各リクエストに対するレスポンスが関連付けされていて

    レスポンスは出来る限り生の状態で取得して、例えばログに記録する、等が出来れば良いと思います。

    また、以降は自論となりますが、

    HTTPレスポンスは本質的にMIMEなので、MIME文書としてヘッダとボディーを一括して取り扱いたいのです。

    各ライブラリを利用すると、ヘッダとボディーがどうしても分断されてしまい、元のMIME文書に戻す工程が必要になってしまい、また、レスポンスとは異なるMIME文書となってしまいます。

    2017年2月20日 10:59
  • もしかしたら、HttpClientHandlerが使えるかもしれません(あるいは派生クラスでカスタマイズ)。

    ※未確認ですのであしからず

    --追記

    あんまりローデータにアクセスするようなハンドリングはできなさそうな雰囲気ですね。ちょっと無理かも。

    • 編集済み なちゃ 2017年2月20日 11:54
    2017年2月20日 11:47
  • ご回答ありがとうございます。

    HttpClientHandlerもHttpClientもレスポンスが
    HttpResponseMessageに抽象化されています。(HttpContent)
    ここからヘッダ+ボディの生Streamが取れれば希望通りとなります。

    System.Net.Httpは違いがわからない似たようなクラスが多いので
    対処できるものがあると良いのですが。。。


    2017年2月20日 12:24
  • 疑問なのですが、質問者さんの求める「生Stream」とはどのようなものを想定されているのでしょうか?

    Content-Encoding: gzipは処理前/展開済み? Transfer-Encoding: chunkedは処理前/結合済み? 1コネクションに複数のレスポンスがある場合は? もうちょっとするとこれに加えてHTTP/2はどうするの? とか。
    こう考えると、かなり都合の良い「自称生Stream」でしかなかったりしませんか?

    2017年2月20日 12:42
  • 修正して再投稿します。

    ご指摘の通り、かなり都合の良い「自称生Stream」が欲しいということになります。

    なんでもかんでも生Streamが良い、ということであればTcpClientを使えばよい、ということになりますが、そこまでするつもりはありません。

    具体的には、生のMIME文書が欲しい、という事になります。

    Content-Encoding:~は、MIMEの仕様なので、そのままで構いません。

    Transfer-Encoding:~関連はライブラリ側でStreamとして取得するところまでは勝手にやっておいてほしい、というイメージです。

    主にサーバー間のWEBAPIでの連携を考えているので、HTTP/2を今のところ意識する必要はありませんが

    最初に書いた通りリダイレクト等は勝手に処理しておいてほしいです。





    2017年2月20日 13:11
  • 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)


    2017年2月21日 1:37
  • ご回答ありがとうございます。

    WinHTTPというものは初めて知りました。

    他のライブラリとは異なるもののようで、興味深いです。

    GetAllResponseHeaders() と ResponseText,ResponseStream というメソッド名から推察するに

    やはりHeaderとボディを分離して取得しているように見えます。


    また、おそらくアンマネージドコードのライブラリように思われ、出来ればマネージドコードのライブラリを利用して開発したいと思います。

    2017年2月21日 2:24
  • 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」は結合して受け取りたいのことでしたが、ということは分割送信されてきたチャンクを結合する必要があるのですから、どちらにせよ応答ボディの分割と再結合はどうしたって発生すると思いますし、その過程で応答メッセージのヘッダとボディを分けるのは至極自然だと思うのですが…。後からヘッダーと結合するのでは駄目なのですか?



    2017年2月21日 3:16
  • たとえば下記のレスポンスの場合、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>

    2017年2月21日 4:45
  • 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==..
    2017年2月21日 12:16
  • もし Fiddler でダメな場合、自力で(プログラム内部に) HTTP Proxy 的なものを作って、それを介して HTTP リクエスト→レスポンスを得るようにするくらいしか方法がないカモです。(理由は既に述べられている通りで、完全な生でも都合が悪いということなので)

    割とハードルは高めですが、標準クラスライブラリの

    • HttpListener
    • HttpClient

    の組み合わせで実現できるカモ。

    あるいは、NuGet や GitHub に HTTP Proxy のライブラリ/コードが落ちているので、それを使ってみるとかでしょうか。

    個人的には

    >HTTPサーバーから返されるヘッダーやボディの内容を出来るだけ生の状態でStreamやStringで取得

    が単なる趣味領域の話であるなら、保留・先送りにして先に進んだ方がよいのではないかと思います。

    業務で、要件だとしたらある程度仕方ない場合もありますが、機能外要件に沢山工数かけてもコストがかさむだけで詮無いかなーと思います。

    例えば、プロクシを挟んでそこでログがとれるんなら、それでもいいんじゃないかと思います。僕的には。


    2017年2月21日 23:52
  • もう一案。

    .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
    2017年2月21日 23:58

  • みなさま、ご回答下さりありがとうございます。

    私自身、何がしたいのか何をするべきなのか不明瞭な部分が多かったですが
    みなさまのご意見を聞き、かなりクリアになってきました。

    Content-Encoding と Transfer-Encoding は
    TCP と IP のように階層レベルで似て非なるものであることを再確認すれば
    どの状態のStreamが必要なのか、明瞭になりそうです。

    今回は、ご案内いただいた.net Coreの各ソースコードも追いながら、System.Net.Httpの
    HttpContentからストリーム化する方法で対処しようと思っています。

    派生クラスやメソッドが多数あるのですが、日本語ドキュメントが十分でないので
    .net coreであれソースコードは参考にさせていただこうと思います。


    FidderをコードレベルでProxyにセットする方法もとても興味深く参考になりました。
    今回はトレースが目的ではありませんでしたが、活用してみたいと思っています。


    最後のご意見を回答とさせていただきましたが、大変参考になりました。みなさまありがとうございました。

    2017年2月22日 3:44