超时Web Api请求

本文关键字:请求 Api Web 超时 | 更新日期: 2023-09-27 18:10:07

像MVC一样,WebApi运行在异步ASP上。. NET管道,意味着不支持执行超时。

在MVC中,我使用[AsyncTimeout]过滤器,WebApi没有这个。那么如何在WebApi中超时一个请求呢?

超时Web Api请求

基于Mendhak的建议,你可以做你想做的事情,尽管不完全是你想要的方式,而不需要跳过相当多的障碍。在不使用的情况下使用,过滤器可能看起来像这样:

public class ValuesController : ApiController
{
    public async Task<HttpResponseMessage> Get( )
    {
        var work    = this.ActualWork( 5000 );
        var timeout = this.Timeout( 2000 );
        var finishedTask = await Task.WhenAny( timeout, work );
        if( finishedTask == timeout )
        {
            return this.Request.CreateResponse( HttpStatusCode.RequestTimeout );
        }
        else
        {
            return this.Request.CreateResponse( HttpStatusCode.OK, work.Result );
        }
    }
    private async Task<string> ActualWork( int sleepTime )
    {
        await Task.Delay( sleepTime );
        return "work results";
    }
    private async Task Timeout( int timeoutValue )
    {
        await Task.Delay( timeoutValue );
    }
}

这里你将收到一个超时,因为我们正在做的实际"工作"将花费比超时更长的时间。

用属性做你想做的是可能的,尽管不是理想的。这是与前面相同的基本思想,但是过滤器实际上可以用来通过反射执行动作。我认为我不会推荐这种方式,但是在这个人为的例子中,您可以看到它是如何实现的:

public class TimeoutFilter : ActionFilterAttribute
{
    public int Timeout { get; set; }
    public TimeoutFilter( )
    {
        this.Timeout = int.MaxValue;
    }
    public TimeoutFilter( int timeout )
    {
        this.Timeout = timeout;
    }

    public override async Task OnActionExecutingAsync( HttpActionContext actionContext, CancellationToken cancellationToken )
    {
        var     controller     = actionContext.ControllerContext.Controller;
        var     controllerType = controller.GetType( );
        var     action         = controllerType.GetMethod( actionContext.ActionDescriptor.ActionName );
        var     tokenSource    = new CancellationTokenSource( );
        var     timeout        = this.TimeoutTask( this.Timeout );
        object result          = null;
        var work = Task.Run( ( ) =>
                             {
                                 result = action.Invoke( controller, actionContext.ActionArguments.Values.ToArray( ) );
                             }, tokenSource.Token );
        var finishedTask = await Task.WhenAny( timeout, work );
        if( finishedTask == timeout )
        {
            tokenSource.Cancel( );
            actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.RequestTimeout );
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.OK, result );
        }
    }
    private async Task TimeoutTask( int timeoutValue )
    {
        await Task.Delay( timeoutValue );
    }
}

可以这样使用:

[TimeoutFilter( 10000 )]
public string Get( )
{
    Thread.Sleep( 5000 );
    return "Results";
}

这适用于简单类型(例如字符串),在Firefox中给出:<z:anyType i:type="d1p1:string">Results</z:anyType>,尽管您可以看到,序列化并不理想。在这段代码中使用自定义类型在序列化过程中可能会有一些问题,但经过一些工作,这在某些特定场景中可能会很有用。操作参数以字典而不是数组的形式出现,这也可能在参数排序方面带来一些问题。显然,有真正的支持会更好。

就vNext而言,他们很可能计划为Web API添加服务器端超时的能力,因为MVC和API控制器是统一的。如果他们这样做,很可能不会通过System.Web.Mvc.AsyncTimeoutAttribute类,因为他们显式地删除了对System.Web的依赖。

到目前为止,在project.json文件中添加System.Web.Mvc条目似乎不可行,但这很可能会改变。如果是这样,虽然你不能使用新的云优化框架与这样的代码,你可能能够使用AsyncTimeout属性的代码,只打算与完整的。net框架运行。

对于它的价值,这是我试图添加到project.json。也许一个特定的版本会让它更快乐?

"frameworks": {
    "net451": {
        "dependencies": { 
            "System.Web.Mvc": ""
        }
    }
}

对它的引用确实显示在解决方案资源管理器的引用列表中,但是它使用黄色感叹号表示存在问题。应用程序本身返回500个错误,而这个引用仍然存在。

使用WebAPI,您通常会在客户端端处理超时,而不是在服务器端。这是因为,我引用:

取消HTTP请求的方法是直接在HttpClient上取消它们。原因是多个请求可以在单个HttpClient中重用TCP连接,因此您无法在不影响其他请求的情况下安全地取消单个请求。

你可以控制请求的超时时间——如果我没记错的话,我想它是在HttpClientHandler上的。

如果你真的需要在API端实现超时,我建议创建一个线程来完成你的工作,然后在一段时间后取消它。例如,您可以将其放在Task中,使用Task.Wait创建"超时"任务,并使用Task.WaitAny作为第一个返回的任务。这可以模拟超时。

同样,如果您正在执行一个特定的操作,请检查它是否已经支持超时。通常,我会从我的WebAPI执行HttpWebRequest,并指定其Timeout属性。

对于你想要超时的每个端点,通过一个CancellationToken管道,例如:

[HttpGet]
public Task<Response> GetAsync()
{
    var tokenSource = new CancellationTokenSource(_timeoutInSec * 1000);
    return GetResponseAsync(tokenSource.Token);
}

让您的生活更轻松,在您的基础控制器中添加以下方法:

    protected async Task<T> RunTask<T>(Task<T> action, int timeout) {
        var timeoutTask = Task.Delay(timeout);
        var firstTaskFinished = await Task.WhenAny(timeoutTask, action);
        if (firstTaskFinished == timeoutTask) {
            throw new Exception("Timeout");
        }
        return action.Result;
    }

现在每个从你的基控制器继承的控制器都可以访问RunTask方法。现在在API中像这样调用RunTask方法:

    [HttpPost]
    public async Task<ResponseModel> MyAPI(RequestModel request) {
        try {
            return await RunTask(Action(), Timeout);
        } catch (Exception e) {
            return null;
        }
    }
    private async Task<ResponseModel> Action() {
        return new ResponseModel();
    }