I have an asp.net 6 application. I would like to add the content-length header to the response for one of my endpoints. I found one way that works from another question on StackOverflow, but as mentioned the solution is not very efficient. Is there a way to add the content-length to the response? And it would be even better if the solution does not use a middleware and could be solved in the controller directly.
Controller:
public async Task<ActionResult<IEnumerable<CourseQueryDTO>>> GetCoursesInSemester (string semesterId, CancellationToken cancellationToken)
{
IEnumerable<CourseQueryDTO> courses = await _courseService.GetCoursesInSemesterAsync(semesterId, cancellationToken);
HttpContext.Response.Headers.Add(HeaderNames.ContentLength, JsonSerializer.SerializeToUtf8Bytes(courses).Length.ToString());
return Ok(courses);
}
The above code throws the following error: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "0HMNUQVREHBBL", Request id "0HMNUQVREHBBL:00000007": An unhandled exception was thrown by the application. System.InvalidOperationException: Response Content-Length mismatch: too few bytes written (2210144 of 2221925).
And on the frontend client I get this error: net::ERR_HTTP2_PROTOCOL_ERROR 200
However, the response status code is still 200.
Program.cs (pipeline order could make a difference)
...
app.UseMiddleware<ExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
UPDATE:
What I did is that I added a custom header that contains the size of the response body in bytes
public async Task<ActionResult<IEnumerable<CourseQueryDTO>>> GetCoursesInSemester (string semesterId, CancellationToken cancellationToken)
{
IEnumerable<CourseQueryDTO> courses = await _courseService.GetCoursesInSemesterAsync(semesterId, cancellationToken);
HttpContext.Response.Headers.Add("X-Total-Size", JsonSerializer.SerializeToUtf8Bytes(courses).Length.ToString());
return Ok(courses);
}
This seems to work, as long as I whitelist the header for CORS.
Is this solution bad?
Related
I wanted to find out how ASP.NET Core determines we have reached the end of the middleware pipeline and starts sending the response back. This is the code that handles it (from the GitHub repository):
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
/*
Some code omitted for clarity
*/
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
My question is this: What does the line context.Response.StatusCode = 404; do? Why is it even there? Shouldn't it be a 200 ("OK")? Where is the code that changes this default value so that we don't get a "404 Not Found" error on every request?
What does the line context.Response.StatusCode = 404; do? Why is it even there?
This call ends up being run as the last component within the middleware pipeline. If the incoming request makes it all the way to the end of the pipeline that you configured, this code will run. It's there to ensure that a 404 is returned when a request isn't handled by your application.
Shouldn't it be a 200 ("OK")?
No, a HTTP 200 OK response isn't appropriate for this. That indicates that the request was handled successfully, but in fact it wasn't handled at all, because logic for processing this particular request was not found (HTTP 404 NotFound).
Where is the code that changes this default value so that we don't get a "404 Not Found" error on every request?
The middleware pipeline supports the concept of short-circuiting (see the docs). Briefly, this means a middleware component decides whether or not to execute the next middleware component in the pipeline. Imagine the following, simplified pipeline setup:
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
In this pipeline, both the static-files and the endpoints middleware may short-circuit the pipeline. If the static-files middleware is able to process the request, it usually sets the status-code to HTTP 200 and returns the file. If the endpoints middleware finds a matching controller/action, it could do one of many things, but usually it will set a success status code such as HTTP 200.
Only if neither the static-files middleware nor the endpoints middleware manages to handle the request, the line called out (context.Response.StatusCode = 404;) will run as a sort of fallback.
I've got some problems concerning CORS/HttpClient in dotnet core. First of all my Application is structured like this:
Webapp -> Microservice-Gateway -> multiple Microservices
If I do an Ajax call from my Webapp
$.ajax({
url: "https://[actual gateway Url]/customer/" + customerId,
type: "GET",
timeout: 60000,
crossDomain: true,
dataType: "json",
success: data => {
//...
}
});
to the Gateway-Server I receive sometimes following error Message:
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:50595' is therefore not allowed
access. The response had HTTP status code 500.
Internally however my gateway throws this error if I debug:
System.Net.Http.HttpRequestException: An error occurred while sending
the request. ---> System.Net.Http.WinHttpException: The operation
timed out
I tried to solve the first error by adding following code in the gateway and every microservice:
public void ConfigureServices(IServiceCollection services) {
var corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin();
corsBuilder.AllowCredentials();
services.AddCors(options => {
options.AddPolicy("SiteCorsPolicy", corsBuilder.Build());
});
//..
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
app.UseCors("SiteCorsPolicy");
//..
}
And just to be sure I also added
[EnableCors("SiteCorsPolicy")]
to every controller.
The second problem I tried to solve by creating an HttpClient as Singleton in my Gateway and modifying following properties:
var client = new HttpClient();
client.Timeout = new TimeSpan(0, 10, 0);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
services.AddSingleton(client);
My gateway sends HTTP-requests like that:
[HttpGet("{id}")]
public async Task<JsonResult> GetById(int id) {
var response = await _client.GetAsync(new Uri(ServiceUrls.Customer + $"{id}"));
if (!response.IsSuccessStatusCode)
return new JsonResult(BadRequest($"{(int)response.StatusCode}: {response.ReasonPhrase}"));
var customer = JsonConvert.DeserializeObject<CustomerViewModel>(await response.Content.ReadAsStringAsync());
return new JsonResult(customer);
}
As I said, sometimes it works and sometimes it doesn't. Maybe you could give me some ideas whats my mistake. Thanks in advance!
Edit: The problem seems to appear more often if there are multiple async ajax calls from the Webapp at once. But this could also be just a feeling.
This probably has nothing to do with your CORS setup. When an error occurs (the HTTP call using HttpClient for example), the default exception handler middleware removes all response headers and returns the error response. This means that any CORS header added by your policy will be removed, therefore you'll receive the error about "No 'Access-Control-Allow-Origin' header is present on the requested resource.".
To overcome this you should handle any exceptions in the controller yourself and return the appropriate error result, or write middleware before or replace the default exception middleware with your own exception handling logic.
There is also an open GitHub issue about this behavior: https://github.com/aspnet/Home/issues/2378.
Ok guys,
thanks for getting me into the right direction. I think I found a workaround/fix. I had to set following property in my gateway:
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
I came upon this issue too, .NET Core 2.0 does not return http status codes on non-200 or non-300 responses. I fixed it by creating middlewear to sit in the creationg of the Response to be sent back to the client.
You can take a look at all the steps here:
https://medium.com/#arieldiaz92/net-core-2-0-fix-that-http-status-0-error-by-sending-cors-headers-on-failures-12fe3d245107
public void Configure(IApplicationBuilder app) {
app.Map("/hahaha", HandleMapTest);
app.Run(async (context) => {
await context.Response.WriteAsync("Hello World!");
});
}
public static void HandleMapTest(IApplicationBuilder app) {
app.Use(async (context, next) => {
await context.Response.WriteAsync("Before!");
await next.Invoke();
await context.Response.WriteAsync("After!");
});
}
The resulted response only has "Before!After!", but missing the "Hello World!". Why does it happen?
The Asp.net docs said:
Avoid modifying HttpResponse after invoking next, one of the next components in the pipeline may have written to the response, causing it to be sent to the client.
Which I don't understand what the "cause it to be sent to the client" mean.
By invokeing next you are passing control on to the next middleware in the chain which means that the current middleware loses control and further middlewares may have written to the response, flushed it or even closed the stream.
"Cause it to be sent to the client" means that one of the further middlewares may have done something, like the options above, to cause the response to be sent from your server to whatever client that is invokeing it. This is not guaranted to have happend, as proven by your code, but it is possible. Therefore, it is discuraged to write to the httpresponse after invoking next just in case.
I found out that the reason is the app.Run will not be inserted to the pipeline if the app.Map matches the request. This is quite unexpected to me.
In ASP.NET 5 I've seem that the following code gives an error 504 in the response:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use((next) => {
return async (context) => {
context.Response.StatusCode = 200;
await context.Response.WriteAsync("Hello World");
context.Response.StatusCode = 201;
await context.Response.WriteAsync("Status code changed");
};
});
}
}
I know we shouldn't change the status code in this arbitrary manner, but my question here is: why changing it gives a problem? The fact is that, commenting the line that changes the status code, and using Response.WriteAsync twice, doesn't give any kind of problem, but changing the status code gives.
When we do it this returns a 504 status code. I believe it has to do with the way the response is sent to the client. It happens because when we call Respose.WriteAsync the response message starts being sent already? What's the reason for this error to occur?
Headers are sent the moment content is written to the response body stream and so you cannot change the headers again...so if you are setting the status code again, then probably an exception is being thrown in the middleware to indicate this...
BTW this exception would cause the 504 that you are seeing(like currently there is no response buffering layer which could catch these kind of exceptions and returns a 500 Internal Server with an error message)...you can put a try-catch block to capture the exception message and see what it says..
We are using a self hosted WebApi and we are required to remove the server header (Server: Microsoft-HTTPAPI/2.0) of the responses sent.
Since it is self hosted, a HttpModule is not an option. Implementing a DelegatingHandler, access to headers as well as adding is possible. The asp.net website nicely details how one can do that.
But the server header seems to be added much later in the pipeline since it is not set in the HttpResponseMessage we return from the DelegatingHandler. However, we are able to add values.
async protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Headers.Server.Add(new ProductInfoHeaderValue("TestProduct", "1.0"));
response.Headers.Add("Server", "TestServerHeader");
return response;
}
Both Server.Add and .Add work as expected. response.Headers.Remove("Server"); however does not work, because the server header is not set, response.Headers.Server is empty.
Is there anything i am missing?
There is no code solution to remove Server HTTP header on self host.
The only solution is to edit windows registry:
https://learn.microsoft.com/ru-ru/archive/blogs/dsnotes/wswcf-remove-server-header
add
appBuilder.Use((context, next) =>
{
context.Response.Headers.Remove("Server");
context.Response.Headers.Add("Server", new[] { "" });
return next.Invoke();
});
to Startup Configuration method just before
config.EnsureInitialized();