Why can't the HttpResponse be changed after 'next' call? - c#

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.

Related

Why does ASP.NET Core return a 404 at the end of the Middleware Pipeline?

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.

When exactly is HttpResponse being sent to the client in .net core API middleware pipeline?

I am so confused about when IIS sends back the response, I really want to know how this works under the hood since I am doing response logging and I don't want to corrupt the body.
This is my logging middleware Invoke which is called before the MVC middleware (controllers). Is the response sent IMMEDIATELY after controller returns its Ok method or does it wait for the rest of the middleware? Is there any chance of corrupting the response if I do logging this way?
public async Task Invoke(HttpContext context)
{
var requestResponseLog = new RequestResponseLog
{
RequestTime = DateTimeOffset.UtcNow,
Request = await FormatRequest(context)
};
Stream originalBody = context.Response.Body;
using (MemoryStream newResponseBody = _recyclableMemoryStreamManager.GetStream())
{
context.Response.Body = newResponseBody;
await _next(context);
newResponseBody.Seek(0, SeekOrigin.Begin);
await newResponseBody.CopyToAsync(originalBody);
newResponseBody.Seek(0, SeekOrigin.Begin);
requestResponseLog.ResponseTime = DateTimeOffset.UtcNow;
requestResponseLog.Response = await FormatResponse(context, newResponseBody);
_requestResponseHandler(requestResponseLog);
}
}
Think of each registered middleware as a step along the path of creating a request. Your invoke method is "your" step on that path. The await _next(context); instructs the framework to move onto the next middleware in the pipeline at that point - but - come back after the rest of the middlewares have executed, so that your code can be picked back up again.
So.. With that in mind, lets assume we have 3 middlewares set up. The pipeline works like this:
[REQUEST COMES IN]
Middleware-1 => Middleware-2 => Middleware-3
[THE RESPONSE IS PRODUCED]
Middleware-3 => Middleware-2 => Middleware-1
[RESPONSE GOES OUT TO CALLER]
Lets say you added await _next(context) into Middleware2's Invoke method. When the code reaches that point, it skips onto the next middleware in the pipeline, but it knows to stop there when it comes back through with the response. So you can intercept it again.
If you are logging a request, your custom code would go before that await _next(context) call. If you are logging a response, it would come after that call.
Note: Additionally, middleware ordering is determined by the order they are registered in your Startup class.

Using Polly to retry after HttpStatusCode.Unauthorized

I'm making calls to an external API and want to deal with the event that a call returns an Unauthorized HttpResponseMessage. When this happens I want to refresh the access token and make the call again.
I'm trying to use Polly with the following code:
public async Task<HttpResponseMessage> MakeGetRequestAsync()
{
var retryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.Retry(1, (exception, retryCount) =>
{
RefreshAccessToken();
});
var result = await retryPolicy.ExecuteAsync(() => CallApiAsync());
return result;
}
private async Task<HttpResponseMessage> CallApiAsync()
{
var url = Options.ResourceSandboxUrl;
var httpClient = new HttpClient();
SetRequestHeaders(httpClient);
var response = await httpClient.GetAsync(url);
response.StatusCode = HttpStatusCode.Unauthorized;
return response;
}
I put breakpoints on the ExecuteAsync statement and in DoSomethingAsync - when I step over ExecuteAsync DoSomethingAsync is not called and control is returned to the function that called MakeGetRequestAsync
I don't understand why DoSomethingAsync is not called - can anyone help me with what I'm trying to achieve?
I've looked at the Polly documentation & Polly questions on Stack Overflow but I can't figure out what's going on..
To use ExecuteAsync() you must declare the policy as .RetryAsync(...), not .Retry(...).
If your actual code reads exactly as the code sample above, the .ExecuteAsync(...) will be throwing for the mismatch between .Retry(...) [a sync policy] and .ExecuteAsync(...) [an async execution]. Since this exception is thrown, CallApiAsync() is indeed never invoked. You should be able to see the thrown exception, when calling MakeGetRequestAsync()
Overall code approach looks good tho: this retry-refreshing-authentication is a proven pattern with Polly!
I'm replying to this old question just to point out the Polly wiki page where this pattern was official documented:
retry-to-refresh-authorization
In particular this is the code snippet suggested:
var authorisationEnsuringPolicy = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(
retryCount: 1, // Consider how many retries. If auth lapses and you have valid credentials, one should be enough; too many tries can cause some auth systems to blacklist.
onRetryAsync: async (outcome, retryNumber, context) => FooRefreshAuthorizationAsync(context),
/* more configuration */);
var response = authorisationEnsuringPolicy.ExecuteAsync(context => DoSomethingThatRequiresAuthorization(context), cancellationToken);
The FooRefreshAuthorizationAsync(...) method can obtain a new authorization token and pass it to the delegate executed through the policy using Polly.Context.
I might be late to the game but I leave here a post for future readers. I have created two slightly different solutions for this refresh token with retry problem.
Retry policy, Delegating Handler and Custom exception
Here is sequence diagram which depicts the communication flow
Here is the full source code
Retry policy, Delegating Handler and Polly.Context
This version separates the responsibilities in a different way:
Here is sequence diagram which depicts the communication flow
Here is the full source code
this is how async await work in .net, when the execution of your code reaches an await, two things will happened
the current thread of your code should release to make your code async, that means, your method should return
when the task you await is complete, your method should continue from where it used to be

