【问题标题】:cURL request with HttpClient - how to set the timeout on the server connection (WinInet)使用 HttpClient 的 cURL 请求 - 如何在服务器连接上设置超时 (WinInet)
【发布时间】:2019-10-14 11:00:26
【问题描述】:

我正在通过下面描述的方法使用 HttpClient 发送 cURL 请求。

这个方法使用的参数是:

SelectedProxy = 存储代理参数的自定义类

Parameters.WcTimeout = 超时时间

url, header, content = cURL请求(基于此工具转换为C#https://curl.olsh.me/)。

        const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
        const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;
        ServicePointManager.SecurityProtocol = Tls12;
        string source = "";

        using (var handler = new HttpClientHandler())
        {
            handler.UseCookies = usecookies;
            WebProxy wp = new WebProxy(SelectedProxy.Address);
            handler.Proxy = wp;

            using (var httpClient = new HttpClient(handler))
            {
                httpClient.Timeout = Parameters.WcTimeout;

                using (var request = new HttpRequestMessage(new HttpMethod(HttpMethod), url))
                {
                    if (headers != null)
                    {
                        foreach (var h in headers)
                        {
                            request.Headers.TryAddWithoutValidation(h.Item1, h.Item2);
                        }
                    }
                    if (content != "")
                    {
                        request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
                    }

                    HttpResponseMessage response = new HttpResponseMessage();
                    try
                    {
                        response = await httpClient.SendAsync(request);
                    }
                    catch (Exception e)
                    {
                        //Here the exception happens
                    }
                    source = await response.Content.ReadAsStringAsync();
                }
            }
        }
        return source;

如果我在没有代理的情况下运行它,它就像一个魅力。 当我使用我首先从 Chrome 测试的代理发送请求时,我的 try {} catch {} 出现以下错误。这是错误树

{"An error occurred while sending the request."}
    InnerException {"Unable to connect to the remote server"}
        InnerException {"A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond [ProxyAdress]"}
        SocketErrorCode: TimedOut

通过使用秒表,我看到 TimedOut 发生在大约 30 秒后。


我根据以下链接Whats the difference between HttpClient.Timeout and using the WebRequestHandler timeout properties?HttpClient Timeout confusion 或 WinHttpHandler 尝试了几个不同的处理程序。

值得注意的是,WinHttpHandler 允许使用不同的错误代码,即 Error 12002 calling WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, 'The operation timed out'。根本原因是相同的,尽管它有助于定位它的错误位置(即 WinInet),这也证实了 @DavidWright 关于 HttpClient 的超时管理请求发送的不同部分的说法。

因此,我的问题来自与服务器建立连接所需的时间,这会触发 WinInet 的 30 秒超时。

我的问题是如何更改这些超时?

附带说明,值得注意的是,使用 WinInet 的 Chrome 似乎并没有受到这种超时的影响,我的应用程序的很大一部分所基于的 Cefsharp 也没有受到影响,并且相同的代理可以通过它正确发送请求.

【问题讨论】:

    标签: c# curl timeout dotnet-httpclient


    【解决方案1】:

    感谢@DavidWright,我明白了一些事情:

    1. 在发送HttpRequestMessage 并从HttpClient 开始超时之前,将启动与服务器的 TCP 连接
    2. TCP 连接有自己的超时,在操作系统级别定义,我们没有确定在运行时从 C# 更改它的方法(如果有人愿意贡献,问题待定)
    3. 坚持尝试连接工作,因为每次尝试都受益于之前的尝试,但需要实施适当的异常管理和手动超时计数器(我实际上在我的代码中考虑了多次尝试,假设每次尝试大约 30 秒)李>

    所有这些最终都在以下代码中结束:

            const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
            const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;
            ServicePointManager.SecurityProtocol = Tls12;
            var sp = ServicePointManager.FindServicePoint(endpoint);
    
            sp.ConnectionLeaseTimeout = (int)Parameters.ConnectionLeaseTimeout.TotalMilliseconds;
    
    
            string source = "";
    
            using (var handler = new HttpClientHandler())
            {
                handler.UseCookies = usecookies;
                WebProxy wp = new WebProxy(SelectedProxy.Address);
                handler.Proxy = wp;
    
                using (var client = new HttpClient(handler))
                {
                    client.Timeout = Parameters.WcTimeout;
    
                    int n = 0;
                    back:
                    using (var request = new HttpRequestMessage(new HttpMethod(HttpMethod), endpoint))
                    {
    
                        if (headers != null)
                        {
                            foreach (var h in headers)
                            {
                                request.Headers.TryAddWithoutValidation(h.Item1, h.Item2);
                            }
                        }
                        if (content != "")
                        {
                            request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
                        }
                        HttpResponseMessage response = new HttpResponseMessage();
    
                        try
                        {
                            response = await client.SendAsync(request);
                        }
                        catch (Exception e)
                        {
                            if(e.InnerException != null)
                            {
                                if(e.InnerException.InnerException != null)
                                {
                                    if (e.InnerException.InnerException.Message.Contains("A connection attempt failed because the connected party did not properly respond after"))
                                    {
                                        if (n <= Parameters.TCPMaxTries)
                                        {
                                            n++;
                                            goto back;
                                        }
                                    }
                                }
                            }
                            // Manage here other exceptions
                        }
                        source = await response.Content.ReadAsStringAsync();
                    }
                }
            }
            return source;
    

    附带说明,我当前的HttpClient 实现将来可能会出现问题。尽管是一次性的,HttpClient 应该通过静态定义在 App 级别,而不是在 using 语句中。要了解更多信息,请转到 herethere

    我的问题是我想在每个请求时更新代理,并且它不是基于每个请求设置的。虽然它解释了新的 ConnectionLeaseTimeout 参数的原因(以最小化租约保持打开的时间),但它是一个不同的主题

    【讨论】:

      【解决方案2】:

      我在使用 HttpClient 时遇到了同样的问题。要返回 SendAsync 需要做两件事:首先,设置发生通信的 TCP 通道(SYN、SYN/ACK、ACK 握手,如果您熟悉的话),其次取回构成通过该 TCP 通道的 HTTP 响应。 HttpClient 的超时仅适用于第二部分。第一部分的超时时间由操作系统的网络子系统控制,在 .NET 代码中更改该超时时间非常困难。

      (这里是重现这种效果的方法。在两台机器之间建立一个有效的客户端/服务器连接,这样你就知道名称解析、端口访问、侦听以及客户端和服务器逻辑都可以工作。然后拔下网线服务器并重新运行客户端请求。无论您在 HttpClient 上设置什么超时,它都会以操作系统的默认网络超时超时。)

      我知道的唯一方法是在不同的线程上启动您自己的延迟计时器,如果计时器首先完成,则取消 SendAsync 任务。您可以使用 Task.Delay 和 Task.WaitAny 或通过创建具有所需时间的 CancellationTokenSource 来执行此操作(这实际上只是在引擎盖下执行第一种方式)。在任何一种情况下,您都需要小心取消和读取输掉比赛的任务的异常。

      【讨论】:

      • 感谢@DavidWright 对于计时器,我可以设置它,例如try { Task[] t = new Task[1]; t[0] = Task.Run(async () =&gt; [whatever]); Task.WaitAll(t, timeout); } catch { //cancel whatever is still pending }。但是对于 TCP 部分,我不知道如何处理它;)
      • @samuelguedon:您的计时器(如果设置为足够小的持续时间)将在 TCP 超时之前返回。然后,您可以取消 SendAsync 任务并继续。您将了解 SendAsync 在 TCP 超时之前不会返回的事实。
      • 在我看来,有一个 30 秒的 TCP 超时,并且您告诉我实现更短的超时并手动终止 SendAsync。但是我不会有结果。我的目标是设置 60、90 秒的超时时间(比当前的 TCP 更长),以便 SendAsync 回复我。也许我在你的解释中遗漏了 smthg ;)
      • 检查后我对您提到的内容有了更好的理解。与服务器的连接由 WinInet Replay 引擎管理,该引擎对服务器连接有 30 秒的超时。尽管我仍在努力寻找如何将其更改为 90 秒。我找到了一些处理程序,它们允许更详细地处理超时,但它仍然没有达到适当的水平。
      • @samuelguedon:抱歉,我以为您希望强制执行更短的超时。延长 TCP 超时时间会很困难,因为当 .NET 代码(您的或 HttpClient)在超时后会从 TCP 子系统收到错误时。因此,您可以 (1) 重试直到所需的超时时间或 (2) 使用注册表设置并重新启动以获得新的 TCP 超时时间。
      猜你喜欢
      • 2011-07-23
      • 1970-01-01
      • 1970-01-01
      • 2016-11-11
      • 2017-05-18
      • 1970-01-01
      • 1970-01-01
      • 2020-06-17
      • 2023-03-27
      相关资源
      最近更新 更多