请先留意以下PHP脚本
PHP脚本A(http://127.0.0.1:8110/test.php):
1 $url = 'http://127.0.0.1:8110/demo.php'; 2 //curl请求 3 $ch = curl_init(); 4 curl_setopt($ch, CURLOPT_URL, $url); 5 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 6 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60); 7 curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 8 curl_setopt($ch, CURLOPT_POST, true); 9 curl_setopt($ch, CURLOPT_POSTFIELDS, 'a=dwadwafadwadwadwa&m=dwadwa'); 10 $result = curl_exec($ch); 11 var_dump($result); 12 exit();
PHP脚本B(127.0.0.1:8110/demo.php):
1 var_dump("PHP input:" . file_get_contents('php://input')); 2 var_dump("POST data:" . $_POST);
执行脚本A的输出结果:
string(81) "string(38) "PHP input:a=dwadwafadwadwadwa&m=dwadwa" string(15) "POST data:Array" "
咦?为何会如此呢?
其实在PHP跟其他编程语言一样,也是有它自己默认的输入流和输出流的。在Web环境下,输入流就是HTTP请求的POST的原始数据,输出流就是HTTP Body,在CLI环境下,就是当前标准的输入输出流(STDIN STDOUT)。而PHP的$_POST全局数组,则是从php://input中解析获得的。
注意1:$HTTP_RAW_POST_DATA也能获取HTTP请求的POST的原始数据,但仅在碰到为识别的MIME类型时产生
注意2:php://input和$HTTP_RAW_POST_DATA均不能在enctype="multipart/form-data"的表单提交下获得数据
接着进入正题,可以看到ThinkPHP3.1的Lib/Core/Dispatcher.class.php有如下代码片段(约在233行):
1 /** 2 * 获得实际的操作名称 3 * @access private 4 * @return string 5 */ 6 static private function getAction($var) { 7 $action = !empty($_POST[$var]) ? 8 $_POST[$var] : 9 (!empty($_GET[$var])?$_GET[$var]:C('DEFAULT_ACTION')); 10 unset($_POST[$var],$_GET[$var]);
在ThinkPHP中Lib/Core/Dispatcher.class.php充当了路由分发的功能(也可以叫前端控制器),这个类负责解析URL并根据约定和配置把后续请求交给指定的Controller和Action执行。
一旦我们在开发中使用了依赖php://input读取请求数据的约定(如一些Flash上传插件,XML-RPC,内部接口服务等等),而刚好提交的数据(可能是二进制数据)又恰好被理解成 /&.+=.+/ 这类字符串,则会导致ThinkPHP出现莫名其妙的URL分发错误(失败)或请求的数据不完整的问题。
而这都是因为ThinkPHP在分发过程中,先检查$_POST再检查$_GET,而且检查获取后又多此一举进行unset造成的。
至此,Lib/Core/Dispatcher.class.php可以修改如下:
1 /** 2 * 获得实际的操作名称 3 * @access private 4 * @return string 5 */ 6 static private function getAction($var) { 7 // $action = !empty($_POST[$var]) ? 8 // $_POST[$var] : 9 // (!empty($_GET[$var])?$_GET[$var]:C('DEFAULT_ACTION')); 10 // unset($_POST[$var],$_GET[$var]); 11 12 $action = (!empty($_GET[$var])?$_GET[$var]:C('DEFAULT_ACTION')); 13 unset($_GET[$var]);
问题则解决。
总结:该问题很难被发现,也难以定位原因,而且似乎只在ThinkPHP运行在 'URL_MODEL' => 0 模式下才发生。