Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timeout a Web Api request?

Tags:

Like MVC WebApi runs on the asynchronous ASP.NET pipeline, meaning execution timeout is unsupported.

In MVC I use the [AsyncTimeout] filter, WebApi doesn't have this. So how do I timeout a request in WebApi?

like image 358
DalSoft Avatar asked Jun 25 '14 08:06

DalSoft


People also ask

What is API request timeout?

Timeouts happen if a service takes more than 30 seconds to respond to a call. If a timeout occurs, you'll see the 500 error status code with details about the timeout in the response. Timeouts are typically caused by one of two things: The call involves too much data. There is a network/service issue.

How do I set timeout in request?

To set a timeout in Python Requests, you can pass the "timeout" parameter for GET, POST, PUT, HEAD, and DELETE methods. The "timeout" parameter allows you to select the maximum time (number of seconds) for the request to complete. By default, requests do not have a timeout unless you explicitly specify one.

How long should I timeout for an API call?

Generally 1 Second is considered acceptable. The reason for this and why the suggested numbers vary so much is most APIs have a lockout if you send requests to fast. However, some APIs will let you send requests faster.


2 Answers

Building on the suggestion by Mendhak, it is possible to do what you want, though not exactly the way you'd like to do it without jumping through quite a few hoops. Doing it without a filter might look something like this:

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 );
    }
}

Here you will receive a timeout because the actual "work" we're doing will take longer than the timeout.

To do what you want with an attribute is possible, though not ideal. It's the same basic idea as before, but the filter could actually be used to execute the action via reflection. I don't think I would recommend this route, but in this contrived example, you can see how it might be done:

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 );
    }
}

This could then be used like this:

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

This works for simple types (e.g. string), giving us: <z:anyType i:type="d1p1:string">Results</z:anyType> in Firefox, though as you can see, the serialization is not ideal. Using custom types with this exact code will be a bit problematic as far as serialization goes, but with some work, this could probably be useful in some specific scenarios. That the action parameters come in the form of a dictionary instead of an array could also pose some issues in terms of the parameter ordering. Obviously having real support for this would be better.

As far as the vNext stuff goes, they may well be planning to add the ability to do server-side timeouts for Web API since MVC and API controllers are being unified. If they do, it will likely not be through the System.Web.Mvc.AsyncTimeoutAttribute class, as they are explicitly removing dependencies on System.Web.

As of today, it doesn't appear that adding a System.Web.Mvc entry to the project.json file works, but this may well change. If it does, while you wouldn't be able to use the new cloud-optimized framework with such code, you might be able to use the AsyncTimeout attribute on code that is only intended to run with the full .NET framework.

For what it's worth, this is what I tried adding to project.json. Perhaps a specific version would have made it happier?

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

A reference to it does show up in the Solution Explorer's references list, but it does so with a yellow exclamation point indicating a problem. The application itself returns 500 errors while this reference remains.

like image 197
Adam Avatar answered Sep 18 '22 16:09

Adam


With WebAPI, you would generally handle timeouts on the client side, rather than the server side. This is because, and I quote:

The way to cancel HTTP requests is to cancel them on the HttpClient directly. The reason being that multiple requests can reuse TCP connections within a single HttpClient and so you can't safely cancel a single request without possibly affecting other requests as well.

You can control the timeout for requests -- I think it's on the HttpClientHandler if I recall correctly.

If you really need to implement a timeout on the API side itself, I would recommend creating a thread to do your work in, and then cancelling it after a certain period. You could for example put it in a Task, create your 'timeout' task using Task.Wait and use Task.WaitAny for the first one to come back. This can simulate a timeout.

Similarly, if you are performing a specific operation, check to see if it already supports timeouts. Quite often, I will perform an HttpWebRequest from my WebAPI, and specify its Timeout property.

like image 21
Mendhak Avatar answered Sep 18 '22 16:09

Mendhak