HttpClient 的请求标头和内容标头有什么区别

Ale*_*lex 6 c# request request-headers dotnet-httpclient

谁能解释一下请求标头和内容标头之间有什么区别?

在本例中,我讨论的是 UWPHttpClient对象。首先创建HttpClient,然后创建HttpRequestMessage,然后分配(在我的例子中分配HttpStreamContent给 HttpRequest 消息的 Content 属性)。上有 Headers 属性HttpRequestMessage, 上也有 Headers 属性HttpStreamContent

我什么时候应该使用其中一种或另一种?
在一种或另一种情况下,标题到底会出现在哪里?

这是一个代码片段来解释我的意思

using(var objProtocolFilter = new HttpBaseProtocolFilter()) {
    objProtocolFilter.AllowUI = false;
    objProtocolFilter.CacheControl.ReadBehavior = HttpCacheReadBehavior.NoCache;
    objProtocolFilter.CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
    
    using(var objClient = new HttpClient(objProtocolFilter)) {
        HttpMethod eMethod = Method switch {
            HttpUploadMethod.Post => HttpMethod.Post,
            HttpUploadMethod.Put => HttpMethod.Put,
            _ => throw new ValueOutOfRangeException(nameof(Method))
        };
        
        using(var objRequest = new HttpRequestMessage(eMethod, RemoteUri)) {
            _Headers.Cast<string>().Execute(item => objRequest.Headers.TryAppendWithoutValidation(item, _Headers[item]));

            objRequest.Content = new HttpStreamContent(objInputStream.AsInputStream());
            _Headers.Cast<string>().Execute(item => objRequest.Content.Headers.TryAppendWithoutValidation(item, _Headers[item]));
            objRequest.Content.Headers.ContentLength = (ulong)objInputStream.Length;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我只是将相同的标头列表添加到HttpRequestMessage和 到HttStreamContent。我想这是错误的,除非这些对象足够聪明,可以在一种或另一种情况下仅应用允许的标头。那么,哪些标头应该放在哪里?它们可以互换吗?

Pet*_*ala 12

理论

它们有不同的目的:

实践

尝试在请求上设置 Content-Type

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, address);
request.Headers.Add("Content-Type", "application/json");
Run Code Online (Sandbox Code Playgroud)

这将产生以下异常:

System.InvalidOperationException:'误用标头名称。确保请求标头与 HttpRequestMessage 一起使用,响应标头与 HttpResponseMessage 一起使用,内容标头与 HttpContent 对象一起使用。

尝试在内容上设置接受

var content = new StringContent("Test", Encoding.UTF8);
content.Headers.Add("Accept","application/json");
Run Code Online (Sandbox Code Playgroud)

这将产生以下异常:

System.InvalidOperationException:'误用标头名称。确保请求标头与 HttpRequestMessage 一起使用,响应标头与 HttpResponseMessage 一起使用,内容标头与 HttpContent 对象一起使用。

尝试在两个地方设置相同的任意标头:

const string headerKey = "A", requestHeaderValue = "B", contentHeaderValue = "C";

var content = new StringContent("Test", Encoding.UTF8);
content.Headers.Add(headerKey, contentHeaderValue);

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, address);
request.Headers.Add(headerKey, requestHeaderValue);
Run Code Online (Sandbox Code Playgroud)

这不会产生任何异常。

哪个值传递给下游?

为了能够回答这个问题,我将使用WireMock.Net nuget 包来运行模拟服务器。

const string address = "http://localhost:9000", route = "/";
var server = WireMockServer.Start(new WireMockServerSettings { Urls = new[] { address } });

server
    .Given(Request.Create()
        .WithPath(route)
        .WithHeader(headerKey, new ExactMatcher(requestHeaderValue))
        .UsingPost())
    .RespondWith(Response.Create()
        .WithBody("From Request header")
        .WithStatusCode(200));

server
    .Given(Request.Create()
        .WithPath(route)
        .WithHeader(headerKey, new ExactMatcher(contentHeaderValue))
        .UsingPost())
    .RespondWith(Response
        .Create()
        .WithBody("From Content header")
        .WithStatusCode(200));
Run Code Online (Sandbox Code Playgroud)
  • 这里我定义了一个监听 9000 端口的模拟服务器
  • 它在路由上有一个端点,/并且它预期一个POST请求
  • 根据headerKey它可能响应的值
    • 要么与From Request header
    • 或与From Content header

如果我发送一个在两个对象上设置相同标头键的请求,那么我将收到以下响应:

From Request header
Run Code Online (Sandbox Code Playgroud)

顺序重要吗?

如果我切换标题键分配的顺序会怎样?

From Request header
Run Code Online (Sandbox Code Playgroud)

结果将是相同的:From Request header


为了完整起见,这里是完整的源代码:

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, address);
request.Headers.Add(headerKey, requestHeaderValue);

var content = new StringContent("Test", Encoding.UTF8);
content.Headers.Add(headerKey, contentHeaderValue);
Run Code Online (Sandbox Code Playgroud)

更新 #1在我的示例中发现了一个错误

我刚刚意识到我忘记使用Content以下属性HttpRequestMessage

var request = new HttpRequestMessage(HttpMethod.Post, address) { Content = content };
Run Code Online (Sandbox Code Playgroud)
  • 如果我们在两个地方设置相同的标头,那么响应将是:{"Status":"No matching mapping found"}
  • 如果我们仅在内容上设置此标头,则响应将是:From Content header
  • 如果我们仅在请求上设置此标头,则响应将是:From Request header

那么,我们为什么会收到这个No matching mapping found?原因是因为在这种情况下发送了两个值,并且没有针对该情况注册的路由。

为了证明这个理论,让我们编写一些评估代码:

var logs = server.FindLogEntries(Request.Create().WithPath(route).UsingPost());
Console.WriteLine(logs.First().RequestMessage.Headers[headerKey].Count);
Run Code Online (Sandbox Code Playgroud)
  • 收到服务器的响应后,我们可以请求日志(通过FindLogEntries
  • 在日志条目上,我们可以访问请求消息,因此我们可以仔细检查标头
  • 正如您所看到的,A标头包含值BC