为什么php://输入可以多次读取,尽管文档说不然?

dec*_*eze 18 php post inputstream

PHP文档的状态php://input只能读一次.

在我的应用程序中,我需要读取它两次,一次用于身份验证,一次用于实际处理内容,两个函数都由不同的独立模块处理.疯狂的是:它有效.

我可以指望在任何地方工作,或者这是我的PHP版本(5.2.10)中的侥幸?我能找到的关于这个的唯一文档就是说它不应该工作,没有提到版本限制.


在丹尼斯的预感之后,我做了这个测试:

$in = fopen('php://input', 'r');
echo fread($in, 1024) . "\n";
fseek($in, 0);
echo fread($in, 1024) . "\n";
fclose($in);
echo file_get_contents('php://input') . "\n";
Run Code Online (Sandbox Code Playgroud)

冰壶:

$ curl http://localhost:8888/tests/test.php -d "This is a test"
This is a test

This is a test
Run Code Online (Sandbox Code Playgroud)

显然它只限于每个打开手柄一次读取.


更多的挖掘表明php://input,对于PUT请求确实只能读取一次.上面的示例使用了POST请求.

Art*_*cto 23

对源代码进行一点检查即可得出答案.

首先,是的,由于底层流没有实现seek处理程序,因此每个句柄限制为一次读取:

php_stream_ops php_stream_input_ops = {
    php_stream_input_write,
    /* ... */
    "Input",
    NULL, /* seek */
    /* ... */
};
Run Code Online (Sandbox Code Playgroud)

其次,读取处理程序有两种不同的行为,具体取决于是否已读取并存储"POST数据" SG(request_info).raw_post_data.

if (SG(request_info).raw_post_data) {
    read_bytes = SG(request_info).raw_post_data_length - *position;
    /* ...*/
    if (read_bytes) {
        memcpy(buf, SG(request_info).raw_post_data + *position, read_bytes);
    }
} else if (sapi_module.read_post) {
    read_bytes = sapi_module.read_post(buf, count TSRMLS_CC);
    /* ... */
} else {
    stream->eof = 1;
}
Run Code Online (Sandbox Code Playgroud)

所以我们有三种可能性:

  1. 请求正文数据已被读取并存储SG(request_info).raw_post_data.在这种情况下,由于存储了数据,我们可以打开和读取多个句柄php://input.
  2. 已读取请求正文数据,但其内容未存储在任何位置.php://input不能给我们任何东西.
  3. 请求数据尚未读取.这意味着我们只能打开php://input并阅读一次.

注意:以下是默认行为.不同的SAPI或其他扩展可能会更改此行为.

在POST请求的情况下,PHP根据内容类型定义不同的POST阅读器和POST处理程序.

案例1.当我们有POST请求时会发生这种情况:

  • 内容类型application/x-www-form-encoded.sapi_activate检测具有内容类型和调用的POST请求sapi_read_post_data.这将检测内容类型并定义POST阅读器/处理程序对.POST阅读器sapi_read_standard_form_data,立即被调用,只是将请求体复制到SG(request_info).post_data.默认后读者php_default_post_reader然后被调用,它充满$HTTP_RAW_POST_DATA如果INI设置always_populate_post_data设置,然后拷贝SG(request_info).post_dataSG(request_info).raw_post_data并清除第一.对处理程序的调用在这里并不重要,并且推迟到构建超级全局(这可能不会发生,以防JIT被激活并且不使用超级全局).
  • 使用无法识别或不存在的内容类型.在这种情况下,没有定义的POST阅读器和处理程序.这两种情况最终都php_default_post_reader没有任何数据读取.由于这是一个POST请求,并且没有读取器/处理程序对,因此sapi_read_standard_form_data将被调用.这与内容类型的读处理程序功能相同application/x-www-form-encoded,因此吞下了所有数据SG(request_info).post_data.与现在唯一不同的是,$HTTP_RAW_POST_DATA总是填充(无论价值always_populate_post_data),并且没有用于构建超全局的处理程序.

案例2.当我们有一个内容类型为"multipart/form-data"的表单请求时,会发生这种情况.POST阅读器是NULL,所以处理程序,它rfc1867_post_handler充当混合reader/handler.在sapi_activate阶段中没有任何数据被读取.该函数sapi_handle_post最终在稍后阶段调用,该阶段又调用POST处理程序.rfc1867_post_handler读取请求数据,填充POSTFILES,但不留下任何内容SG(request_info).raw_post_data.

案例3.最后一种情况发生在与POST不同的请求(例如PUT).php_default_post_reader被直接调用.由于请求不是POST请求,因此吞下数据sapi_read_standard_form_data.由于没有读取数据,因此没有任何事情要做.