Owen Lacey
Owen Lacey

Reputation: 31

HttpClient hangs on GetAsync when making many web requests to localhost

I'm having the below issue when calling api/Values/Test?times={x}. The issue doesn't occur when x is 1, 2, ..., but the web request times out when it reaches 9, 10, and so on. As you can see, the Test endpoint will make a web request to EndPoint x times (excuse the pointless method bodies, they'll do more meaningful things in practice):

public class ValuesController:  System.Web.Http.ApiController
{
    [HttpGet]
    public async Task<object> Test(int times)
    {
        var tasks = new List<Task<int>>();
        for (var i = 0; i < times; i++)
        {
            //Make web request to the `EndPoint` method below
            var task = new ApiRequestBuilder<int>("https://localhost:44301/api/Values/EndPoint")
                .GetAsync();
            tasks.Add(task);
        }
        return await Task.WhenAll(tasks.ToArray());
    } 

    [HttpGet]
    [CustomAuthorise]//This makes a web request to `AuthEndPoint` (below)
    public int EndPoint()
    {
        return 0;
    }

    [HttpGet]
    public bool AuthEndPoint()
    {
        return true;
    }
}

(NB see below for what ApiRequestBuilder is)

The problem, however, isn't EndPoint being hit x times, it's the CustomAuthorise attribute, which makes a further web request, that is the problem:

public class CustomAuthoriseAttribute : System.Web.Http.AuthorizeAttribute
{
    public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        var result = await this.AuthoriseAsync(actionContext);
        if (!result)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
        }
    }

    public virtual async Task<bool> AuthoriseAsync(HttpActionContext context)
    {
        //***********HANGING HERE************
        //var result= await new ApiRequestBuilder<bool>("https://www.example.com")
        var result= await new ApiRequestBuilder<bool>("https://localhost:44301/Values/AuthEndPoint")
            .GetAsync();

        return result;
    }
}

As you can see, I've commented out the route "https://www.example.com". When I set the url to something that isn't localhost, a can spam that web request (by setting x to a large number) however much I want.

ApiRequestBuilder is a helper for making web requests via a System.Net.Http.HttpClient (version 4.0.0.0, .NETframework v4.5.1), that serializes the response to the generic type parameter you give it.

public class ApiRequestBuilder<T> 
{
    protected string Url => Uri?.AbsoluteUri;
    protected Uri Uri;
    protected readonly HttpClient Client;

    public ApiRequestBuilder(string url) 
    {
        Uri = new Uri(url);
        var client = new HttpClient()
        {
            BaseAddress = this.Uri,
            //Set timeout so replicating this issue itn't a pain
            Timeout = TimeSpan.FromSeconds(5)
        };
        this.Client = client;       
    }

    public async Task<T> GetAsync()
    {
        var response = await this.Client.GetAsync(this.Url);
        var responseContent = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseContent);
    }
}

Things I've tried:

Potentially useful:

Does anyone know why using web request in authorise attributes seems hang with concurrency using localhost rather than using a url out there in the big wide world?

UPDATE

Running netstat -n at the point of hanging (I removed the timeout for this so I could look at what the TCP connections were doing). For every web request in progress that was seemingly blocked I had the following:

Proto  Local Address          Foreign Address        State
TCP    [::1]:44301            [::1]:44728            CLOSE_WAIT
TCP    [::1]:44728            [::1]:44301            FIN_WAIT_2

With the help of this, it seems like the server has asked the client to exit, the clients acknowledged it, but they're both hanging (client isn't closing?).

Upvotes: 3

Views: 2190

Answers (2)

Nkosi
Nkosi

Reputation: 247323

Could be that too many clients are being created and causing a known issue with socket exhaustion.

Refrence YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE

Using a single static client should potentially solve this problem

public class ApiRequestBuilder<T>  {
    protected string Url => Uri?.AbsoluteUri;
    protected Uri Uri;
    static HttpClient Client = new HttpClient() {
        //Set timeout so replicating this issue itn't a pain
        Timeout = TimeSpan.FromSeconds(5)
    };

    public ApiRequestBuilder(string url) {
        Uri = new Uri(url);        
    }

    public async Task<T> GetAsync() {
        var response = await Client.GetAsync(this.Url);
        var result = await response.Content.ReadAsAsync<T>();
        return result;
    }
}

Upvotes: 1

Related Questions