2010-06-24 14 views
15

PHP文档statesphp://input只能读取一次。尽管文档中有其他说明,但为什么可以多次读取php://输入?

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

我可以指望这个工作无处不在,还是这是我的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请求。

+3

...现在,4.5年后,PHP 5.6正式版[支持](http://docs.php.net/manual/en/migration56.new-features.php)从php://输入中读取'不止一次,甚至寻求操作:) – 2015-01-12 22:22:41

+1

对于我们这些还没有在PHP 5.6中,将'file_get_contents('php:// input')'封装在缓存结果并调用它的函数中,而不是一个可行的工作-周围。 – Umbrella 2015-06-08 17:55:05

回答

21

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

首先,是的,你只限于每处理一个读,因为底层的流没有实现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并只读一次。

注意:以下是默认行为。不同的SAPI或附加扩展可能会改变这种行为。

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

案例1.这种情况发生时,我们有一个POST请求:

  • 随着内容类型application/x-www-form-encodedsapi_activate检测到带有内容类型的POST请求并调用sapi_read_post_data。这将检测内容类型并定义POST读取器/处理器对。 POST阅读器是sapi_read_standard_form_data,立即调用它并将请求正文复制到SG(request_info).post_data。然后调用默认后置读取器php_default_post_reader,如果设置了ini设置always_populate_post_data,则会填充$HTTP_RAW_POST_DATA,然后将SG(request_info).post_data复制到SG(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发生这种情况时,我们有与内容类型“多部分/格式数据”形式的请求。 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吞噬。由于没有数据被读取,所以没有任何事情要做。

+0

精细的回答!但是,这种行为是否有很好的理由,还是仅仅是一种疏忽?即它是按“意图”还是“按照代码”工作? :) – deceze 2010-08-04 03:36:22

+1

@deceze执行看起来很合理。 application/x-www-form-encoded数据通常很短,所以将其保存在内存中的内存损失很小。 multipart/form-data通常要大得多(例如包含文件),那么拥有它的内存会更加繁重。对于其他POST请求,我认为将所有内容保存在内存中并不是一个好主意,但它是向后兼容所必需的(旧脚本只有$ HTTP_RAW_POST_DATA)。至于其他的请求方法,实现也似乎是合理的 - 允许用户以流方式读取数据,从而节省内存。 – Artefacto 2010-08-04 03:49:14

+0

我想这是有道理的。谢谢! – deceze 2010-08-04 03:56:41

3

也许它们表示fseek()或rewind()不可用。 您是否尝试过在打开的php://输入中使用这些函数之一?

+1

看来这可能是它。良好的预感。查看更新的问题。 – deceze 2010-06-25 01:10:53

+0

也许我忽略了一些东西,但是......新的问题是什么? – 2010-06-30 14:22:52

相关问题