Owin自托管 - 限制请求的最长时间

时间:2018-03-02 19:00:53

标签: c# owin

如何在服务器端有效限制请求长度超时?我使用Microsoft.Owin.Host.HttpListener并且有些情况(由于呼叫外部服务)服务请求需要花费大量时间。这不是问题 - 但是网络服务器应该早点放弃 - 永远不会(我做了一些测试,但是在5分钟后我停止了它)。

有没有办法限制服务单个请求的时间(类似于IIS生态系统中的<httpRuntime maxRequestLength="..." />)?

示例控制器代码:

public async Task<HttpResponseMessage> Get() {
  // ... calls to 3pty services here
   await Task.Delay(TimeSpan.FromMinutes(5));
}

启动网络服务器:

WebApp.Start(this.listeningAddress, new Action<IAppBuilder>(this.Build));

注意:我已经阅读了有关限制http侦听器的内容,但这只是限制了传入的请求属性,它不会取消因服务器处理速度慢而导致请求缓慢的请求:

var listener = appBuilder.Properties[typeof(OwinHttpListener).FullName] as OwinHttpListener;
var timeoutManager = listener.Listener.TimeoutManager;
timeoutManager.DrainEntityBody = TimeSpan.FromSeconds(20);
timeoutManager.EntityBody = TimeSpan.FromSeconds(20);
timeoutManager.HeaderWait = TimeSpan.FromSeconds(20);
timeoutManager.IdleConnection = TimeSpan.FromSeconds(20);
timeoutManager.RequestQueue = TimeSpan.FromSeconds(20);

相关:

https://github.com/aspnet/AspNetKatana/issues/152

1 个答案:

答案 0 :(得分:3)

从概念上讲,“较旧”的Web服务器解决方案 - 即IIS使用每个请求一个线程分离,ThreadAbortException使用慢速请求。 Owin使用不同的哲学 - 即它根据请求激发新任务并且最好避免强行取消任务。这个问题有两个方面:

  • 如果需要太长时间,请客户离开
  • 如果花费太长时间取消服务器处理

两者都可以使用中间件组件实现。对于客户端断开连接的情况(context.Request.CallCancelled contextIOwinContext

,还有owin基础设施直接提供的取消令牌

如果你只是想在很长时间内尽快取消服务器流程,我会推荐像

这样的东西。
public class MyMiddlewareClass : OwinMiddleware
{
    // 5 secs is ok for testing, you might want to increase this
    const int WAIT_MAX_MS = 5000;

    public MyMiddlewareClass(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        using (var source = CancellationTokenSource.CreateLinkedTokenSource(
            context.Request.CallCancelled))
        {
            source.CancelAfter(WAIT_MAX_MS);
            // combined "client disconnected" and "it takes too long" token
            context.Set("RequestTerminated", source.Token);
            await Next.Invoke(context);
        }
    }
}

然后在控制器中

public async Task<string> Get()
{
  var context = this.Request.GetOwinContext();
  var token = context.Get<CancellationToken>("RequestTerminated");
  // simulate long async call
  await Task.Delay(10000, token);
  token.ThrowIfCancellationRequested();

  return "Hello !";
}

避开客户端更复杂。中间件看起来像这样:

public static async Task ShutDownClientWhenItTakesTooLong(IOwinContext context, 
    CancellationToken timeoutToken)
{
    await Task.Delay(WAIT_MAX_MS, timeoutToken);
    if (timeoutToken.IsCancellationRequested)
    {
        return;
    }

    context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
}

public async Task ExecuteMainRequest(IOwinContext context, 
    CancellationTokenSource timeoutSource, Task timeoutTask)
{
    try
    {
       await Next.Invoke(context);
    }
    finally
    {
       timeoutSource.Cancel();
       await timeoutTask;
    }
}

public override async Task Invoke(IOwinContext context)
{
    using (var source = CancellationTokenSource.CreateLinkedTokenSource(
        context.Request.CallCancelled))
    using (var timeoutSource = new CancellationTokenSource())
    {
        source.CancelAfter(WAIT_MAX_MS);
        context.Set("RequestTerminated", source.Token);
        var timeoutTask = ShutDownClientWhenItTakesTooLong(context, timeoutSource.Token);
        await Task.WhenAny(
            timeoutTask,
            ExecuteMainRequest(context, timeoutSource, timeoutTask)
        );
    }
}