HttpClient获取所有头信息

39

目前,我正在开发 API 封装。如果我发送错误的 Consumer Key,服务器将在标头中返回Status403 Forbidden。它还将传递自定义标头。那么我该如何获取这些自定义标头呢?

这就是从服务器接收到的响应。

Cache-Control: private
Date: Wed,  01 May 2013 14:36:17 GMT
P3P: policyref="/w3c/p3p.xml",  CP="ALL CURa ADMa DEVa OUR IND UNI COM NAV INT STA PRE"
Server: Apache/2.2.23 (Amazon)
Status: 403 Forbidden
X-Error: Invalid consumer key.
X-Error-Code: 152
X-Powered-By: PHP/5.3.20
Connection: keep-alive

我需要检索X-ErrorX-Error-Code。目前,我正在使用HttpClient类处理请求。如果我在VS Studio 2012的“Quick Watch”下观察响应头,则可以像这样找到它:

((System.Net.Http.Headers.HttpHeaders)(response.Headers)).headerStore["X-Error-Code"].ParsedValue

还有其他方法可以做到这一点吗?

编辑: headerStore无法通过代码访问,因为这是私有字段。我只能通过“Quick Watch”窗口访问它。

这是我的请求片段:

var response = await _httpClient.PostAsync("/v3/oauth/request", content);
6个回答

53

不行,我不能这样做,因为我会收到这个错误 Error 3Cannot apply indexing with [] to an expression of type System.Net.Http.Headers.HttpResponseHeaders'。我会更新上面的代码以便更清晰明了。 - Shulhi Sapli
@ShulhiSapli:啊-没有注意到它是“HttpClient”。我会编辑的。 - Jon Skeet
如果您需要更多细节,可以在此处查看:https://github.com/shulhi/PocketNet/blob/dev/PocketNet/PocketNet/Authenticator/PocketOauth.cs - Shulhi Sapli

42

由于问题的标题是“检索所有标头”,我想添加一个与此相关的答案。

HttpClient 方法返回的 HttpResponseMessage 具有两个标头属性:

  • HttpResponseMessage.Headers 是一个带有通用响应标头的 HttpResponseHeaders
  • HttpResponseMessage.Content.Headers 是一个带有特定内容标头(如Content-Type)的 HttpContentHeaders

这两个对象都实现了 IEnumerable<KeyValuePair<string, IEnumerable<string>>,因此您可以轻松地使用以下方式将所有标头组合在一起:

var responseMessage = await httpClient.GetAsync(url);
var headers = responseMessage.Headers.Concat(responseMessage.Content.Headers);

// headers has type IEnumerable<KeyValuePair<String,IEnumerable<String>>>

为什么这是一个可枚举的名称集合,它包含多个值呢?因为一些HTTP头(例如Set-Cookie)可以在响应中重复出现(尽管大多数其他标头只能出现一次 - 但软件应该优雅地处理违反RFC的Web服务器返回的无效标头)。

生成所有标头的字符串:

我们可以使用单个Linq表达式生成平面标头字符串:

  • 使用ConcatHttpResponseMessage.HeadersHttpResponseMessage.Content.Headers组合在一起。
    • 不要使用Union,因为那样不会保留所有标头。
    • (作为个人风格偏好,当我将两个IEnumerable<T>对象连接在一起时,我从Enumerable.Empty<T>()开始,以获得外观上对称的结果 - 不是出于性能或任何其他原因)。
  • 在每个标头集合上使用.SelectMany来展开每个集合之前连接其平面化结果。
  • 使用AggregateStringBuilder高效生成string表示。

就像这样:

    HttpResponseMessage resp = await httpClient.GetAsync( url );

    String allHeaders = Enumerable
        .Empty<(String name, String value)>()
        // Add the main Response headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
         // Concat with the content-specific headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Content.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
        // Render to a string:
        .Aggregate(
            seed: new StringBuilder(),
            func: ( sb, pair ) => sb.Append( pair.name ).Append( ": " ).Append( pair.value ).AppendLine(),
            resultSelector: sb => sb.ToString()
        );

将所有标头加载到NameValueCollection中:

另一种选择是使用来自.NET Framework 1.1的经典NameValueCollection类,该类支持带有多个值的键(确实,Classic ASP.NET WebForms用于此目的):

