我正在使用HttpClient通过下面描述的方法发送cURL请求。
用于此方法的参数为:
SelectedProxy = 一个自定义类,用于存储我的代理参数
参数.Wc超时 = 超时
url、标头、内容 = 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 测试的代理发送请求时,我的尝试 {} 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
通过使用秒表,我看到超时发生在大约 30 秒后。
我根据以下链接尝试了几个不同的处理程序 HttpClient.Timeout 和使用 WebRequestHandler 超时属性有什么区别?、HttpClient 超时混淆或 WinHttpHandler。
值得注意的是,WinHttpHandler 允许不同的错误代码,即错误 12002 调用WINHTTP_CALLBACK_STATUS_REQUEST_ERROR"操作超时"。根本原因是相同的,尽管它有助于定位错误的位置(即WinInet),这也证实了@DavidWright所说的关于HttpClient超时管理请求发送的不同部分的说法。
因此,我的问题来自与服务器建立连接所需的时间,这会触发 WinNet 的 30 秒超时。
那么我的问题是如何更改这些超时?
附带说明一下,值得注意的是,使用 WinInet 的 Chrome 似乎没有受到这种超时的影响,也没有 Cefsharp 我的应用程序的很大一部分所基于的,并且相同的代理可以通过它正确发送请求。
多亏了@DavidWright我明白了几件事:
- 在此之前,发送
HttpRequestMessage
并启动HttpClient
超时之前,将启动与服务器的 TCP 连接 - TCP 连接有自己的超时,在操作系统级别定义,我们没有确定在运行时从 C# 更改它的方法(如果有人想贡献,问题待定)
- 坚持尝试连接是有效的,因为每次尝试都受益于以前的尝试,尽管需要实现适当的异常管理和手动超时计数器(我实际上在我的代码中考虑了许多尝试,假设每次尝试都在 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
是一次性的,但应该通过静态而不是在using
语句中在应用级别定义。 要了解有关此内容的更多信息,请转到此处或那里。
我的问题是我想在每个请求时续订代理,并且它不是基于每个请求设置的。虽然它解释了新的 ConnectionLeaseTimeout 参数的重新定义(以最小化租约保持打开的时间),但这是一个不同的主题
我在使用HttpClient时遇到了同样的问题。SendAsync 需要做两件事才能返回:首先,设置发生通信的 TCP 通道(SYN、SYN/ACK、ACK 握手,如果您熟悉的话),其次通过该 TCP 通道获取构成 HTTP 响应的数据。HttpClient 的超时仅适用于第二部分。第一部分的超时由操作系统的网络子系统控制,在 .NET 代码中更改该超时非常困难。
(以下是重现此效果的方法。在两台计算机之间设置有效的客户端/服务器连接,以便您知道名称解析、端口访问、侦听以及客户端和服务器逻辑都有效。然后拔下服务器上的网络电缆并重新运行客户端请求。它将随着操作系统的默认网络超时而超时,无论您在 HttpClient 上设置了什么超时。
我知道解决这个问题的唯一方法是在不同的线程上启动您自己的延迟计时器,如果计时器首先完成,则取消 SendAsync 任务。您可以使用 Task.Delay 和 Task.WaitAny 或通过创建具有所需 timeone 的 CancelTokenSource 来执行此操作(这基本上只是在引擎盖下执行第一种方式)。无论哪种情况,您都需要小心取消和读取输掉比赛的任务的异常。