为什么php://input会被多次阅读,尽管文档另有说明? [英] Why can php://input be read more than once despite the documentation saying otherwise?

查看:202
本文介绍了为什么php://input会被多次阅读,尽管文档另有说明?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

PHP文档声明 php://input只能读取一次.

The PHP documentation states that php://input can only be read once.

在我的应用程序中,我需要阅读两次,一次用于身份验证,一次用于实际处理内容,并且两个功能均由不同的独立模块处理.疯狂的事情是:有效.

In my application I need to read it twice, once for authentication purposes and once for actually processing the content, and both functions are handled by different, independent modules. The crazy thing is: it works.

我可以指望它在任何地方都能正常工作吗,或者这在我的PHP版本(5.2.10)中是fl幸的?我能找到的唯一有关此文件的文档指出该文件不起作用,没有提到版本限制.

Can I count on this working everywhere, or is this a fluke in my version of PHP (5.2.10)? The only documentation I can find about this is the one that states that it shouldn't work, with no version limitation mentioned.

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

Following Dennis' hunch, I did this test:

$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

显然,它仅限于每个打开的句柄阅读一次.

Apparently it's limited to one read per open handle.

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

A little more digging revealed that indeed php://input can only be read once, ever, for PUT requests. The above example used a POST request.

推荐答案

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

A little inspection of the source code yields the answers.

首先,是的,由于基础流未实现seek处理程序,因此每个句柄只能读取一次:

First, yes, you're limited to one read per handle because the underlying stream does not implement the seek handler:

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

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

Second, the read handler has two different behaviors depending on whether the "POST data" has been read and stored in 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;
}

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

So we have three possibilities here:

  1. 请求主体数据已被读取并存储在SG(request_info).raw_post_data中.在这种情况下,由于已存储数据,因此我们可以打开和读取php://input的多个句柄.
  2. 已读取请求正文数据,但其内容未存储在任何地方. php://input无法给我们任何东西.
  3. 尚未读取请求数据.这意味着我们可以打开php://input并只读取一次.
  1. The request body data has already been read and stored in SG(request_info).raw_post_data. In this case, since the data is stored, we can open and read multiple handles for php://input.
  2. The request body data has been read, but its contents were not stored anywhere. php://input cannot give us anything.
  3. The request data hasn't been read yet. This means we can open php://input and read it only once.

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

对于POST请求,PHP会根据内容类型定义不同的POST阅读器和POST处理程序.

In case of POST requests, PHP defines a different POST reader and a POST handler depending on the content-type.

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

Case 1. This happens when we have a POST request:

  • 具有内容类型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设置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的值如何),并且没有用于构建超全局变量的处理程序.
  • With content-type application/x-www-form-encoded. sapi_activate detects a POST request with a content-type and calls sapi_read_post_data. This detects the content-type and defines the POST reader/handler pair. The POST reader is sapi_read_standard_form_data, which is immediately called and just copies the request body to SG(request_info).post_data. The default post reader php_default_post_reader is then called, which fills $HTTP_RAW_POST_DATA if the ini setting always_populate_post_data is set and then copies SG(request_info).post_data to SG(request_info).raw_post_data and clears the first. The call to the handler doesn't matter here and is deferred until the superglobals are built (which may not happen, in case JIT is activated and the superglobals are not used).
  • With an unrecognized or inexistent content-type. In this case, there's no defined POST reader and handler. Both cases end up in php_default_post_reader without any data read. Since this is a POST request and there's no reader/handler pair, sapi_read_standard_form_data will be called. This is the same function as the read handler the content type application/x-www-form-encoded, so all the data gets swallowed to SG(request_info).post_data. The only differences from now on is that $HTTP_RAW_POST_DATA is always populated (no matter the value of always_populate_post_data) and there's no handler for building the superglobals.

情况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中保留任何内容.

Case 2. This happens when we have a form request with content-type "multipart/form-data". The POST reader is NULL, so the handler, which is rfc1867_post_handler acts as a mixed reader/handler. No data whatsoever is read in the sapi_activate phase. The function sapi_handle_post is eventually called in a later phase, which, in its turn calls the POST handler. rfc1867_post_handler reads the request data, populates POST and FILES, but leaves nothing in SG(request_info).raw_post_data.

第3种情况.最后一种情况发生于不同于POST的请求(例如PUT).直接调用php_default_post_reader.因为该请求不是POST请求,所以数据被sapi_read_standard_form_data吞没.由于未读取任何数据,因此无需执行任何操作.

Case 3. This last case takes place with requests different from POST (e.g. PUT). php_default_post_reader is directly called. Because the request is not a POST request, the data is swallowed by sapi_read_standard_form_data. Since no data is read, there's not anything left to be done.

这篇关于为什么php://input会被多次阅读,尽管文档另有说明?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