【问题标题】:WebClient hangs until timeoutWebClient 挂起直到超时
【发布时间】:2019-05-21 04:45:59
【问题描述】:

我尝试使用 WebClient 下载网页,但它一直挂起,直到达到 WebClient 中的超时,然后失败并出现异常。

以下代码将不起作用

WebClient client = new WebClient();
string url = "https://www.nasdaq.com/de/symbol/aapl/dividend-history";
string page = client.DownloadString(url);

使用不同的 URL,传输工作正常。例如

WebClient client = new WebClient();
string url = "https://www.ariva.de/apple-aktie";
string page = client.DownloadString(url);

完成非常快,并且页面变量中包含整个 html。

使用 HttpClient 或 WebRequest/WebResponse 在第一个 URL 上给出相同的结果:阻塞直到超时异常。

两个 URL 都可以在浏览器中正常加载,大约需要 2-5 秒。 知道问题是什么,有什么可用的解决方案吗?

我注意到,当在 Windows 窗体对话框上使用 WebBrowser 控件时,第一个 URL 会加载 20 多个需要单击确认的 javascript 错误。当访问第一个 URL 时在浏览器中打开开发人员工具时,可以观察到相同的情况。

但是,WebClient 不会对它获得的回报采取行动。它不运行javascript,也不加载引用的图片、css或其他脚本,所以这应该不是问题。

谢谢!

拉尔夫

【问题讨论】:

  • 最好的方法是使用像wireshark或fiddler这样的嗅探器并比较工作与不工作。通常问题是 c# 代码中缺少标头。这里有一些需要检查的东西 1) 状态代码,如 200 Done 2) http 版本 1.0(流)2.2 块。 Chunk 模式需要 Next Chunk 消息,否则会超时 3) IE 兼容性。 c# 代码可能默认为错误的浏览器设置。

标签: c# winforms timeout webclient freeze


【解决方案1】:

第一个站点 "https://www.nasdaq.com/de/symbol/aapl/dividend-history"; 需要:

这里的User-agent 很重要。如果在WebRequest.UserAgent 中指定了最近的User-agent,则网站可以激活Http 2.0 协议和HSTS (HTTP Strict Transport Security)。这些仅由最近的浏览器支持/理解(作为参考,FireFox 56 或更高版本)。

使用较新的浏览器作为User-agent 是必要的,否则网站将期待(并等待)动态 响应。使用旧的User-agent,网站将激活Http 1.1 协议,从不激活HSTS。

第二个站点"https://www.ariva.de/apple-aktie"; 需要:

  • ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
  • 不需要服务器证书验证
  • 不需要特定的用户代理

我建议以这种方式设置 WebRequest(或相应的 HttpClient 设置):
(WebClient 可以工作,但可能需要派生的自定义控件)

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    Uri uri = new Uri("https://www.nasdaq.com/de/symbol/aapl/dividend-history");
    string destinationFile = "[Some Local File]";
    await HTTPDownload(uri, destinationFile);
    button1.Enabled = true;
}


CookieContainer httpCookieJar = new CookieContainer();

//The 32bit IE11 header is the User-agent used here
public async Task HTTPDownload(Uri resourceURI, string filePath)
{
    // Windows 7 may require to explicitly set the Protocol
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    // Only blindly accept the Server certificates if you know and trust the source
    ServicePointManager.ServerCertificateValidationCallback += (s, cert, ch, sec) => { return true; };
    ServicePointManager.DefaultConnectionLimit = 50;

    var httpRequest = WebRequest.CreateHttp(resourceURI);

    try
    {
        httpRequest.CookieContainer = httpCookieJar;
        httpRequest.Timeout = (int)TimeSpan.FromSeconds(15).TotalMilliseconds;
        httpRequest.AllowAutoRedirect = true;
        httpRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
        httpRequest.ServicePoint.Expect100Continue = false;
        httpRequest.UserAgent = "Mozilla / 5.0(Windows NT 6.1; WOW32; Trident / 7.0; rv: 11.0) like Gecko";
        httpRequest.Accept = "ext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
        httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate;q=0.8");
        httpRequest.Headers.Add(HttpRequestHeader.CacheControl, "no-cache");

        using (var httpResponse = (HttpWebResponse)await httpRequest.GetResponseAsync())
        using (var responseStream = httpResponse.GetResponseStream())
        {
            if (httpResponse.StatusCode == HttpStatusCode.OK) {
                try {
                    int buffersize = 132072;
                    using (var fileStream = File.Create(filePath, buffersize, FileOptions.Asynchronous)) {
                        int read;
                        byte[] buffer = new byte[buffersize];
                        while ((read = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                        {
                            await fileStream.WriteAsync(buffer, 0, read);
                        }
                    };
                }
                catch (DirectoryNotFoundException) { /* Log or throw */}
                catch (PathTooLongException) { /* Log or throw */}
                catch (IOException) { /* Log or throw */}
            }
        };
    }
    catch (WebException) { /* Log and message */} 
    catch (Exception) { /* Log and message */}
}

第一个网站 (nasdaq.com) 返回的有效载荷长度为 101.562 字节
第二个网站(www.ariva.de)返回的有效载荷长度是56.919字节

【讨论】:

  • 哇。简直哇。我还没有对此进行测试,并且几乎完全理解它(大量阅读这里使用的类),但我非常有信心这是原因和解决方案。非常感谢!
  • 我在发布之前测试了代码(它是我经常使用的编辑版本 - 与 WebRequest/WebResponse 类有关)。如果你有问题,问走。拥有它的 HttpClient 版本也很好(更灵活,而且,现代)。如果你有兴趣,我可以添加一个 PasteBin。
  • 这行得通 - 有点。超时仍然会发生,特别是对于网站的第二次和进一步联系。第一个请求永远不会超时。上面的代码在范围和使用块方面都很好,我看不出为什么在第一次调用成功后第二次调用无法完成。
  • 如果您没有在 WebRequest 上设置活动超时,则可能会发生这种情况。此外,服务器可能会要求 Cookie 来测试当前记录的 IP(与之前的请求相同)是否已经注册(检查 Cookie 到期日期)。尝试更新代码。将CookieContainer 声明为包含下载方法的类中的字段(这样Cookie 将在整个请求中被回收)并添加Timeout,如图所示。
  • 多年后提出后续问题......我很抱歉。这纯粹是一个业余爱好,没有多少时间可用,我在您的代码中使用了不同的数据源,但现在再次查看了 NASDAQ 页面。从技术角度来看,上面的代码运行良好,但是与浏览器相比,在代码中运行时返回的内容是不同的。我怀疑这是由于页面中的 javascript,它(我认为)替换了“无数据”-如果运行,此代码将用真实数据返回。 http 请求不会在响应中运行 javascript - 或者是吗?
【解决方案2】:

显然下载该链接有问题(不正确的 url,未经授权的访问,...),但是您可以使用 Async 方法来解决 socking 部分:

  WebClient client = new WebClient();
  client.DownloadStringCompleted += (s, e) =>
  {
       //here deal with downloaded file
  };
  client.DownloadStringAsync(url);

【讨论】:

    猜你喜欢
    • 2022-01-12
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 1970-01-01
    • 2014-01-04
    • 2021-12-11
    • 2012-04-02
    • 1970-01-01
    相关资源
    最近更新 更多