【问题标题】:HttpClient hangs on GetAsync when making many web requests to localhost向 localhost 发出许多 Web 请求时,HttpClient 在 GetAsync 上挂起
【发布时间】:2018-10-12 09:09:32
【问题描述】:

我在致电api/Values/Test?times={x} 时遇到以下问题。 x 为 1、2、...时不会出现此问题,但在达到 9、10 等时 Web 请求会超时。 如您所见,Test 端点将向EndPointx 发出网络请求次(请原谅无意义的方法体,它们在实践中会做更有意义的事情):

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见下文ApiRequestBuilder是什么)

然而,问题不是 EndPoint 被击中 x 次,而是 CustomAuthorise 属性发出进一步的网络请求,这就是问题所在:

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

如您所见,我已将路线 "https://www.example.com" 注释掉。当我将 url 设置为不是 localhost 的内容时,无论我想要多少,都可以向该网络请求发送垃圾邮件(通过将 x 设置为一个大数字)。

ApiRequestBuilder 是通过System.Net.Http.HttpClient(版本4.0.0.0,.NETframework v4.5.1)发出Web 请求的助手,它将响应序列化为您提供给它的泛型类型参数。

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

我尝试过的事情:

  • ApiRequestBuilder 中每次使用HttpClient 都有一个using 语句 - 仍然挂起。

  • ValuesController.Test 的 for 循环中等待每个 Task - 这可行,但不能解决我的问题,因为实际上我正在尝试模拟并发性以使用我的自定义身份验证属性。

可能有用:

  • 我在端口 44301 上运行本地 IIS,最大并发连接数很大 - 4294967295。

  • 就像我说的,在授权时将 url 从 localhost 更改为 example.com 可以解决问题,从而将问题隔离为我的本地设置有问题

有谁知道为什么在授权属性中使用 Web 请求似乎与使用 localhost 的并发性有关,而不是在广阔的世界中使用 url?

更新

在挂起时运行netstat -n(我为此删除了超时,以便查看 TCP 连接在做什么)。对于看似被阻止的每个正在进行的网络请求,我都有以下内容:

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

this 的帮助下,似乎服务器已要求客户端退出,客户端已确认,但它们都挂起(客户端没有关闭?)。

【问题讨论】:

    标签: asp.net asp.net-web-api2 authorization dotnet-httpclient


    【解决方案1】:

    可能是因为创建的客户端过多,导致已知的套接字耗尽问题。

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

    使用单个静态客户端应该可以解决这个问题

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

    【讨论】:

    • 同意这个答案。更多信息可以查看HttpClient Issues
    • @user3785553 是的,我也看过那篇文章。将其包含在答案中以供参考。
    • @Nkosi 感谢您的帮助。不幸的是,将HttpClient 更改为静态属性并不能解决问题。如果是这种情况,我希望您提到的套接字耗尽问题在使用“www.example.com”而不是localhost 时也是一个问题
    • HttpClient 有限制,当我在本地机器上使用邮递员测试 1000 次调用而没有间隔暂停时,我自己就有了这个限制。似乎套接字会被堵塞。也许 .net core 2.1 / 2.2 中新的 httpclientfactory 机制可以帮上忙
    【解决方案2】:
    猜你喜欢
    • 2013-11-03
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-31
    • 2019-05-12
    • 1970-01-01
    相关资源
    最近更新 更多