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

What is the difference between request headers and content headers for HttpClient

任何人都可以解释请求 header 和内容 header 之间的区别吗?

在这种特殊情况下,我说的是 UWP HttpClient object。首先创建 HttpClient,然后创建 HttpRequestMessage,然后将 HttpStreamContent 分配给 HttpRequest 消息的 Content 属性。 HttpRequestMessage上有Headers属性,HttpStreamContent上有Headers属性。

我什么时候应该使用一个或另一个?
在一种或另一种情况下,header 究竟会出现在哪里?

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

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;
        }
    }
}

这里我只是将相同的 header 列表添加到 HttpRequestMessageHttStreamContent。我想这是错误的,除非那些 objects 足够聪明以在一种或另一种情况下只应用允许的 headers。那么,哪个 header 应该去哪里?它们可以互换吗?

理论

它们有不同的用途:

练习

尝试在请求

上设置Content-Type
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, address);
request.Headers.Add("Content-Type", "application/json");

这将产生以下异常:

System.InvalidOperationException: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'

尝试在内容上设置接受

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

这将产生以下异常:

System.InvalidOperationException: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'

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

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

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

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

这不会产生任何异常。

哪个值传递给下游?

为了能够回答这个问题,我将使用 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));
  • 这里我定义了一个监听9000端口的模拟服务器
  • 它在 / 路由上有一个端点,它预期 POST 请求
  • 根据 headerKey 值它可能会响应
    • 或者 From Request header
    • From Content header

如果我发送的请求在两个 objects 上设置了相同的 header 键,那么我将收到以下响应:

From Request header

顺序重要吗?

如果我调换 header 按键分配的顺序会怎样?

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

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

结果将是相同的:From Request header


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

static readonly HttpClient httpClient = new HttpClient();
static async Task Main(string[] args)
{
    const string headerKey = "A", requestHeaderValue = "B", contentHeaderValue = "C";
    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));

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

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

    var response = await httpClient.SendAsync(request);
    var headerSource = await response.Content.ReadAsStringAsync();
    Console.WriteLine(headerSource);
}