Why changing the status code after writing to the body returns 504?

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..

Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending

I'm writing an application that proxies some HTTP requests using the ASP.NET Web API and I am struggling to identify the source of an intermittent error.
It seems like a race condition... but I'm not entirely sure.
Before I go into detail here is the general communication flow of the application:
Client makes a HTTP request to Proxy 1.
Proxy 1 relays the contents of the HTTP request to Proxy 2
Proxy 2 relays the contents of the HTTP request to the Target Web Application
Target Web App responds to the HTTP request and the response is streamed (chunked transfer) to Proxy 2
Proxy 2 returns the response to Proxy 1 which in turn responds to the original calling Client.
The Proxy applications are written in ASP.NET Web API RTM using .NET 4.5.
The code to perform the relay looks like so:
//Controller entry point.
public HttpResponseMessage Post()
{
using (var client = new HttpClient())
{
var request = BuildRelayHttpRequest(this.Request);
//HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
//As it begins to filter in.
var relayResult = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
var returnMessage = BuildResponse(relayResult);
return returnMessage;
}
}
private static HttpRequestMessage BuildRelayHttpRequest(HttpRequestMessage incomingRequest)
{
var requestUri = BuildRequestUri();
var relayRequest = new HttpRequestMessage(incomingRequest.Method, requestUri);
if (incomingRequest.Method != HttpMethod.Get && incomingRequest.Content != null)
{
relayRequest.Content = incomingRequest.Content;
}
//Copies all safe HTTP headers (mainly content) to the relay request
CopyHeaders(relayRequest, incomingRequest);
return relayRequest;
}
private static HttpRequestMessage BuildResponse(HttpResponseMessage responseMessage)
{
var returnMessage = Request.CreateResponse(responseMessage.StatusCode);
returnMessage.ReasonPhrase = responseMessage.ReasonPhrase;
returnMessage.Content = CopyContentStream(responseMessage);
//Copies all safe HTTP headers (mainly content) to the response
CopyHeaders(returnMessage, responseMessage);
}
private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
var content = new PushStreamContent(async (stream, context, transport) =>
await sourceContent.Content.ReadAsStreamAsync()
.ContinueWith(t1 => t1.Result.CopyToAsync(stream)
.ContinueWith(t2 => stream.Dispose())));
return content;
}
The error that occurs intermittently is:
An asynchronous module or handler completed while an asynchronous operation was still pending.
This error usually occurs on the first few requests to the proxy applications after which the error is not seen again.
Visual Studio never catches the Exception when thrown.
But the error can be caught in the Global.asax Application_Error event.
Unfortunately the Exception has no Stack Trace.
The proxy applications are hosted in Azure Web Roles.
Any help identifying the culprit would be appreciated.
Your problem is a subtle one: the async lambda you're passing to PushStreamContent is being interpreted as an async void (because the PushStreamContent constructor only takes Actions as parameters). So there's a race condition between your module/handler completing and the completion of that async void lambda.
PostStreamContent detects the stream closing and treats that as the end of its Task (completing the module/handler), so you just need to be sure there's no async void methods that could still run after the stream is closed. async Task methods are OK, so this should fix it:
private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
Func<Stream, Task> copyStreamAsync = async stream =>
{
using (stream)
using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
{
await sourceStream.CopyToAsync(stream);
}
};
var content = new PushStreamContent(stream => { var _ = copyStreamAsync(stream); });
return content;
}
If you want your proxies to scale a bit better, I also recommend getting rid of all the Result calls:
//Controller entry point.
public async Task<HttpResponseMessage> PostAsync()
{
using (var client = new HttpClient())
{
var request = BuildRelayHttpRequest(this.Request);
//HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
//As it begins to filter in.
var relayResult = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var returnMessage = BuildResponse(relayResult);
return returnMessage;
}
}
Your former code would block one thread for each request (until the headers are received); by using async all the way up to your controller level, you won't block a thread during that time.
I would like to add some wisdom for anyone else who landed here with the same error, but all of your code seems fine. Look for any lambda expressions passed into functions across the call-tree from where this occurs.
I was getting this error on a JavaScript JSON call to an MVC 5.x controller action. Everything I was doing up and down the stack was defined async Task and called using await.
However, using Visual Studio's "Set next statement" feature I systematically skipped over lines to determine which one caused it. I kept drilling down into local methods until I got to a call into an external NuGet package. The called method took an Action as a parameter and the lambda expression passed in for this Action was preceded by the async keyword. As Stephen Cleary points out above in his answer, this is treated as an async void, which MVC does not like. Luckily said package had *Async versions of the same methods. Switching to using those, along with some downstream calls to the same package fixed the problem.
I realize this is not a novel solution to the problem, but I passed over this thread a few times in my searches trying to resolve the issue because I thought I didn't have any async void or async <Action> calls, and I wanted to help someone else avoid that.
A slightly simpler model is that you can actually just use the HttpContents directly and pass them around inside the relay. I just uploaded a sample illustrating how you can rely both requests and responses asynchronously and without buffering the content in a relatively simple manner:
http://aspnet.codeplex.com/SourceControl/changeset/view/7ce67a547fd0#Samples/WebApi/RelaySample/ReadMe.txt
It is also beneficial to reuse the same HttpClient instance as this allows you to reuse connections where appropriate.

Categories