Reputation: 31
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:
Have a using
statement for each use of HttpClient
in ApiRequestBuilder
- still hangs.
Await each Task
in the for loop in ValuesController.Test
- this works, but does not solve my problem, as in practice I'm trying to simulate concurrency for using my custom auth attribute.
Potentially useful:
I'm running local IIS on port 44301
, the maximum concurrent connections is massive - 4294967295.
Like I said, changing the url from localhost
to something like example.com
when authorising, fixes the issue, which isolates the issue to something being wrong with my local setup
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
Reputation: 286
This solution is for . Net core 2.1 but it may explain your issue: https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
Upvotes: 0
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