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

时间:2010-06-24 06:18:47

标签: php post inputstream

php://input只能读取一次的states文档。

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

我可以指望在任何地方工作,或者这是我的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";

卷曲:

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

This is a test

显然,每次打开手柄只读一次


稍微挖掘一下,确实php://input确实只能读取一次,只能读取PUT请求。上面的示例使用了POST请求。

2 个答案:

答案 0 :(得分:23)

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

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

php_stream_ops php_stream_input_ops = {
    php_stream_input_write,
    /* ... */
    "Input",
    NULL, /* seek */
    /* ... */
};

其次,读取处理程序有两种不同的行为,具体取决于“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;
}

所以我们在这里有三种可能性:

  1. 请求正文数据已被读取并存储在SG(request_info).raw_post_data中。在这种情况下,由于存储了数据,我们可以打开和读取php://input的多个句柄。
  2. 已读取请求正文数据,但其内容未存储在任何位置。 php://input不能给我们任何东西。
  3. 尚未读取请求数据。这意味着我们可以打开php://input并只阅读一次。
  4. 注意:以下是默认行为。不同的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,如果设置了ini设置$HTTP_RAW_POST_DATA,则填充always_populate_post_data,然后将SG(request_info).post_data复制到SG(request_info).raw_post_data并清除第一个。{1}}。对处理程序的调用在这里并不重要,并且推迟到构建超级全局(这可能不会发生,以防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会吞下数据。由于没有读取数据,因此没有任何事情要做。

答案 1 :(得分:3)

也许他们的意思是fseek()或rewind()不可用。 您是否在打开的php:// input?

上尝试了其中一个函数