使用 Guzzle 但不是通过浏览器或命令行 cURL 或 wget 时出现 406

406 when using Guzzle but not through browser, or command line cURL or wget

我们有一个 php 网络应用程序使用 Guzzle 5 下载 Wordpress RSS 提要。

除此 Feed 外一切正常 https://www.socialquant.net/blog/feed/

此站点的所有者确实希望我们拉取 Feed,并且并非故意试图阻止访问。

我可以使用 wgetcurl 从本地计算机和生产 Web 服务器(我们最初注意到问题的地方)成功下载文件,无需特殊选项。

以前发生过一次,当时我们认为问题是由 Apache 上的 mod_security 引起的,通过添加任意 User-Agent header 解决了这个问题。但是那一次我能够在命令行上一致地重现问题,这次它只是通过 Guzzle/PHP

失败了

我已将浏览器请求的响应 header 复制到问题供稿,以及另一个有效的供稿。我划掉了那些相同的,剩下下面的

Server:Apache/2.2.22
Vary:User-Agent
X-Powered-By:PHP/5.3.29
Content-Encoding:gzip

Server:Apache
Vary:Accept-Encoding
X-Powered-By:PHP/5.5.30

这并没有提供太多见解。 gzip 内容编码跳出,我试图找到另一个使用 gzip 的工作提要来验证这一点,但这无关紧要,因为 Guzzle 的默认模式是自动处理编码。我们使用相同的设置从使用 gzip 的 CDN 下载图像。

有人有什么想法吗?谢谢:)

编辑

使用 Guzzle 5.3.0

代码:

$client = new \GuzzleHttp\Client();

try {
    $res = $client->get( $feed, [
      'headers' => ['User-Agent' => 'Mozilla/4.0']
    ] );
} catch (\Exception $e) {

}

恐怕我没有合适的解决你的问题的方法,但我有它再次工作。

tl;dr 版本

这是 User-Agent header,将其更改为几乎任何其他内容都有效。

wget 调用失败:

wget -d --header="User-Agent: Mozilla/4.0"  https://www.socialquant.net/blog/feed/ 

但这行得通

wget -d --header="User-Agent: SomeRandomText" https://www.socialquant.net/blog/feed/

因此,下面的 PHP 现在也可以使用了:

require 'vendor/autoload.php';

$client = new \GuzzleHttp\Client();
$feed = 'https://www.socialquant.net/blog/feed/';

try {

    $res = $client->get( 
        $feed, 
        [
            'headers' => [
                'User-Agent' => 'SomeRandomText',
            ]
        ]
    );
    echo $res->getBody();
} catch (\Exception $e) {
    echo 'Exception: ' . $e->getMessage();
} 

我的想法

正如您所指出的,我从 wgetcurl 开始,这在没有设置特殊的 header 或选项时有效。在我的浏览器中打开它也有效。我也尝试使用 Guzzle 而不设置 User-Agent 并且这也有效。

一旦我将 User-Agent 设置为 Mozilla/4.0 甚至 Mozilla/5.0 它开始失败 406 Not Acceptable

根据HTTP Status Code definitions,406表示

The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.

理论上,添加 AcceptAccept-Encoding header 应该可以解决问题,但事实并非如此。不是通过 Guzzle 或 wget.

然后我发现 Mozilla Developer Network definition 上面写着:

This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent.

这有点指向 User-Agent。这让我相信你确实是正确的 mod_security 正在做一些奇怪的事情。我确信客户端服务器上 mod_security 或 Apache 的更新添加了一条规则,以特定方式解析 Mozilla/* 用户代理,因为发送 User-Agent: Mozilla/4.0 () 也有效。

这就是为什么我说我没有适合您的解决方案。即使客户希望您拉取提要,他们(或他们的主机)仍然控制着规则。

注意: 我注意到我的 IP 在多次 406 尝试失败后被列入黑名单,之后我不得不等待一个小时才能再次访问该站点。很可能是 mod_security 规则。 mod_security 可能 甚至会接收到您的用户代理的自动请求并开始阻止它或使用 406 拒绝它。

我也没有适合您的解决方案,因为我也遇到了同样的问题(除了我收到错误 503 并且有 60% 的时间失败)。如果您找到解决方案,请告诉我。

但是,我想与您分享我最近的研究发现。我发现某些 User-Agents 对我来说比其他的效果更好。这让我相信情况并非多诺万所说的那样(至少对我而言)。

当我将 User-Agent 设置为 null 时,它在 100% 的时间内都有效。但是,我还没有提出任何大的请求,因为我害怕被禁止 IP,正如我知道的那样,我会提出一个大的请求。

当我对请求本身执行 var_dump 时,我看到很多包含 Guzzle 标记的数组。我在想,也许 Amazon 的检测服务可以判断出我在欺骗 headers?我不知道。

希望你明白了。