问题
I need a middleware to cancel a request if my api is taking more than x amount of time. All my api calls are asynch and cancellable:
public async Task<IActionResult> Get(CancellationToken token) { }
...
public async Task InvokeAsync(HttpContext context)
{
//....
int customDurationMilis = 1000; //1second
cancellTimer = new Timer(CancellRequestCallBack, context, customDurationMilis, Timeout.Infinite);
//...
await _next(context);
}
private void CancellRequestCallBack(HttpContext context)
{
//log...
//Aborts the connection underlying this request.
context.Abort();
}
imagine I have an api action method taking 2 seconds, the timer will trigger the CancellRequestCallBack which is doing a connection abort. Is good idea to have a Timer? or should go with another approach? How to return a custom error code instead of context.Abort() and cancel the ongoing api call? some ideas will be very much appreciate. thanks
UPDATE, trying to do it with this :
public async Task InvokeAsync(HttpContext context)
{
var task = _next(context);
if (await Task.WhenAny(task, Task.Delay(1000)) != task)
{
throw new MyCustomException("timeout!");
}
}
at least with this I catch the exception in errorhandlermiddleware returning a status code 500. But I don;t get it in Postman. Instead I am getting "Could not get any response" Also the thread with the request will still run I guess.
回答1:
This is actually pretty difficult to implement for a couple of reasons:
- Cancellation is cooperative. This means you need everything you're awaiting (transitively) to respect the token.
- Being async means there's no thread to abort. You can abort the entire TCP connection but if nobody is listening to the token and nobody is reading the body, it won't do anything to the running server code.
- Using
Task.WhenAny
isn't a good solution because you might end up using theHttpContext
in parallel. The code you "cancelled" might be about to write a valid response at the same time your middleware is trying to write a timeout response. Even if you throw an exception, it's dangerous because there's other running code that might be touching theHttpContext
in parallel.
You can risk it, but there might be subtle race conditions that cause things to fail in unexpected ways.
If you're respecting cancellation tokens, then you can just cancel the token and wait for next to unwind. When it does, then you can see if the response has started before writing to it and return a 408 (request timed out).
回答2:
came up with a solution respecting the tokens. The idea is to create a new token and define a timeout for it. But additionally, will need to link the original token with the new one using CreateLinkedTokenSource.
public async Task InvokeAsync(HttpContext context)
{
using (var timoutTS = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted))
{
timoutTS.CancelAfter(200);
context.RequestAborted = timoutTS.Token;
await _next(context);
}
}
回答3:
How about adding resilience and transient fault handling with Polly?
Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.
来源:https://stackoverflow.com/questions/52630613/asp-net-core-middleware-that-cancel-a-request-if-taking-to-long