![](/img/trans.png)
[英]Set c# HttpClient request timeout regardless the host/server is available or not
[英]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,我明白了一些事情:
HttpRequestMessage
并从HttpClient
开始超时之前,将启动与服务器的 TCP 连接所有这一切都以以下代码结束:
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.