diff --git a/README.md b/README.md index 0286f3e3..2ad17a46 100644 --- a/README.md +++ b/README.md @@ -722,56 +722,71 @@ $middlewares = new MiddlewareRunner([ #### RequestBodyParserMiddleware -The `RequestBodyParserMiddleware` takes a fully buffered request body (generally from [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware)), -and parses the forms and uploaded files from the request body. - -Parsed submitted forms will be available from `$request->getParsedBody()` as -array. -For example the following submitted body (`application/x-www-form-urlencoded`): - -`bar[]=beer&bar[]=wine` - -Results in the following parsed body: +The `RequestBodyParserMiddleware` takes a fully buffered request body +(generally from [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware)), +and parses the form values and file uploads from the incoming HTTP request body. + +This middleware handler takes care of applying values from HTTP +requests that use `Content-Type: application/x-www-form-urlencoded` or +`Content-Type: multipart/form-data` to resemble PHP's default superglobals +`$_POST` and `$_FILES`. +Instead of relying on these superglobals, you can use the +`$request->getParsedBody()` and `$request->getUploadedFiles()` methods +as defined by PSR-7. + +Accordingly, each file upload will be represented as instance implementing [`UploadedFileInterface`](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#36-psrhttpmessageuploadedfileinterface). +Due to its blocking nature, the `moveTo()` method is not available and throws +a `RuntimeException` instead. +You can use `$contents = (string)$file->getStream();` to access the file +contents and persist this to your favorite data store. ```php -$parsedBody = [ - 'bar' => [ - 'beer', - 'wine', - ], -]; -``` - -Aside from `application/x-www-form-urlencoded`, this middleware handler -also supports `multipart/form-data`, thus supporting uploaded files available -through `$request->getUploadedFiles()`. - -The `$request->getUploadedFiles(): array` will return an array with all -uploaded files formatted like this: - -```php -$uploadedFiles = [ - 'avatar' => new UploadedFile(/**...**/), - 'screenshots' => [ - new UploadedFile(/**...**/), - new UploadedFile(/**...**/), - ], -]; -``` +$handler = function (ServerRequestInterface $request) { + // If any, parsed form fields are now available from $request->getParsedBody() + $body = $request->getParsedBody(); + $name = isset($body['name']) ? $body['name'] : 'unnamed'; + + $files = $request->getUploadedFiles(); + $avatar = isset($files['avatar']) ? $files['avatar'] : null; + if ($avatar instanceof UploadedFileInterface) { + if ($avatar->getError() === UPLOAD_ERR_OK) { + $uploaded = $avatar->getSize() . ' bytes'; + } else { + $uploaded = 'with error'; + } + } else { + $uploaded = 'nothing'; + } -Usage: + return new Response( + 200, + array( + 'Content-Type' => 'text/plain' + ), + $name . ' uploaded ' . $uploaded + ); +}; -```php -$middlewares = new MiddlewareRunner([ +$server = new Server(new MiddlewareRunner([ new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB new RequestBodyParserMiddleware(), - function (ServerRequestInterface $request, callable $next) { - // If any, parsed form fields are now available from $request->getParsedBody() - return new Response(200); - }, -]); + $handler +])); ``` +See also [example #12](examples) for more details. + +> Note that this middleware handler simply parses everything that is already + buffered in the request body. + It is imperative that the request body is buffered by a prior middleware + handler as given in the example above. + This previous middleware handler is also responsible for rejecting incoming + requests that exceed allowed message sizes (such as big file uploads). + If you use this middleware without buffering first, it will try to parse an + empty (streaming) body and may thus assume an empty data structure. + See also [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) for + more details. + #### Third-Party Middleware A non-exhaustive list of third-party middleware can be found at the [`Middleware`](https://github.com/reactphp/http/wiki/Middleware) wiki page. diff --git a/examples/12-upload.php b/examples/12-upload.php new file mode 100644 index 00000000..d723261f --- /dev/null +++ b/examples/12-upload.php @@ -0,0 +1,132 @@ +getMethod() === 'POST') { + // Take form input values from POST values (for illustration purposes only!) + // Does not actually validate data here + $body = $request->getParsedBody(); + $name = isset($body['name']) && is_string($body['name']) ? htmlspecialchars($body['name']) : 'n/a'; + $age = isset($body['age']) && is_string($body['age']) ? (int)$body['age'] : 'n/a'; + + // Show uploaded avatar as image (for illustration purposes only!) + // Real applications should validate the file data to ensure this is + // actually an image and not rely on the client media type. + $avatar = 'n/a'; + $uploads = $request->getUploadedFiles(); + if (isset($uploads['avatar']) && $uploads['avatar'] instanceof UploadedFileInterface) { + /* @var $file UploadedFileInterface */ + $file = $uploads['avatar']; + if ($file->getError() === UPLOAD_ERR_OK) { + // Note that moveFile() is not available due to its blocking nature. + // You can use your favorite data store to simply dump the file + // contents via `(string)$file->getStream()` instead. + // Here, we simply use an inline image to send back to client: + $avatar = ' (' . $file->getSize() . ' bytes)'; + } else { + // Real applications should probably check the error number and + // should print some human-friendly text + $avatar = 'upload error ' . $file->getError(); + } + } + + $dump = htmlspecialchars( + var_export($request->getParsedBody(), true) . + PHP_EOL . + var_export($request->getUploadedFiles(), true) + ); + + $body = << +$dump + +BODY; + } else { + $body = << + + + + + + + + +BODY; + } + + $html = << + + + + +$body + + + +HTML; + + return new Response( + 200, + array('Content-Type' => 'text/html; charset=UTF-8'), + $html + ); +}; + +// buffer and parse HTTP request body before running our request handler +$server = new Server(new MiddlewareRunner(array( + new RequestBodyBufferMiddleware(100000), // 100 KB max + new RequestBodyParserMiddleware(), + $handler +))); + +$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop); +$server->listen($socket); + +echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL; + +$loop->run();