runtime: HttpResponseMessage does not contain Content-Type and Content-Length headers in DelegatingHandler
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
When inside DelegatingHandler
, which is a HTTP request/response interceptor implementation for C#/.NET, (maybe somewhere else as well), HttpResponseMessage.Headers
does not contain Content-Length
and Content-Type
, although the actual underlying HTTP response contains them.
They might have been case-insensitive (that’s allowed by HTTP RFC) in name but they are not detected at all.
Edit: might be related: https://developercommunity.visualstudio.com/t/Getting-Content-Length-when-using-HttpCl/1187153?space=21&q=git+rebase+--continue
The following must be used in order to get these two headers - the response body (“content”) must be read (unexpected and unwanted):
await response.Content.LoadIntoBufferAsync();
var bodySizeBytes = response.Content.Headers.ContentLength;
Why actual content must be seemingly read to know content length header value which was created to know content length without reading the content? (the header is not mandatory (HTTP RFC ), but it is “removed” from the response in C#).
Expected Behavior
HttpResponseMessage.Headers
contains Content-Length
and Content-Type
headers when they are sent in HTTP response so that HttpResponseMessage.Content
does not have to be read into buffer to be able to get Content-Length
header value (response body/content size in bytes).
Steps To Reproduce
Do a HttpClient GET call with using the following MessageHandler:
/// <summary>
/// Must be registered as transient
/// </summary>
public class HttpRequestInfoInterceptor : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
//TEST
var responseHasContent = response.Content is not null;
Trace.WriteLine($"Response has content: {responseHasContent}");
Trace.WriteLine("--------");
Trace.WriteLine("Response headers:");
var headers = response.Headers.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
foreach(var header in headers)
{
Trace.WriteLine($"{header.Key}: {header.Value}");
}
Trace.WriteLine("--------");
await response.Content.LoadIntoBufferAsync(); //needs to called else the following line fails
var contentLength = response.Content.Headers.ContentLength;
Debug.WriteLine("Response headers ("from content"):");
var contentHeaders = response.Content.Headers.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
foreach (var header in contentHeaders)
{
Trace.WriteLine($"{header.Key}: {header.Value}");
}
return response;
}
}
Set up in DI like this:
public static IServiceCollection AddApiClient<T>(this IServiceCollection services)
where T : class //and where T has HttpClient constructor parameter
{
services.AddTransient<HttpRequestInfoInterceptor>();
//.AddHttpClient -> NuGet package Microsoft.Extensions.Http
services.AddHttpClient<T>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://www.example.com/");
}).AddHttpMessageHandler<HttpRequestInfoInterceptor>();
return services;
}
Output:
Response has content: True
--------
Response headers:
Date: System.String[]
Server: System.String[]
X-Powered-By: System.String[]
X-XSS-Protection: System.String[]
X-Frame-Options: System.String[]
Set-Cookie: System.String[]
Upgrade: System.String[]
Connection: System.String[]
Vary: System.String[]
Transfer-Encoding: System.String[]
--------
Response headers ("from content"):
Content-Type: System.String[]
Content-Length: System.String[]
When I call the endpoint manually via Swagger UI (which calls curl
and displays the result in web UI) (note: from what I read on the internet, gzip
might be an important piece here):
content-encoding: gzip
content-length: 25
content-type: text/plain;charset=UTF-8
date: Wed,06 Dec 2023 18:54:37 GMT
expires: Thu,19 Nov 1981 08:52:00 GMT
pragma: no-cache
server: Apache
vary: Accept-Encoding
x-firefox-spdy: h2
x-frame-options: SAMEORIGIN
x-powered-by: CENSORED
x-xss-protection: 1; mode=block
Exceptions (if any)
No response
.NET Version
8.0.100
Anything else?
No response
About this issue
- Original URL
- State: closed
- Created 7 months ago
- Comments: 16 (8 by maintainers)
How would a handler like the
ProgressMessageHandler
you mentioned report progress percentage if the amount of data read from the content didn’t match the advertised content length?It is a valid assumption for users to make that if a
ContentLength
is set, that number is correct and reflects exactly the amount of data that can be read from theHttpContent
. Not removing the header when making modifications would mean breaking that assumption.Potentially exposing the original header info in some way is what #42789 is tracking. Closing this as a duplicate of that. Feel free to reopen the issue if it turns out that you’re not using automatic decompression after all.
For that the only current workaround is not to use automatic decompression - see https://github.com/dotnet/runtime/issues/42789#issuecomment-699628305