Skip to content

Commit 1a593ee

Browse files
committed
Move entire body handling to RequestBodyBufferMiddleware
1 parent 9b9920b commit 1a593ee

File tree

3 files changed

+83
-86
lines changed

3 files changed

+83
-86
lines changed

src/Middleware/RequestBodyBufferMiddleware.php

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
use Psr\Http\Message\ServerRequestInterface;
66
use React\Http\Response;
7+
use React\Http\ChunkedDecoder;
8+
use React\Http\SizeLimitedStream;
9+
use React\Http\LengthLimitedStream;
10+
use React\Http\HttpBodyStream;
711
use React\Promise\Stream;
812
use React\Stream\ReadableStreamInterface;
913
use RingCentral\Psr7\BufferStream;
@@ -29,9 +33,49 @@ public function __construct($sizeLimit = null)
2933

3034
public function __invoke(ServerRequestInterface $request, $stack)
3135
{
32-
$size = $request->getBody()->getSize();
36+
$contentLength = 0;
37+
$sizeExceeded = false;
38+
39+
/** @var HttpBodyStream */
40+
$body = $request->getBody();
41+
if ($request->hasHeader('Transfer-Encoding')) {
42+
43+
if (strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') {
44+
// $this->emit('error', array(new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding')));
45+
// return $this->writeError($conn, 501, $request);
46+
return new Response(501, array('Content-Type' => 'text/plain'), 'Only chunked-encoding is allowed for Transfer-Encoding');
47+
}
48+
49+
$body = new ChunkedDecoder($body);
50+
51+
// handle max body size
52+
$body = new SizeLimitedStream($body, $this->sizeLimit);
53+
$body->on('error', function($error) use (&$sizeExceeded) {
54+
if ($error instanceof \OverflowException) {
55+
$sizeExceeded = true;
56+
}
57+
});
58+
59+
$request = $request->withoutHeader('Transfer-Encoding');
60+
$request = $request->withoutHeader('Content-Length');
61+
62+
$contentLength = null;
63+
} elseif ($request->hasHeader('Content-Length')) {
64+
$string = $request->getHeaderLine('Content-Length');
3365

34-
if ($size > $this->sizeLimit) {
66+
$contentLength = (int)$string;
67+
if ((string)$contentLength !== (string)$string) {
68+
// Content-Length value is not an integer or not a single integer
69+
$this->emit('error', array(new \InvalidArgumentException('The value of `Content-Length` is not valid')));
70+
return $this->writeError($conn, 400, $request);
71+
}
72+
73+
$body = new LengthLimitedStream($body, $contentLength);
74+
}
75+
76+
$request = $request->withBody(new HttpBodyStream($body, $contentLength));
77+
78+
if ($contentLength > $this->sizeLimit) {
3579
return new Response(413, array('Content-Type' => 'text/plain'), 'Request body exceeds allowed limit');
3680
}
3781

@@ -46,6 +90,12 @@ public function __invoke(ServerRequestInterface $request, $stack)
4690
$request = $request->withBody($stream);
4791

4892
return $stack($request);
93+
}, function($error) use (&$sizeExceeded) {
94+
if ($sizeExceeded) {
95+
return new Response(413, array('Content-Type' => 'text/plain'), 'Request body exceeds allowed limit');
96+
}
97+
98+
return $error;
4999
});
50100
}
51101

