可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
PHP manual states that a stream opened with php://input support seek operation and can be read multiple times as of PHP 5.6, but I can't make it work. The following example clearly shows it doesn't work:
<!DOCTYPE html> <html> <body> <form method="post"> <input type="hidden" name="test_name" value="test_value"> <input type="submit"> </form> <?php if ($_SERVER['REQUEST_METHOD'] === 'POST') { $input = fopen('php://input', 'r'); echo 'First attempt: ' . fread($input, 1024) . '<br>'; if (fseek($input, 0) != 0) exit('Seek failed'); echo 'Second attempt: ' . fread($input, 1024) . '<br>'; } ?> </body> </html>
Output:
First attempt: test_name=test_value Second attempt:
php://input stream was
- successfully read
- successfully rewinded (fseek succeeded)
- unsuccessfully read
Am I doing something wrong?
回答1:
With the amount of exceptions and lack of portability using php://input
I'd recommend you to read the stream and save it to another stream to avoid unexpected behaviour.
You can use php://memory
in order to create a file-stream-like wrapper, which will give you all the same functionality that php://input
should have without all of the annoying behaviour.
Example:
<?php $inputHandle = fopen('php://memory', 'r+'); fwrite($inputHandle, file_get_contents('php://input')); fseek($inputHandle, 0);
Additionally you can create your own class to refer to this object consistently:
<?php class InputReader { private static $instance; /** * Factory for InputReader * * @param string $inputContents * * @return InputReader */ public static function instance($inputContents = null) { if (self::$instance === null) { self::$instance = new InputReader($inputContents); } return self::$instance; } protected $handle; /** * InputReader constructor. * * @param string $inputContents */ public function __construct($inputContents = null) { // Open up a new memory handle $this->handle = fopen('php://memory', 'r+'); // If we haven't specified the input contents (in case you're reading it from somewhere else like a framework), then we'll read it again if ($inputContents === null) { $inputContents = file_get_contents('php://input'); } // Write all the contents of php://input to our memory handle fwrite($this->handle, $inputContents); // Seek back to the start if we're reading anything fseek($this->handle, 0); } public function getHandle() { return $this->handle; } /** * Wrapper for fseek * * @param int $offset * @param int $whence * * @return InputReader * * @throws \Exception */ public function seek($offset, $whence = SEEK_SET) { if (fseek($this->handle, $offset, $whence) !== 0) { throw new \Exception('Could not use fseek on memory handle'); } return $this; } public function read($length) { $read = fread($this->handle, $length); if ($read === false) { throw new \Exception('Could not use fread on memory handle'); } return $read; } public function readAll($buffer = 8192) { $reader = ''; $this->seek(0); // make sure we start by seeking to offset 0 while (!$this->eof()) { $reader .= $this->read($buffer); } return $reader; } public function eof() { return feof($this->handle); } }
Usage:
$first1024Bytes = InputReader::instance()->seek(0)->read(1024); $next1024Bytes = InputReader::instance()->read(1024);
Usage (read all):
$phpInput = InputReader::instance()->readAll();
回答2:
Another approach might be to open the input stream each time instead of rewinding and seeking.
$input = fopen('php://input', 'r'); echo 'First attempt: ' . fread($input, 1024) . '<br>'; $input2 = fopen('php://input', 'r'); echo 'Second attempt: ' . fread($input2, 1024) . '<br>';
If the resource cost won't a problem.
Also there's file_get_contents
$input = file_get_contents("php://input"); $input = json_decode($input, TRUE);
if you're sending json.