深入剖析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&para2=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 略胜一筹,还得根据具体场景去选择使用。

标签: php, http, post, input

29
May 2016
AUTHOR WiFeng
CATEGORY Web
COMMENTS No Comments

添加新评论 »

   点击刷新验证码