Skip to content

Commit c8d66b8

Browse files
committed
Improve documentation for buffering the request body
1 parent 604d79a commit c8d66b8

File tree

3 files changed

+77
-30
lines changed

3 files changed

+77
-30
lines changed

README.md

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,9 @@ and
238238
`Server`, but you can add these parameters by yourself using the given methods.
239239
The next versions of this project will cover these features.
240240

241-
Note that the request object will be processed once the request headers have
242-
been received.
241+
Note that by default, the request object will be processed once the request headers have
242+
been received (see also [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware)
243+
for an alternative).
243244
This means that this happens irrespective of (i.e. *before*) receiving the
244245
(potentially much larger) request body.
245246
While this may be uncommon in the PHP ecosystem, this is actually a very powerful
@@ -254,16 +255,18 @@ approach that gives you several advantages not otherwise possible:
254255
such as accepting a huge file upload or possibly unlimited request body stream.
255256

256257
The `getBody()` method can be used to access the request body stream.
257-
This method returns a stream instance that implements both the
258+
In the default streaming mode, this method returns a stream instance that implements both the
258259
[PSR-7 StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface)
259260
and the [ReactPHP ReadableStreamInterface](https://github.com/reactphp/stream#readablestreaminterface).
260261
However, most of the `PSR-7 StreamInterface` methods have been
261262
designed under the assumption of being in control of the request body.
262263
Given that this does not apply to this server, the following
263264
`PSR-7 StreamInterface` methods are not used and SHOULD NOT be called:
264265
`tell()`, `eof()`, `seek()`, `rewind()`, `write()` and `read()`.
265-
Instead, you should use the `ReactPHP ReadableStreamInterface` which
266-
gives you access to the incoming request body as the individual chunks arrive:
266+
If this is an issue for your use case, it's highly recommended to use the
267+
[`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) instead.
268+
The `ReactPHP ReadableStreamInterface` gives you access to the incoming
269+
request body as the individual chunks arrive:
267270

268271
```php
269272
$server = new Server(function (ServerRequestInterface $request) {
@@ -298,7 +301,7 @@ $server = new Server(function (ServerRequestInterface $request) {
298301
The above example simply counts the number of bytes received in the request body.
299302
This can be used as a skeleton for buffering or processing the request body.
300303

301-
See also [example #4](examples) for more details.
304+
See also [example #9](examples) for more details.
302305

303306
The `data` event will be emitted whenever new data is available on the request
304307
body stream.
@@ -415,7 +418,7 @@ non-alphanumeric characters.
415418
This encoding is also used internally when decoding the name and value of cookies
416419
(which is in line with other implementations, such as PHP's cookie functions).
417420

418-
See also [example #6](examples) for more details.
421+
See also [example #5](examples) for more details.
419422

420423
### Response
421424

@@ -680,24 +683,41 @@ $server = new Server(new MiddlewareRunner([
680683

681684
#### RequestBodyBufferMiddleware
682685

683-
One of the build in middleware is `RequestBodyBufferMiddleware` which will buffer the incoming
684-
request body until the reported size has been reached. Then it will call the next
685-
middleware in line with the new request instance containing the full request body.
686-
The constructor accepts one argument, a maximum request body size. When one isn't
687-
provided it will use `post_max_size` from PHP's configuration.
688-
(**Note that the value from the CLI configuration will be used.**)
686+
One of the built-in middleware is the `RequestBodyBufferMiddleware` which
687+
can be used to buffer the whole incoming request body in memory.
688+
This can be useful if full PSR-7 compatibility is needed for the request handler
689+
and the default streaming request body handling is not needed.
690+
The constructor accepts one optional argument, the maximum request body size.
691+
When one isn't provided it will use `post_max_size` (default 8 MiB) from PHP's
692+
configuration.
693+
(Note that the value from your matching SAPI will be used, which is the CLI
694+
configuration in most cases.)
695+
696+
Any incoming request that has a request body that exceeds this limit will be
697+
rejected with a `413` (Request Entity Too Large) error message without calling
698+
the next middleware handlers.
699+
700+
Any incoming request that does not have its size defined and uses the (rare)
701+
`Transfer-Encoding: chunked` will be rejected with a `411` (Length Required)
702+
error message without calling the next middleware handlers.
703+
Note that this only affects incoming requests, the much more common chunked
704+
transfer encoding for outgoing responses is not affected.
705+
It is recommended to define a `Content-Length` header instead.
706+
Note that this does not affect normal requests without a request body
707+
(such as a simple `GET` request).
689708

690-
Before buffering the request body the `RequestBodyBufferMiddleware` will check if the request
691-
body has a size. When size is null a [HTTP 411](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411)
692-
response will be send to the client. When size is bigger then supplied to the `RequestBodyBufferMiddleware`
693-
constructor or taken from `post_max_size` a [HTTP 413](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413)
694-
response will be dispatched.
709+
All other requests will be buffered in memory until the request body end has
710+
been reached and then call the next middleware handler with the complete,
711+
buffered request.
712+
Similarly, this will immediately invoke the next middleware handler for requests
713+
that have an empty request body (such as a simple `GET` request) and requests
714+
that are already buffered (such as due to another middleware).
695715

696716
Usage:
697717

698718
```php
699719
$middlewares = new MiddlewareRunner([
700-
new RequestBodyBufferMiddleware(),
720+
new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
701721
function (ServerRequestInterface $request, callable $next) {
702722
// The body from $request->getBody() is now fully available without the need to stream it
703723
return new Response(200);

src/Middleware/RequestBodyBufferMiddleware.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,11 @@ public function __invoke(ServerRequestInterface $request, $stack)
5454
}
5555

5656
/**
57-
* Gets post_max_size from PHP's configuration
58-
* and turns it into bytes up to a maximum of GigaBytes.
59-
* Anything other than configured as Bytes, KiloBytes, MegaBytes, or GigaBytes
60-
* is considered out of range.
57+
* Gets post_max_size from PHP's configuration expressed in bytes
6158
*
6259
* @return int
60+
* @link http://php.net/manual/en/ini.core.php#ini.post-max-size
61+
* @codeCoverageIgnore
6362
*/
6463
private function iniMaxPostSize()
6564
{

tests/Middleware/RequestBodyBufferMiddlewareTest.php

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,42 @@
33
namespace React\Tests\Http\Middleware;
44

55
use Psr\Http\Message\ServerRequestInterface;
6-
use React\Http\Middleware\RequestBodyBuffer;
6+
use React\Http\Middleware\RequestBodyBufferMiddleware;
77
use React\Http\ServerRequest;
88
use React\Tests\Http\TestCase;
99
use RingCentral\Psr7\BufferStream;
10+
use React\Stream\ThroughStream;
11+
use React\Http\HttpBodyStream;
1012

1113
final class RequestBodyBufferMiddlewareTest extends TestCase
1214
{
13-
public function testBuffer()
15+
public function testBufferingResolvesWhenStreamEnds()
16+
{
17+
$stream = new ThroughStream();
18+
$serverRequest = new ServerRequest(
19+
'GET',
20+
'https://example.com/',
21+
array(),
22+
new HttpBodyStream($stream, 11)
23+
);
24+
25+
$exposedRequest = null;
26+
$buffer = new RequestBodyBufferMiddleware(20);
27+
$buffer(
28+
$serverRequest,
29+
function (ServerRequestInterface $request) use (&$exposedRequest) {
30+
$exposedRequest = $request;
31+
}
32+
);
33+
34+
$stream->write('hello');
35+
$stream->write('world');
36+
$stream->end('!');
37+
38+
$this->assertSame('helloworld!', $exposedRequest->getBody()->getContents());
39+
}
40+
41+
public function testAlreadyBufferedResolvesImmediately()
1442
{
1543
$size = 1024;
1644
$body = str_repeat('x', $size);
@@ -24,7 +52,7 @@ public function testBuffer()
2452
);
2553

2654
$exposedRequest = null;
27-
$buffer = new RequestBodyBuffer();
55+
$buffer = new RequestBodyBufferMiddleware();
2856
$buffer(
2957
$serverRequest,
3058
function (ServerRequestInterface $request) use (&$exposedRequest) {
@@ -35,7 +63,7 @@ function (ServerRequestInterface $request) use (&$exposedRequest) {
3563
$this->assertSame($body, $exposedRequest->getBody()->getContents());
3664
}
3765

38-
public function test411Error()
66+
public function testUnknownSizeReturnsError411()
3967
{
4068
$body = $this->getMockBuilder('Psr\Http\Message\StreamInterface')->getMock();
4169
$body->expects($this->once())->method('getSize')->willReturn(null);
@@ -47,7 +75,7 @@ public function test411Error()
4775
$body
4876
);
4977

50-
$buffer = new RequestBodyBuffer();
78+
$buffer = new RequestBodyBufferMiddleware();
5179
$response = $buffer(
5280
$serverRequest,
5381
function () {}
@@ -56,7 +84,7 @@ function () {}
5684
$this->assertSame(411, $response->getStatusCode());
5785
}
5886

59-
public function test413Error()
87+
public function testExcessiveSizeReturnsError413()
6088
{
6189
$stream = new BufferStream(2);
6290
$stream->write('aa');
@@ -68,7 +96,7 @@ public function test413Error()
6896
$stream
6997
);
7098

71-
$buffer = new RequestBodyBuffer(1);
99+
$buffer = new RequestBodyBufferMiddleware(1);
72100
$response = $buffer(
73101
$serverRequest,
74102
function () {}

0 commit comments

Comments
 (0)