HttpClient中请求头和内容头有什么区别?

6

有人能解释一下请求头和内容头之间的区别吗?

在这个特定的案例中,我谈论的是UWP HttpClient对象。首先创建HttpClient,然后创建HttpRequestMessage,然后将HttpStreamContent分配给HttpRequest消息的Content属性。 HttpRequestMessage上有Headers属性,HttpStreamContent上也有Headers属性。

什么情况下应该使用其中一个?
这两种情况下标头会出现在哪里?

以下是代码片段以说明我的意思

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

在这里,我只是将相同的标题列表添加到HttpRequestMessageHttStreamContent中。除非这些对象足够聪明,能够在一个或另一个情况下仅应用允许的标题,否则我认为这是错误的。那么,哪些标题应该放在哪里?它们是可以互换的吗?

1个回答

12

理论

它们有不同的用途:

练习

尝试在请求中设置Content-Type

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

这将产生以下异常:

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

尝试在内容上设置Accept

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

这将产生以下异常:

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

这不会产生任何异常。

哪个值被传递到下游?

为了能够回答这个问题,我将使用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

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

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

结果将是相同的:从请求头
为了完整起见,这里是完整的源代码:
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, requestHeaderValue);

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

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

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

我刚刚意识到我忘记使用 HttpRequestMessageContent 属性:

var request = new HttpRequestMessage(HttpMethod.Post, address) { Content = content };
  • 如果我们在两个位置都设置相同的标题,那么响应将是:{"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);
  • 当我们从服务器收到响应后,可以通过FindLogEntries请求日志
  • 在日志条目上,我们可以访问请求消息,因此可以仔细检查标头
  • 如您所见,A标头包含两个值BC

这似乎是一个语义上的差异。如果我们谈论代码生成的原始请求,像http://rebol.net/cookbook/recipes/0059.html这样的请求所有头部项都作为单个列表传递,对吗?因此,如果我为HttpStreamContent设置Accept头,并为HttpRequestMessage设置Content-Range,是否会对生成的原始请求产生影响?或者这些类不允许这种事情发生? - Alex
@Alex 我已经扩展了我的答案,详细说明了我对你的新问题的回答。请查看它们。 - Peter Csala
@Alex 我的解释清楚吗?还需要更多的阐述吗? - Peter Csala
1
完美的答案,谢谢。我肯定得到了我需要的一切。 - Alex

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