繁体   English   中英

cURL 使用 HttpClient 请求 - 如何设置服务器连接超时 (WinInet)

[英]cURL request with HttpClient - how to set the timeout on the server connection (WinInet)

我通过这里描述的方法使用 HttpClient 发送 cURL 请求。

此方法使用的参数是:

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

Parameters.WcTimeout = 超时时间

url, header, content = the cURL request (based on this tool to convert to 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 秒后。


我根据以下链接尝试了一些不同的处理程序HttpClient.Timeout 和使用 WebRequestHandler 超时属性有什么区别? , HttpClient Timeout 混淆或与 WinHttpHandler 混淆。

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

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

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

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

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

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

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

所以感谢@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应该通过 static 在 App 级别定义,而不是在using语句中。 在此处此处阅读有关此 go 的更多信息。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM