深入剖析PHP输入流 php://input
记得当初在刚刚参加工作时,看到其中一个项目中在接受客户端(iphone app)数据时使用到 php://input ,觉得很好奇为什么要这么做?问了相关同事说是只有这样才能获取到客户端提交过来的数据,于是我信了,没有继续深究下去。
但是在最近我又想到这个 php://input ,打开官方手册这里写的比较准确,如下:
php://input is a read-only stream that allows you to read raw data from the request body. In the case of POST requests, it is preferable to use php://input instead of $HTTP_RAW_POST_DATA as it does not depend on special php.ini directives. Moreover, for those cases where $HTTP_RAW_POST_DATA is not populated by default, it is a potentially less memory intensive alternative to activating always_populate_raw_post_data. php://input is not available with enctype="multipart/form-data".
php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。 而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。 enctype="multipart/form-data" 的时候 php://input 是无效的。
概况为3要素:
1. 读取原始POST数据
2. 不能用于 mulipart/form-data
3. 对比 $HTTP_RAW_POST_DATA 更好用,性能也更好
我们依次来验证一下前两点,第三点就不去验证了,这个变量使用概率太小了。
第一个示例:
server_test.php
<?php $str = file_get_contents('php://input', 'r'); echo "\n========POST===========\n"; print_R($_POST); echo "\n======php://input======\n"; echo $str . "\n\n"; ?>
client_test.php
<?php // $data = array('aa' => 111, 'bbb' => 22222); // $data = "xxxx http://baidu.com/ pouetpo http%3a%2f%2fbaidu.com%2f uet"; $data = "a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2f"; // $data = 'aaabbbcccccddddd'; // $data = json_encode($data); $httpHeader = array( // "Content-type:application/x-www-form-urlencoded", // "Content-type:multipart/form-data;", ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://log.bbs.le.com/test.php?kkk=2222'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader); $ret = curl_exec($ch); echo $ret; ?>
输出结果是:
========POST=========== Array ( [a] => 1 [b] => 2 [c] => 3 [d] => http://baidu.com/ ) ======php://input====== a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2f
可见,php://input 只读取到个POST数据,并不包含GET数据,并且也是未经过PHP 自动 url_decode 处理的原始数据。
第二个示例:
我们主动指定请求头 content-type
server_test.php 还是用上面的不变
client_test.php 这里稍作修改
<?php // $data = array('aa' => 111, 'bbb' => 22222); // $data = "xxxx http://baidu.com/ pouetpo http%3a%2f%2fbaidu.com%2f uet"; $data = "a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2f"; // $data = 'aaabbbcccccddddd'; // $data = json_encode($data); $httpHeader = array( // "Content-type:application/x-www-form-urlencoded", "Content-type:multipart/form-data", ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://log.bbs.le.com/test.php?kkk=2222'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader); $ret = curl_exec($ch); echo $ret; ?>
输出结果是:
========POST=========== Array ( ) ======php://input====== a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2f
那么问题来了,为什么我们构造了 multipart/form-data 请求,php://input 也能够获取到数据呢?因为这不是真正的 form-data。
下面接着使用CURL指定的构造 forum-data 的方式做个测试,如下:
client_test.php 这里稍作改动
<?php $data = array('aa' => 111, 'bbb' => 22222); // $data = "xxxx http://baidu.com/ pouetpo http%3a%2f%2fbaidu.com%2f uet"; // $data = "a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2f"; // $data = 'aaabbbcccccddddd'; // $data = json_encode($data); $httpHeader = array( // "Content-type:application/x-www-form-urlencoded", // "Content-type:multipart/form-data", ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://log.bbs.le.com/test.php?kkk=2222'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader); $ret = curl_exec($ch); echo $ret; ?>
输出结果是:
========POST=========== Array ( [aa] => 111 [bbb] => 22222 ) ======php://input======
这次出现的结果与PHP手册中给出的定义是一致的,因为当 CURL POSTFIELS 为 Array 时,CURL会把请求自动置为 mutipart/form-data,这个可以通过在 server_test.php 中输出 $_SERVER 变量来对比差异。
顺便附上 CURL 的这个潜规则定义:
CURLOPT_POSTFIELDS The full data to post in a HTTP "POST" operation. To post a file, prepend a filename with @ and use the full path. This can either be passed as a urlencoded string like 'para1=val1¶2=val2&...' or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data.
换句话说就是,如果你要使用 php://input 获取数据,那么就不用使用 CURL POSTFIELDS 为 Array ,不然你死活取不到数据。
再接着看一个示例:
当请求的data不是一个query 格式,而是一个json字符串时,他们哥俩的差别又如何呢?
client_test.php
<?php $data = array('aa' => 111, 'bbb' => 22222); // $data = "xxxx http://baidu.com/ pouetpo http%3a%2f%2fbaidu.com%2f uet"; // $data = "a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2f"; // $data = 'aaabbbcccccddddd'; $data = json_encode($data); $httpHeader = array( // "Content-type:application/x-www-form-urlencoded", // "Content-type:multipart/form-data", ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://log.bbs.le.com/test.php?kkk=2222'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader); $ret = curl_exec($ch); echo $ret; ?>
输出结果是:
========POST=========== Array ( ) ======php://input====== {"aa":111,"bbb":22222}
这个时候,php://input 获取到了数据,而POST并没有被填充。 另外当 Content-Type 不为 application/x-www-form-urlencoded 与 multipart/form-data 时,POST总是获取不到值,而 php://input 却可以获取到数据,这个场景体现在 xml rpc 调用中,这里就不在举类了。
在这个地方有人会提出这样的想法,如果php://input是原生的POST数据,不进行 urldecode 操作,使用它性能是不是更好呢?我们在这里做一下测试,不过我认为这个POST数据一定要大才有可能体现出区别
server_test.php
<?php $time_start = microtime(true); $str = file_get_contents('php://input', 'r'); $arr = $_POST; $time_end = microtime(true); $time = $time_end - $time_start; $mem = memory_get_usage(); echo "time: $time, mem: $mem \n"; ?>
client_test.php
<?php $data = "a=1&b=2&c=3"; // $data = "a=1&b=2&c=3&d=http%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2fhttp%3a%2f%2fbaidu.com%2f"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://log.bbs.le.com/test.php?kkk=2222'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $ret = curl_exec($ch); echo $ret; ?>
server 端代码相同的情况下,client 里设定的2个不同大小的 data 运行结果分别是:
time: 2.3841857910156E-5, mem: 224432
time: 2.6226043701172E-5, mem: 231800
相同的 client端代码的情况下,分别调用 php://input 与 $_POST 运行结果分别是:
time: 2.4795532226562E-5, mem: 231712
time: 2.8610229492188E-6, mem: 228536
要注意到,上面的结果是 $_POST 要优于 php://input
相同的 client端代码里设定 content-type 为 text/html ,使其 $_POST 不起作用,分别调用 php://input 与 $_POST 运行结果分别是:
time: 2.6941299438477E-5, mem: 233264
time: 4.0531158447266E-6, mem: 230200
这个结果也是 $_POST 要优于 php://input。但是其中text/html 的设定,运行时间有些增加。
综合上面的3个测试,无法说明 php://input 优于 $_POST。
总结,php://input 可以获取到多重格式的http entiy body 数据,包括 json html xml 等,而 $_POST 只能获取到 http_query_string 格式或 multitype/form-data 格式数据。但是$_POST在处理日常的 form 表单提交数据时更加直接,可以直接指定 key 获取到其对应 value ,所以我们常常在程序中看到特别多的$_POST。运行效率方面,$_POST 略胜一筹,还得根据具体场景去选择使用。