执行 Web 请求时如何指定仅连接超时?
Posted
技术标签:
【中文标题】执行 Web 请求时如何指定仅连接超时?【英文标题】:How can I specify a connection-only timeout when executing web requests? 【发布时间】:2014-11-16 22:08:03 【问题描述】:我目前正在使用通过 HttpClient 类发出 HTTP 请求的代码。虽然您可以为请求指定超时时间,但该值适用于整个请求(包括解析主机名、建立连接、发送请求和接收响应)。
如果请求无法解析名称或建立连接,我需要一种方法使请求快速失败,但有时我还需要接收大量数据,因此不能仅仅减少超时。
有没有办法使用内置 (BCL) 类或替代 HTTP 客户端堆栈来实现此目的?
我已经简要地查看了 RestSharp 和 ServiceStack,但似乎都没有为连接部分提供超时(但如果我错了,请纠正我)。
【问题讨论】:
我不确定您是否看过以下内容,如果是,请忽略..HttpWebRequest.Timeout @DJKRAZE HttpWebRequest 的超时“适用于整个请求和响应”(来自 MSDN 页面)。 在建立连接后更改超时值是一个选项吗? 你为什么不事先执行(异步)DNS解析,这是主要的错误?我也不会尝试单独在端口上建立连接——让常规超时来解决这个问题,因为好的防火墙与慢速服务器无法区分——但可以排除 DNS 解析。 "如果他们无法解析名称或建立连接" 这有两个部分 A) DNS 解析和 B) 打开到该端口的 Socket。没有 DNS 解析通常是因为 1) DNS 服务器或其其他邻居无法解析名称或 2) DNS 服务器已关闭。第二部分是发送套接字打开请求的愿望,这是端口扫描器所做的,它查看套接字(地址/端口对)是否正在响应。它只需通过 Socket.Open() 启动会话即可。客户端要么响应,要么不响应,如果你有 DNS 解析,它所花费的时间是最短的。 【参考方案1】:如果连接时间过长,您可以使用Timer
中止请求。当时间过去时添加一个事件。你可以使用这样的东西:
static WebRequest request;
private static void sendAndReceive()
// The request with a big timeout for receiving large amout of data
request = HttpWebRequest.Create("http://localhost:8081/index/");
request.Timeout = 100000;
// The connection timeout
var ConnectionTimeoutTime = 100;
Timer timer = new Timer(ConnectionTimeoutTime);
timer.Elapsed += connectionTimeout;
timer.Enabled = true;
Console.WriteLine("Connecting...");
try
using (var stream = request.GetRequestStream())
Console.WriteLine("Connection success !");
timer.Enabled = false;
/*
* Sending data ...
*/
System.Threading.Thread.Sleep(1000000);
using (var response = (HttpWebResponse)request.GetResponse())
/*
* Receiving datas...
*/
catch (WebException e)
if(e.Status==WebExceptionStatus.RequestCanceled)
Console.WriteLine("Connection canceled (timeout)");
else if(e.Status==WebExceptionStatus.ConnectFailure)
Console.WriteLine("Can't connect to server");
else if(e.Status==WebExceptionStatus.Timeout)
Console.WriteLine("Timeout");
else
Console.WriteLine("Error");
static void connectionTimeout(object sender, System.Timers.ElapsedEventArgs e)
Console.WriteLine("Connection failed...");
Timer timer = (Timer)sender;
timer.Enabled = false;
request.Abort();
这里的时间只是举例,您必须根据自己的需要进行调整。
【讨论】:
你是第一个真正解决问题的东西。它不是很漂亮,但仍然很有用! 可以在初始化定时器的时候指定AutoReset = false
,去掉handler中的timer.Enabled = false
。此外,您可以使 Elapsed
看起来像这样:timer.Elapsed += (o, e) => try request?.Abort(); catch ;
内联可以避免全局 request
和额外的处理程序方法。【参考方案2】:
.NET 的 HttpWebRequest 公开了 2 个属性,用于指定连接远程 HTTP 服务器的超时:
Timeout - 获取或设置 GetResponse 和 GetRequestStream 方法的超时值(以毫秒为单位)。 ReadWriteTimeout - 写入或读取超时前的毫秒数。默认值为 300,000 毫秒(5 分钟)。Timeout
属性与您所追求的最接近,但它确实表明无论 Timeout 值如何,DNS 解析最多可能需要 15 秒:
域名系统 (DNS) 查询最多可能需要 15 秒才能返回或超时。如果您的请求包含需要解析的主机名,并且您将 Timeout 设置为小于 15 秒的值,则可能需要 15 秒或更长时间才会引发 WebException 以指示您的请求超时。
为 DNS 查找提前设置低于 15 秒的超时时间的一种方法是使用 lookup the hostname yourself,但许多解决方案需要 P/Invoke 来指定低级设置。
在 ServiceStack HTTP 客户端中指定超时
底层的HttpWebRequest
Timeout 和 ReadWriteTimeout 属性也可以在 ServiceStack 的高级 HTTP 客户端中指定,即在 C# Service Clients 中:
var client = new JsonServiceClient(BaseUri)
Timeout = TimeSpan.FromSeconds(30)
;
或使用 ServiceStack 的 HTTP Utils 与:
var timeoutMs = 30 * 1000;
var response = url.GetStringFromUrl(requestFilter: req =>
req.Timeout = timeoutMs);
【讨论】:
感谢您的回复。但是,除非我误解了什么,否则您所做的只是指出 HttpWebRequest 的局限性。我真正在寻找的是没有这些限制的替代品。 @MortenMertner 我建议在唯一可用的超时选项中,Timeout
是最接近您正在寻找的选项。此外,所有基于 .NET 底层 HttpWebRequest
构建的客户端也最多只能使用相同的 API,因为更精细控制的唯一解决方法是 P/Invoke 到 Win32 系统 API。
对。我已经在使用 Timeout,但对它不满意,所以仍在寻找替代的 .NET 库。我最终可以自己编写一个(使用系统 API),但这比我能挤进当前周期的工作要多。
@MortenMertner 您是否考虑过异步使用 HttpWebRequest 并实现自己的超时逻辑?【参考方案3】:
我相信 RestSharp 在 RestClient 中确实有超时属性。
var request = new RestRequest();
var client = new RestClient
Timeout = timeout, //Timeout in milliseconds to use for requests made by this client instance
ReadWriteTimeout = readWriteTimeout //The number of milliseconds before the writing or reading times out.
;
var response = client.Execute(request);
//Handle response
【讨论】:
RestSharp 在内部使用 HttpWebRequest,因此 Timeout 的行为与使用 HttpClient 的行为没有什么不同。 @MortenMertner,HttpWebRequest 有两种不同的 Timeout,一种 Timeout 用于整个连接生命周期,而 ReadWriteTimeout 用于 Stream 刷新超时。 HttpClient 没有同时指定两者是设计缺陷,但是 HttpClient 是异步的,因此您可以并且您应该实现自己的 Cancellation Token Source 以提供读/写流操作的取消(超时)。 @AkashKava 没有实现这两者并不是一个问题(参见这个 SO 问题的第二个答案:***.com/questions/1500955/…),而是两者都没有 ConnectionTimeout。我可能会使用计时器并手动中止请求(根据 Ludovic 的建议),但它有异味。【参考方案4】:没错,您无法设置此特定超时。 我没有足够的信息来说明这些库是如何构建的,但出于它们的目的,我相信它们是合适的。有人想做一个请求并为所有事情设置一个超时时间。
我建议您采取不同的方法。
你在这里尝试做两件不同的事情,HttpRequest
一次做:
-
尝试查找主机/建立连接;
传输数据;
您可以尝试将其分为两个阶段。
-
使用
Ping
类 (check this out) 尝试访问您的主机并为其设置超时;
使用HttpRequest
IF 它可以满足您的需求(超时,
这个过程不应该减慢一切,因为解析名称/路由的一部分将在第一阶段完成。这不会是一次性的。
此解决方案有一个缺点:您的远程主机必须接受 ping。 希望这会有所帮助。
【讨论】:
不要将 PING 用于网络范围的客户端或主机可用性,因为路由器通常不允许 ICMP 数据包。【参考方案5】:我用这个方法检查是否可以建立连接。但是,这并不能保证可以通过HttpWebRequest
中的后续调用建立连接。
private static bool CanConnect(string machine)
using (TcpClient client = new TcpClient())
if (!client.ConnectAsync(machine, 443).Wait(50)) // Check if we can connect in 50ms
return false;
return true;
【讨论】:
【参考方案6】:如果超时不适合您的需要 - 不要使用它们。您可以使用等待操作完成的处理程序。当您收到响应时 - 停止处理程序并继续。这样,您将在失败时收到短时间请求,并在大量数据时收到长时间请求。
可能是这样的:
var handler = new ManualResetEvent(false);
request = (HttpWebRequest)WebRequest.Create(url)
// initialize parameters such as method
request.BeginGetResponse(new AsyncCallback(delegate(IAsyncResult result)
try
var request = (HttpWebRequest)result.AsyncState;
using (var response = (HttpWebResponse)request.EndGetResponse(result))
using (var stream = response.GetResponseStream())
// success
response.Close();
catch (Exception e)
// fail operations go here
finally
handler.Set(); // whenever i succeed or fail
), request);
handler.WaitOne(); // wait for the operation to complete
【讨论】:
【参考方案7】:如果一开始只请求标题,然后如果成功,则通常的资源,
webRequest.Method = "HEAD";
【讨论】:
要了解为什么这不是问题的答案,请考虑请求已经全部是HEAD
请求的情况。将超时应用于建立连接的原始问题不会消失——它们不会“快速失败”。
我没有看到 OP 是关于所有头部请求的。解决“如果请求无法解析名称或建立连接,我需要一种方法使请求快速失败,但有时我还需要接收大量数据,因此不能仅仅减少超时”的 OP。 Head only 会让您知道您是否有 404、500 或您无法编程的东西。以上是关于执行 Web 请求时如何指定仅连接超时?的主要内容,如果未能解决你的问题,请参考以下文章