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 = <<