乐趣区

Windows下PHP服务nginx不能使用file_get_contents的原因

注意:本文为转载,原文链接:Windows 下 PHP 服务 nginx 不能使用 file_get_contents/curl/fopen 的原因!

一、问题说明
在 Windows 环境下搭建了一个本地开发服务环境,使用 Nginx 做服务,但是在使用 file_get_contents() 获取本地的链接时 http://127.0.0.1/index.php,出现了这样的错误:
file_get_contents(http://127.0.0.1/index.php) [<a href=’function.file-get-contents’>function.file-get-contents</a>]: failed to open stream: HTTP request failed!
本地电脑 php 环境为:nginx+php+mysql;于是找到这篇文章做个笔记,记录下!
这两天一直在搞 windows 下 nginx+fastcgi 的 file_get_contents 请求。我想,很多同学都遇到当 file_get_contents 请求外网的 http/https 的 php 文件时毫无压力,比如 echo file_get_contents(‘http://www.baidu.com’),它会显示百度的页面。但当你请求 localhost/127.0.0.1 本地网络的 php 服务时却一直是 timeout,无论你将请求时间和脚本运行时间多长都无法返回数据,如 file_get_contents(‘http://localhost/phpinfo.php’)。然而当你尝试请求 html 这样的静态文件时却完全没有问题。是什么原因呢?!
首先,我们知道 file_get_contents/curl/fopen 打开一个基于 tcp/ip 的 http 请求时,请求数据发送到 nginx,而 nginx 则委托给 php-cgi(fastcgi) 处理 php 文件,一般情况 fastcgi 处理完一个 php 请求后会马上释放结束信号,等待下一个处理请求(当然也有程序假死,一直占用资源的情况)。打开 nginx.conf,我们看到下面这一行:
location ~ .php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME d:/www/htdocs$fastcgi_script_name;
include fastcgi_params;
}

上面已经清楚地看到,所有使用 php 结尾的文件都经过 fastcgi 处理,而在 php.ini 的配置文件中也有一句:
cgi.force_redirect = 1
表明,所有 php 程序安全地强制转向交给 cgi 处理。
但在 windows 中,本地 127.0.0.1:9000 怎样与 php-cgi 联系的呢?!答案是增加一个 php-cgi 进程,用它来监听 127.0.0.1:9000。通过控制器命令:
RunHiddenConsole.exe D:/www/php/php-cgi.exe -b 127.0.0.1:9000 -c C:/WINDOWS/php.ini
我们就可以在启动 windows 时,开启一个 php-cgi.exe 进程监听来自 127.0.0.1:9000 的请求。在 dos 命令下打开 netstat –a 就可以看到本地计算机下的 9000 端口处于 listening 状态(也就是空置,如果没有发送任何请求的话)。
好了,该说说在 php 中使用 file_get_contents()、curl()、fopen() 函数访问 localhost 时为什么不能返回结果。我们再来试验在 index.php 中加入 file_get_contents(‘http://127.0.0.1/phpinfo.php’) 语句向 phpinfo.php 发送一个请求,这时浏览器中的状态指示一直在打转,表示它一直在工作中。打开 Dos 中的 netstat 命令,可以看到本地的 9000 端口的状态为:ESTABLISHED,表示该进程在联机处理中。实际上,这里我们已经同时向 nginx 发送了两个基于 http 的 php 请求,一个是解析 index.php,而另一个是 phpinfo.php,这样矛盾就出来了,因为我们的 windows 系统只加载了一个 http 进程,因此,它无法同时处理两个 php 请求,它只能先处理第一个请求(index.php),而 index.php 却又在等待 phpinfo.php 处理结果,phpinfo.php 没人帮它处理请求,因为它一直在等待 index.php 释放结束信号,因此,造成了程序的阻塞状态,陷入了死循环。所以我们就看到了浏览器的状态指示一直在打转。Curl() 与 fopen 函数的原因也相同。
二、解决方法
找到了原因,我们也就有了解决办法。
一是,向系统增加一个 http 请求,当一个 php-cig 内要加载另一个请求时,它能够分配其它 http 处理额外的 php 请求。这时需给另一个 http sever 分配不同的端口,比如 8080。nginx 的案例如下:
http {
server {
listen 80;
server_name 127.0.0.1;
location / {
index index.php;
root /web/www/htdocs;
}
}
server {
listen 8080;
server_name 127.0.0.1;
location / {
index index.html;
root /web/www/htdocs;
}
}
include /opt/nginx/conf/vhosts/php.conf;
}

这样,端口 80 与 8080 可以分别处理不同的程序,比如:test.php
echo file_get_contents(‘http://localhost:8080/phpinfo.php’);
当然,在 *unix 下有更多选择,比如 fork。
另外提醒下,网上有人说,通过去掉地址中的 http:// 协议标记,而使用相对地址就规避函数的检查,实际情况是不是这样呢?!当在 index.php 中使用 file_get_contents(‘phpinfo.php’); 时,我们可以看到函数输出了 phpinfo.php 的源代码,相当于 file_get_contents(‘file:c:wwwphpinfo.php’);,它实际上只是读取你的文本内容,因为 file_get_contents() 函数首先是处理 file 协议的,而 curl 则直接报错无法解析。因此这些人纯粹是不学无术的骗子。
还有人提出修改 hosts 文件,增加 localhost www.xxx.com 影射关系,函数通过 www.xxx.com 访问本地 php,这其实也是不治本的偏方,因为这只是方便计算机的 dns 解析,最终 www.xxx.com 交给 127.0.0.1,而后者交给唯一 http,还是阻塞。

退出移动版