我一直试图从我在服务器上创建的PHP页面访问这个特定的REST服务。我把问题缩小到这两行。我的PHP页面是这样的:
<?php
$response = file_get_contents("https://maps.co.weber.ut.us/arcgis/rest/services/SDE_composite_locator/GeocodeServer/findAddressCandidates?Street=&SingleLine=3042+N+1050+W&outFields=*&outSR=102100&searchExtent=&f=json");
echo $response; ?>
页面在第2行结束,出现以下错误:
Warning: file_get_contents(): SSL operation failed with code 1.
OpenSSL Error messages: error:14090086:SSL
routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed in
...php on line 2
Warning: file_get_contents(): Failed to enable crypto in ...php on
line 2
Warning:
file_get_contents(https://maps.co.weber.ut.us/arcgis/rest/services/SDE_composite_locator/GeocodeServer/findAddressCandidates?Street=&SingleLine=3042+N+1050+W&outFields=*&outSR=102100&searchExtent=&f=json):
failed to open stream: operation failed in ...php on line 2
我们使用的是Gentoo服务器。我们最近升级到了PHP 5.6版本。这个问题是在升级之后出现的。
当我用https://www.google.com这样的地址替换REST服务时,我发现;我的页面工作得很好。
在前面的尝试中,我设置“verify_peer”=>false,并将其作为参数传递给file_get_contents,如这里所述:file_get_contents忽略verify_peer=>false?但正如作者所指出的;这没什么区别。
我问过我们的一个服务器管理员,php.ini文件中是否存在这些行:
扩展= php_openssl.dll
allow_url_fopen = On
他告诉我,因为我们在Gentoo上,所以openssl是在我们构建的时候编译的;在php。ini文件中没有设置。
我还确认allow_url_fopen正在工作。由于这个问题的专门性;我找不到很多有用的信息。你们有人遇到过这样的东西吗?谢谢。
出现此错误的原因是PHP没有受信任的证书颁发机构列表。
PHP 5.6及以上版本尝试自动加载受系统信任的ca。问题是可以解决的。更多信息请参见http://php.net/manual/en/migration56.openssl.php。
PHP 5.5及更早的版本确实很难正确设置,因为您必须在每个请求上下文中手动指定CA包,这是您不希望在代码中出现的事情。
所以我决定在PHP版本< 5.6的代码中禁用SSL验证:
$req = new HTTP_Request2($url);
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
//correct ssl validation on php 5.5 is a pain, so disable
$req->setConfig('ssl_verify_host', false);
$req->setConfig('ssl_verify_peer', false);
}
这是一个非常有用的链接:
http://php.net/manual/en/migration56.openssl.php
一份官方文档,描述了在PHP 5.6中对open ssl所做的更改
从这里我了解到我应该将另一个参数设置为false:“verify_peer_name”=>false
注意:这具有非常重要的安全影响。禁用验证可能会允许MITM攻击者使用无效的证书来窃听请求。虽然在本地开发中这样做可能有用,但在生产中应该使用其他方法。
所以我的工作代码是这样的:
<?php
$arrContextOptions=array(
"ssl"=>array(
"verify_peer"=>false,
"verify_peer_name"=>false,
),
);
$response = file_get_contents("https://maps.co.weber.ut.us/arcgis/rest/services/SDE_composite_locator/GeocodeServer/findAddressCandidates?Street=&SingleLine=3042+N+1050+W&outFields=*&outSR=102100&searchExtent=&f=json", false, stream_context_create($arrContextOptions));
echo $response; ?>
你可以通过编写一个使用curl的定制函数来解决这个问题,如下所示:
function file_get_contents_curl( $url ) {
$ch = curl_init();
curl_setopt( $ch, CURLOPT_AUTOREFERER, TRUE );
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, TRUE );
$data = curl_exec( $ch );
curl_close( $ch );
return $data;
}
然后,只要在调用以https开头的url时使用file_get_contents_curl而不是file_get_contents。