src/Server.php

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
class Server extends EventEmitter
7777
{
7878
private $callback;
79-
private $bufferSize;
8079

8180
/**
8281
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
@@ -87,17 +86,15 @@ class Server extends EventEmitter
8786
* See also [listen()](#listen) for more details.
8887
*
8988
* @param callable $callback
90-
* @param int $bufferSize Max buffer for chunked requests
9189
* @see self::listen()
9290
*/
93-
public function __construct($callback, $bufferSize = 16 * 1024 * 1024)
91+
public function __construct($callback)
9492
{
9593
if (!is_callable($callback)) {
9694
throw new \InvalidArgumentException();
9795
}
9896

9997
$this->callback = $callback;
100-
$this->bufferSize = $bufferSize;
10198
}
10299

103100
/**
@@ -197,56 +194,14 @@ public function handleConnection(ConnectionInterface $conn)
197194
/** @internal */
198195
public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $request)
199196
{
200-
$cancel = null;
201-
$that = $this;
202-
203-
$contentLength = 0;
204-
$stream = new CloseProtectionStream($conn);
205-
if ($request->hasHeader('Transfer-Encoding')) {
206-
if (strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') {
207-
$this->emit('error', array(new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding')));
208-
return $this->writeError($conn, 501, $request);
209-
}
210-
211-
$stream = new ChunkedDecoder($stream);
212-
$stream = new SizeLimitedStream($stream, $this->bufferSize);
213-
214-
// handle max body size
215-
$stream->on('error', function($error) use ($that, $conn, $request, $cancel) {
216-
if ($error instanceof \OverflowException) {
217-
if ($cancel instanceof CancellablePromiseInterface) {
218-
$cancel->cancel();
219-
}
220-
221-
$that->emit('error', array($error));
222-
return $that->writeError($conn, 413, $request);
223-
}
224-
});
225-
226-
$request = $request->withoutHeader('Transfer-Encoding');
227-
$request = $request->withoutHeader('Content-Length');
228-
229-
$contentLength = null;
230-
} elseif ($request->hasHeader('Content-Length')) {
231-
$string = $request->getHeaderLine('Content-Length');
232-
233-
$contentLength = (int)$string;
234-
if ((string)$contentLength !== (string)$string) {
235-
// Content-Length value is not an integer or not a single integer
236-
$this->emit('error', array(new \InvalidArgumentException('The value of `Content-Length` is not valid')));
237-
return $this->writeError($conn, 400, $request);
238-
}
239-
240-
$stream = new LengthLimitedStream($stream, $contentLength);
241-
}
242-
243-
$request = $request->withBody(new HttpBodyStream($stream, $contentLength));
197+
$request = $request->withBody(new HttpBodyStream(new CloseProtectionStream($conn), null));
244198

245199
if ($request->getProtocolVersion() !== '1.0' && '100-continue' === strtolower($request->getHeaderLine('Expect'))) {
246200
$conn->write("HTTP/1.1 100 Continue\r\n\r\n");
247201
}
248202

249203
$callback = $this->callback;
204+
$cancel = null;
250205
$promise = new Promise(function ($resolve, $reject) use ($callback, $request, &$cancel) {
251206
$cancel = $callback($request);
252207
$resolve($cancel);
@@ -259,6 +214,7 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface
259214
});
260215
}
261216

217+
$that = $this;
262218
$promise->then(
263219
function ($response) use ($that, $conn, $request) {
264220
if (!$response instanceof ResponseInterface) {
@@ -287,13 +243,6 @@ function ($error) use ($that, $conn, $request) {
287243
return $that->writeError($conn, 500, $request);
288244
}
289245
);
290-
291-
if ($contentLength === 0) {
292-
// If Body is empty or Content-Length is 0 and won't emit further data,
293-
// 'data' events from other streams won't be called anymore
294-
$stream->emit('end');
295-
$stream->close();
296-
}
297246
}
298247

299248
/** @internal */

src/SizeLimitedStream.php

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,29 +63,27 @@ public function close()
6363
$this->removeAllListeners();
6464
}
6565

66-
/** @internal */
67-
public function handleData($data)
68-
{
69-
$sizeExceeded = false;
70-
71-
if (($this->transferredLength + strlen($data)) > $this->maxLength) {
72-
// Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
73-
$data = (string)substr($data, 0, $this->maxLength - $this->transferredLength);
74-
$sizeExceeded = true;
75-
}
76-
77-
if ($data !== '') {
78-
$this->transferredLength += strlen($data);
79-
$this->emit('data', array($data));
80-
}
81-
82-
if ($sizeExceeded) {
83-
$this->handleError(new \OverflowException());
84-
$this->emit('end');
85-
$this->close();
86-
$this->stream->removeListener('data', array($this, 'handleData'));
87-
}
88-
}
66+
/** @internal */
67+
public function handleData($data)
68+
{
69+
$sizeExceeded = false;
70+
71+
if (($this->transferredLength + strlen($data)) > $this->maxLength) {
72+
// Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
73+
$data = (string)substr($data, 0, $this->maxLength - $this->transferredLength);
74+
$sizeExceeded = true;
75+
}
76+
77+
if ($data !== '') {
78+
$this->transferredLength += strlen($data);
79+
$this->emit('data', array($data));
80+
}
81+
82+
if ($sizeExceeded) {
83+
$this->handleError(new \OverflowException());
84+
$this->handleEnd();
85+
}
86+
}
8987

9088
/** @internal */
9189
public function handleError(\Exception $e)
@@ -95,10 +93,10 @@ public function handleError(\Exception $e)
9593
}
9694

9795
/** @internal */
98-
public function handleEnd()
99-
{
100-
$this->emit('end');
101-
$this->close();
102-
$this->stream->removeListener('data', array($this, 'handleData'));
103-
}
96+
public function handleEnd()
97+
{
98+
$this->emit('end');
99+
$this->close();
100+
$this->stream->removeListener('data', array($this, 'handleData'));
101+
}
104102
}

0 commit comments

Comments
 (0)