Почему ввод 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.

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

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

Случай 1. Это происходит, когда у нас есть запрос POST:

  • С помощью application/x-www-form-encoded типа контента 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 почтовый читатель 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 типа контента application/x-www-form-encoded , поэтому все данные проглатываются в SG(request_info).post_data . Единственные отличия от этого $HTTP_RAW_POST_DATA том, что всегда заполняется $HTTP_RAW_POST_DATA (независимо от значения always_populate_post_data ), и нет обработчика для создания суперглобалов.

Случай 2. Это происходит, когда у нас есть запрос формы с типом «multipart / form-data» типа контента. rfc1867_post_handler 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 . Поскольку данные не считываются, нечего делать.

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