Почему ввод php://может быть прочитан более одного раза, несмотря на то, что документация говорит иначе?

Документация 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";

Керлинг:

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

This is a test

По-видимому, он ограничивается одним прочитанным для открытого дескриптора.


Немного больше копания показало, что действительно php://input можно читать только один раз, когда-либо, для запросов PUT. В приведенном выше примере использовался запрос POST.

Ответы

Ответ 1

Небольшая проверка исходного кода дает ответы.

Во-первых, да, вы ограничены одним чтением за дескриптор, потому что базовый поток не реализует обработчик 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;
}

Итак, у нас есть три возможности:

  • Данные тела запроса уже прочитаны и сохранены в SG(request_info).raw_post_data. В этом случае, поскольку данные хранятся, мы можем открывать и читать несколько дескрипторов для php://input.
  • Данные тела запроса были прочитаны, но его содержимое не хранилось нигде. php://input не может дать нам ничего.
  • Данные запроса еще не прочитаны. Это означает, что мы можем открыть php://input и прочитать его только один раз.

ПРИМЕЧАНИЕ. Ниже следует поведение по умолчанию. Различные SAPI или дополнительные расширения могут изменить это поведение.

В случае запросов POST PHP определяет другое ПОСТ-считывающее устройство и обработчик 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_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. Это происходит, когда у нас есть запрос формы с типом "multipart/form-data" типа контента. Считыватель POST NULL, поэтому обработчик rfc1867_post_handler действует как смешанный reader/handler. Никакие данные не считываются в фазе sapi_activate. Функция sapi_handle_post в конечном итоге вызывается в более поздней фазе, которая, в свою очередь, вызывает обработчик POST. rfc1867_post_handler считывает данные запроса, заполняет POST и FILES, но ничего не оставляет в SG(request_info).raw_post_data.

Случай 3. Этот последний случай выполняется с запросами, отличными от POST (например, PUT). php_default_post_reader непосредственно вызывается. Поскольку запрос не является запросом POST, данные проглатываются sapi_read_standard_form_data. Поскольку данные не считываются, нечего делать.

Ответ 2

Может быть, они означают, что fseek() или перемотка назад() недоступны. Вы пробовали одну из этих функций на открывшемся входе php://?