Reputation: 799
Can anybody explain what is the difference between the request header and content header?
In this particular case I'm talking about UWP HttpClient
object. First you create HttpClient
, then you create HttpRequestMessage
and then you assign, in my case HttpStreamContent
to the Content property of the HttpRequest message. There is Headers property on the HttpRequestMessage
and there is Headers property on the HttpStreamContent
.
When should I use one or another?
Where exactly the headers will appear in one or another case?
Here is a code snippet to explain what I mean
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;
}
}
}
Here I just add the same list of headers to HttpRequestMessage
and to HttStreamContent
. I guess it's wrong unless those objects are smart enough to apply only permitted headers in one or the other case. So, which headers should go where? Are they interchangeable?
Upvotes: 11
Views: 5118
Reputation: 22829
They serve different purpose:
Content-Disposition
, Content-Range
, Content-Length
, Content-Type
, etc.Accept
, Accept-Encoding
, Authorization
, Cookie
, etc.HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, address);
request.Headers.Add("Content-Type", "application/json");
This will produce the following exception:
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");
This will produce the following exception:
System.InvalidOperationException: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
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);
This will not produce any exception.
In order to be able answer this question I will use the WireMock.Net nuget package to run a mock server.
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));
/
route and it anticipates a POST
requestheaderKey
value it may respond
From Request header
From Content header
If I send a request where the same header key is set on both objects then I will receive the following response:
From Request header
What if I switch the order of the header key assignments?
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, address);
request.Headers.Add(headerKey, requestHeaderValue);
var content = new StringContent("Test", Encoding.UTF8);
content.Headers.Add(headerKey, contentHeaderValue);
The result will be the same: From Request header
.
For the sake of completeness here is the full source code:
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);
}
UPDATE #1 Found a bug in my example
I've just realized that I forgot to use the Content
property of the HttpRequestMessage
:
var request = new HttpRequestMessage(HttpMethod.Post, address) { Content = content };
{"Status":"No matching mapping found"}
From Content header
From Request header
So, why did we receive this No matching mapping found
? The reason is that because both values are sent in this case and there is no registered route for that case.
To prove the theory let's write some assessment code:
var logs = server.FindLogEntries(Request.Create().WithPath(route).UsingPost());
Console.WriteLine(logs.First().RequestMessage.Headers[headerKey].Count);
FindLogEntries
)A
header contains both values B
and C
Upvotes: 19