none
HttpWebResponseで取得したHTTPレスポンスヘッダ値が「,(カンマ)」で分割される。 RRS feed

  • 質問

  • 初めて質問させていただきます。

    お手数ですが、宜しくお願い致します。


    C#でHTTPクライアントアプリを作っております。

    HTTPリクエスト/レスポンスの送受信には「HttpWebRequest」「HttpWebResponse」を使っていますが、

    下記コード例に示す通り、WebHeaderCollection.GetValues(string)を使用して受信したHTTPレスポンスのヘッダ値を取得すると、

    ヘッダ値に「,(カンマ)」が含まれている場合、カンマでレスポンスヘッダが分割されてしまいます。

    •コード例

    HttpWebResponse response = (HttpWebResponse)request.GetResponse()
    WebHeaderCollection headers = response.Headers;
    foreach (string key in headers.Keys)
    {
     foreach (string value in headers.GetValues(key))
     {
      Console.WriteLine("{0}: {1}", key, value);
     }
    }

    •実行結果

    Pragma: no-cache
    Vary: Accept-Encoding
    Content-Encoding: gzip
    Content-Length: 3855
    Cache-Control: no-store
    Cache-Control: no-cache
    Cache-Control: must-revalidate
    Cache-Control: post-check=0
    Cache-Control: pre-check=0
    Content-Type: text/html
    Date: Thu, 25 Jun 2015 07:34:18 GMT
    Expires: Thu, 19 Nov 1981 08:52:00 GMT
    Location: ./login.php
    Set-Cookie: Loggedin=deleted; expires=Wed
    Set-Cookie: 25-Jun-2014 07:34:17 GMT; path=/
    Server: Apache/2.2.14 (Ubuntu) mod_mono/2.4.3 PHP/5.3.2-1ubuntu4.5 with Suhosin-Patch proxy_html/3.0.1 mod_python/3.3.1 Python/2.6.5 mod_ssl/2.2.14 OpenSSL/0.9.8k Phusion_Passenger/3.0.17 mod_perl/2.0.4 Perl/v5.10.1
    X-Powered-By: PHP/5.3.2-1ubuntu4.5

    上記例では、Cache-ControlとSet-Cookieの値がカンマで分割され、複数のヘッダとして取得されています。

    ちなみに、このHTTPレスポンスのRawデータは以下の通りです。

    当然ですが、Cache-ControlとSet-Cookieはカンマで分割されておりません。

    HTTP/1.1 302 Found
    Pragma: no-cache
    Vary: Accept-Encoding
    Content-Encoding: gzip
    Content-Length: 3855
    Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
    Content-Type: text/html
    Date: Thu, 25 Jun 2015 07:34:18 GMT
    Expires: Thu, 19 Nov 1981 08:52:00 GMT
    Location: ./login.php
    Set-Cookie: Loggedin=deleted; expires=Wed, 25-Jun-2014 06:26:18 GMT; path=/
    Server: Apache/2.2.14 (Ubuntu) mod_mono/2.4.3 PHP/5.3.2-1ubuntu4.5 with Suhosin-Patch proxy_html/3.0.1 mod_python/3.3.1 Python/2.6.5 mod_ssl/2.2.14 OpenSSL/0.9.8k Phusion_Passenger/3.0.17 mod_perl/2.0.4 Perl/v5.10.1
    X-Powered-By: PHP/5.3.2-1ubuntu4.5

    この事象をクリアする方法があれば、ご教授いただけないでしょうか。

    ちなみに、.NET Framework 4.5.51650を使用しております。

    開発はVisual Studio 2013 Express Editionです。


    お手数お掛けしますが、宜しくお願い致します。


    以上です。

    • 移動 星 睦美 2015年7月10日 0:39 .NET Micro Framework
    2015年6月29日 4:57

回答

  • WebHeaderCollection::GetValues(String)使えばそりゃそうなるでしょう。そう言うメソッドなので。

    WebHeaderCollection::this[String]、つまりheaders[key]でアクセスすれば、Addしたときのがそのまま返されます。

    • 回答としてマーク secscan2015 2015年6月29日 6:11
    2015年6月29日 5:12

