为什么C# HttpClient无法调用这个URL(总是超时)?

Dan*_*plo 5 c# asynchronous timeout http dotnet-httpclient

我一直在开发一个确定网页信息的应用程序。其中的组成部分之一涉及向 URL 发出 HTTP GET 请求、抓取 HTML 并对其进行分析。这对于我向它抛出的每个 URL 都运行良好,除了一个......

罪魁祸首是 .NET HttpClient,它似乎总是在请求问题域内的任何 URL 时超时。然而,使用浏览器请求的相同 URL 在几毫秒内就会返回内容。标题似乎没有什么异常。

增加超时只会导致爆炸需要更长的时间。我已经尝试了几分钟,结果相同。我尝试过各种方法,例如将用户代理字符串设置为 Chrome 的字符串,但没有成功。

有问题的域是:http://careers.adidas-group.com 请注意,同一站点也在https://careers.adidas-group.com上运行在 HTTPS 上(它具有有效的证书)。使用任一协议都会导致相同的错误。

我可以使用一个简单的 C# 控制台应用程序来显示该问题,如下所示:

static void Main(string[] args)
{
    string url = "http://careers.adidas-group.com";

    var client = new HttpClient
    {
        Timeout = TimeSpan.FromSeconds(10)
    };

    using (var message = new HttpRequestMessage(HttpMethod.Get, url))
    {
        using (var httpResponse = Task.Run(() => client.SendAsync(message)).Result)
        {
            Console.WriteLine("{0}: {1}", httpResponse.StatusCode, httpResponse.ReasonPhrase);
        }
    }

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

请注意,在上面的示例中,我将超时设置为 10 秒,只是为了加快解决问题的速度 - 但是,增加超时并没有什么区别。

具有不同 URL(例如https://stackoverflow.com/)的相同代码运行良好。

另请注意,上面的代码已简化为作为控制台应用程序运行。我的实际代码在异步 MVC 控制器方法中正确异步运行(使用等待) - 我只是用来Task.Run(() => )使其与示例中的同步 Main 方法的上下文一起工作。但这对结果没有影响。(实际的异常是“任务被取消”,但这似乎是超时的症状,而不是实际的问题)。

谁能向我解释为什么会发生这种情况(与服务器配置有关吗?)以及我可以做什么(如果有的话)来使 HttpClient 满足请求?谢谢。

Dan*_*plo 10

好的,经过大量调查后,我决定必须由服务器在请求中查找特定标头。因此,我检查了大多数浏览器发送的内容,复制了这些内容,然后最终将其缩减为需要存在以下所有标头的服务器:

client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
client.DefaultRequestHeaders.Add("Accept-Language", "en-GB,en;q=0.9,en-US;q=0.8");
Run Code Online (Sandbox Code Playgroud)

删除其中任何一项,服务器都不会响应。很奇怪!

感谢所有看过这篇文章的人,我希望这个答案可以帮助将来的人:)

编辑-更奇怪

好吧,奇怪的事情现在还在继续,因为即使这解决了本地运行的问题(在 VS 2017 中使用 IIS Express),但在部署到实时环境(在 IIS 7.5 / Windows Server 中运行)时仍然无法工作。与控制台应用程序版本相同 - 适用于本地 PC,不适用于服务器。尝试了 3 台 Windows 服务器,相同的代码,它在其中一台上运行,但在另外两台上不起作用。太奇怪了。

进一步编辑 - 决议?

因此,在进一步阅读后,某些网络服务器(例如akamai Ghost(托管有问题的域))似乎具有一些相当复杂的“机器人”检测,可以拒绝来自未知客户端的连接。措施包括检查 HTTP 请求标头的顺序,以便它们与用户代理通常发送的内容相匹配(即,如果您将用户代理字符串伪造为 Chrome,您最好像Chrome那样行事,按照 chrome 的顺序发送标头并接受相同的内容类型等)。

在尝试伪造大量浏览器用户代理字符串后,我最终发现“假装”为 Google PageSpeed 机器人是有效的。将用户代理字符串设置为:“ Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; Google Page Speed Insights) Chrome/27.0.1453 Safari/537.36

无论使用什么版本的 Windows 服务器或 .NET Framework,这似乎都有效。

我最终想出的标题是:

this.Client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/apng,*/*;q=0.8");
this.Client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
this.Client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
this.Client.DefaultRequestHeaders.Add("Accept-Language", "en-GB,en;q=0.9,en-US;q=0.8");
this.Client.DefaultRequestHeaders.Add("Connection", "keep-alive");
this.Client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
this.Client.DefaultRequestHeaders.Add("Pragma", "no-cache");
this.Client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; Google Page Speed Insights) Chrome/27.0.1453 Safari/537.36");
Run Code Online (Sandbox Code Playgroud)

  • 我虽然,我在这里经历了和你一样的事情,我快疯了。惊人的修复,谢谢! (2认同)

Ken*_*lac 5

您得出的答案是正确的。不过,为了将来的通知,我建议使用 Web 调试器,例如 Charles 或 Fiddler。它可以更轻松地复制您的请求,并最终找到您没有收到主机任何响应的根源。在这个例子中我使用了查尔斯。

网络请求信息

从我的 Visual Studio 调试器中我可以看到客户端“DefaultHeaders”都是空的。现在OP已经演示了我们需要做的就是将标头添加到我们的客户端并希望它满足主机的要求。

static void Main(string[] args)
{
    string url = "http://careers.adidas-group.com";

    var client = new HttpClient
    {
        Timeout = TimeSpan.FromSeconds(10)
    };

    client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
    client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.5");
    client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0");
    client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");

    using (var message = new HttpRequestMessage(HttpMethod.Get, url))
    {

        using (var httpResponse = Task.Run(() => client.SendAsync(message)).Result)
        {
            Console.WriteLine("{0}: {1}", httpResponse.StatusCode, httpResponse.ReasonPhrase);
        }
    }

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

我只是费心添加那些我知道对大多数主机来说必不可少的内容。测试上面的代码,我们得到一个代码“OK:OK”。如果我们尝试删除其中任何一行:

client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.5");
client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
Run Code Online (Sandbox Code Playgroud)

我们将再次陷入无限循环。这意味着主机不关心您正在使用什么用户代理。阿迪达斯的robots.txt ( https://careers.adidas-group.com/robots.txt )也表明了这一点- 这表明(因为数据挖掘器是不使用浏览器的自动化服务) - 阿迪达斯不介意在他们的领域周围有几个蜘蛛/数据挖掘者。