像这样:

    HttpResponseMessage resp = await httpClient.GetAsync( url );

    NameValueCollection allHeaders = Enumerable
        .Empty<(String name, String value)>()
        // Add the main Response headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
         // Concat with the content-specific headers as a flat list of value-tuples with potentially duplicate `name` values:
        .Concat(
            resp.Content.Headers
                .SelectMany( kvp => kvp.Value
                    .Select( v => ( name: kvp.Key, value: v ) )
                )
        )
        .Aggregate(
            seed: new NameValueCollection(),
            func: ( nvc, pair ) => { nvc.Add( pair.name, pair.value ); return nvc; },
            resultSelector: nvc => nvc
        );

这为headers变量创建了一个非常复杂的类型。如何将数据提取到字符串或简单集合中? - Jamie Marshall
@JamieMarshall 由于它将所有内容放入键/值对的IEnumerable中,因此您应该能够使用foreach循环遍历组合列表。每个键值对的键是标题名称,每个键值对的值是标题值列表。 - Ben Sutton
值得注意的是,为了使 Concat 正常工作,您需要确保包含 using System.Linq; - Ben Sutton

14

在尝试查找不存在的标头时,我发现一个小技巧。您应该使用TryGetValues而不是GetValues,因为在运行时,如果未找到标头,它会抛出异常。您可以使用类似于以下代码的东西:

IEnumerable<string> cookieHeader; 
response.Headers.TryGetValues("Set-Cookie", out cookieHeader);

6
有点臃肿,但容易理解。
            System.Diagnostics.Debug.Write("----- CLIENT HEADERS -----" + Environment.NewLine);
            foreach (KeyValuePair<string, IEnumerable<string>> myHeader in myHttpClient.DefaultRequestHeaders)
            {
                System.Diagnostics.Debug.Write(myHeader.Key + Environment.NewLine);
                foreach(string myValue in myHeader.Value)
                {
                    System.Diagnostics.Debug.Write("\t" + myValue + Environment.NewLine);
                }
            }

            System.Diagnostics.Debug.Write("----- MESSAGE HEADERS -----" + Environment.NewLine);
            foreach (KeyValuePair<string, IEnumerable<string>> myHeader in myHttpRequestMessage.Headers)
            {
                System.Diagnostics.Debug.Write(myHeader.Key + Environment.NewLine);
                foreach (string myValue in myHeader.Value)
                {
                    System.Diagnostics.Debug.Write("\t" + myValue + Environment.NewLine);
                }
            }

            System.Diagnostics.Debug.Write("----- CONTENT HEADERS -----" + Environment.NewLine);
            foreach (KeyValuePair<string, IEnumerable<string>> myHeader in myHttpRequestMessage.Content.Headers)
            {
                System.Diagnostics.Debug.Write(myHeader.Key + Environment.NewLine);
                foreach (string myValue in myHeader.Value)
                {
                    System.Diagnostics.Debug.Write("\t" + myValue + Environment.NewLine);
                }
            }

0
这对我有效:
(String[])response.Headers.GetValues("X-Error"))[0]

我对这个Nisse被down vote的原因很好奇。我在这里包含它,因为它来自我正在使用的控制台应用程序。 - nmishr
我也给它点了踩,因为它不安全:HttpHeaders 的规范并不保证 .GetValues(String) 返回一个 String[](它只说返回一个 IEnumerable<String>),如果指定的名称没有值,则会得到一个 InvalidOperationException - 如果重复使用相同的标头,则仅返回指定标头名称的第一个值。 - Dai
1
另一个降低此答案评分的原因是,OP 询问如何获取“所有标头”,但此答案仅返回单个命名标头的值 - 因此根本没有回答原始问题。 - Dai

0

这是一个请求,但响应头也具有相同的结构。在 .NET 6 中进行了测试。

somerequest.Headers.Select(x => new { x.Key, Value = x.Value?.FirstOrDefault() } )

这将出现在立即窗口中:

{System.Linq.Enumerable.SelectEnumerableIterator<System.Collections.Generic.KeyValuePair<string, System.Collections.Generic.IEnumerable<string>>, <>f__AnonymousType0<string, string>>}
    [0]: {{ Key = Transfer-Encoding, Value = chunked }}
    [1]: {{ Key = Server, Value = Microsoft-IIS/10.0 }}
    [2]: {{ Key = WWW-Authenticate, Value = Bearer error="invalid_token", error_description="The token expired at '03/29/2023 11:37:34'" }}
    [3]: {{ Key = X-Powered-By, Value = ASP.NET }}
    [4]: {{ Key = Date, Value = Thu, 30 Mar 2023 10:23:47 GMT }}

这类似于输出的问题示例。因此,如果您想将其显示为日志或输出的字符串,则可以在之后连接这些字符串。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接