すべての返信

  • WebHeaderCollection::GetValues(String)使えばそりゃそうなるでしょう。そう言うメソッドなので。

    WebHeaderCollection::this[String]、つまりheaders[key]でアクセスすれば、Addしたときのがそのまま返されます。

    • 回答としてマーク secscan2015 2015年6月29日 6:11
    2015年6月29日 5:12
  • 早速のご回答、誠にありがとうございます。

    ご教授頂いた方法で対処する方向で検討させていただきます。

    Set-Cookie値が中途半端な位置で分割されるのはつらいですが、仕様であれば仕方がないですね。

    2015年6月29日 6:11
  • > Set-Cookie値が中途半端な位置で分割されるのはつらいですが、仕様であれば仕方がないですね。

    「仕様であれば仕方がない」という話ではなくて、数あるやり方の中から質問者さんがそういう方法を選んだということではないのでしょうか?

    Hongliang が回答されたように、以下のようにすれば望む結果が得られるのではないかと思いますが、確認されたでしょうか?

    foreach (string key in headers.Keys)
    {
        Console.WriteLine("{0}: {1}", key, headers[key]);
    }
    

    2015年6月29日 9:09
  • ご回答ありがとうございます。

    また、返信が遅くなり申し訳ありません。

    ご教授頂いた方法も確認致しましたが、同名のヘッダが複数存在する場合、下記の様に1行に纏められます。

    • 期待するレスポンスデータ
    Set-Cookie: A
    Set-Cookie: B
    • 上記方法で取得したレスポンスデータ
    Set-Cookie: A, B

    事前にローカルProxy(Burp Suiteなど)で取得したレスポンス(上記の期待するレスポンスデータ)と、HttpWebResponseで取得したレスポンスデータの差分比較を行う必要がありますので、上記の違いが大きな差分となっています。

    現在はレスポンスヘッダ値をチェックし、差分が生じないようにレスポンスヘッダを組み直す方法を採用しています。

    以上

    2015年7月9日 6:00
  • では、WebHeaderCollection::GetValues(int)の方はどうでしょうか。

    for (int i = 0; i < headers.Count; ++i) {
        string key = headers.GetKey(i);
        string[] values = headers.GetValues(i);
        foreach (string value in values) {
            Console.WriteLine("{0}: {1}", key, value);
        }
    }
    • 回答の候補に設定 佐祐理 2015年7月10日 0:44
    2015年7月9日 6:24
  • 質問者さんは Set-Cookie: A, B という形で取得したいのだと思っていましたが、勘違いだったようですみません。

    以下のような形で取得し、かつ、例えば expires=Wed, 16-Sep-2015 16:51:30 GMT というように期限が設定されていても、Wed の後のカンマで分割されることがないようにしたいということですね。

    Set-Cookie: A
    Set-Cookie: B

    GetValues メソッドを使うとカンマ ( , ) で分割されるのは、GetValues メソッドは文字列の中身を見てパースしている訳ではなく、単純に何も考えずにカンマでデリミティングしているからだそうです。

    Set-Cookie header broken in WebHeaderCollection
    http://bytes.com/topic/asp-net/answers/309751-set-cookie-header-broken-webheadercollection

    なので、GetValues メソッドではうまく行きません。先の私のレスで書いた方法で Set-Cookie: A, B の形で取得して、曜日の後のカンマは無視するようにしてパースしてはいかがでしょう。

    どうしても Set-Cookie: A という形で取得したいというわけではなく、Cookie の中の情報を取得できればいいのであれば、上の記事に書いてあるように、CookieContainer, CookieCollection, Cookie クラスを利用すれば必要な情報を取得できるはずです。

    使い方のサンプルは以下のページにありますので見てください。

    Cookie クラス
    https://msdn.microsoft.com/ja-jp/library/system.net.cookie(v=vs.110).aspx


    • 編集済み SurferOnWww 2015年7月9日 7:34 タイポ訂正
    2015年7月9日 7:28
  • # .NET Micro Frameworkフォーラムではなく、.NET Frameworkフォーラム等(C#やExpressでもいいかなと)が適切に思います。モデレーターさん移動をお願いします。

    Hongliangさんの回答のようにGetValue(int)を使うことになると思います。

    ちなみに.NET Framework 4.5からHttpWebRequest / HttpWebResponseに代わるHttpClientクラスが用意されています。

    static async Task RunAsync(Uri uri) {
        using (var client = new HttpClient())
        using (var response = await client.GetAsync(uri))
            foreach (var pair in response.Headers) {
                var key = pair.Key;
                foreach (var value in pair.Value)
                    Console.WriteLine("{0}: {1}", key, value);
            }
    }
    
    static void Main() {
        var uri = new Uri("http://www.example.com");
        RunAsync(uri).Wait();
    }

    2015年7月9日 8:10
  • > Hongliangさんの回答のようにGetValue(int)を使うことになると思います。

    実際に .NET 4 で試してみましたが、GetValues(string) は単純にカンマ・デリミティング、GetValues(int) の方はきちんとパースしました。

    実装が違うようですね。


    • 編集済み SurferOnWww 2015年7月9日 8:48 Typo 訂正
    2015年7月9日 8:46
  • WebHeaderCollectionの元となっているNameValueCollectionの動作仕様です。

    Item[string]インデクサなどにもその片鱗を見ることができ、同一のキーはコンマ区切りで結合できるものと見なされています。intによるインデックスアクセス(Item[int]、GetKey(int)、GetValues(int)など)はそれらの影響を受けずにアクセスできます。

    この辺りはもうソースコードを見て実際の動作を把握するしかありません。

    2015年7月9日 9:11
  • おかげさまで、そういうことがあることを知ることができ、一つ利口になりました。

    さらに試してみましたが、GetValues(int) の方は、曜日の後のカンマだけでなく、以下のような Cookie(最後に注目)でもカンマがデリミタかそうでないかの見分けが付くようです。

    Set-Cookie: .ASPXANONYMOUS=Hd_V47vw0AEk...; expires=Wed, 16-Sep-2015 20:11:36 GMT; path=/; HttpOnly
    Set-Cookie: ASP.NET_SessionId=nvd2cdexbtjwxvxjw5x0msja; path=/; HttpOnly
    Set-Cookie: DateTimeCookie=2015/07/09 18:31:36; path=/
    Set-Cookie: RandomNumber=1408121466; path=/
    Set-Cookie: session_cache={"Cache":"XZ9","Time":"6351258293015444647","SessionID":"ls4e3hz2yv4hnhxfn4d2xqhm"}; path=/

    GetValues(string) の方はもちろんダメでした。

    ソースを見たわけではないですが、結果からすると、GetValues(int) の方はヘッダの種類、Cookie であればカンマがデリミタかそうでないかを見分けるというパース機能が実装されているということでしょうか。もしそうであれば、そういうことは MSDN ライブラリに書いておいて欲しいと思うのですが・・・



    • 編集済み SurferOnWww 2015年7月9日 9:56 誤字脱字訂正
    2015年7月9日 9:55
  • 確認できてないので思い切りはずしてるかもしれないのですが、intの方はそもそもパース自体してなくて、ただ内部に保存しているヘッダ情報をそのまま取り出してるだけじゃないですかね?

    stringの方は逆に自分でパースしてるからおかしくなってしまうという。

    • 編集済み なちゃ 2015年7月9日 10:29
    2015年7月9日 10:29
  • フォーラム オペレーターの星 睦美です。
    こちらのスレッドは.NET Micro Framework に関する話題を扱うフォーラムに投稿いただいていますが、質問の内容から Visual C# フォーラムに移動しました。投稿した質問はフォーラム ページの左上、[クイック アクセス] > マイ スレッドから確認できます。

    フォーラム オペレーター 星 睦美 - MSDN Community Support

    2015年7月10日 0:43
  • Hongliang様、SurferOnWww様、佐祐理様、なちゃ

    御礼が遅くなり申し訳ございません。

    また貴重な情報をご提供いただき、ありがとうございます。

    Hongliang様にご教授いただいた方法(GetValue(int)を使用)により、期待していた結果を得ることができました。

    Set-CookieやCache-Controlなどのヘッダ値をRawデータと同じ形式で取り出すことができました。

    改めて御礼申し上げます。

    2015年7月16日 4:56