From e61c80758fc3477be50f79678a459f05c81563a6 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Sun, 16 Aug 2020 23:57:12 -0400 Subject: [PATCH 1/3] Update to new RFC6455 for permessage-deflate --- composer.json | 2 +- src/WebSocketConnection.php | 36 +++++++++++++++---- src/WebSocketMiddleware.php | 20 +++++++++-- src/WebSocketOptions.php | 69 +++++++++++++++++++++++++++++++++++++ tests/ab/fuzzingclient.json | 2 +- tests/ab/testServer.php | 4 +-- 6 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 src/WebSocketOptions.php diff --git a/composer.json b/composer.json index 9457758..17b68c8 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "ratchet/rfc6455": "^0.2.3", + "ratchet/rfc6455": "^0.3", "react/http": "^1.0" }, "require-dev":{ diff --git a/src/WebSocketConnection.php b/src/WebSocketConnection.php index 43559bc..28e17df 100644 --- a/src/WebSocketConnection.php +++ b/src/WebSocketConnection.php @@ -4,6 +4,7 @@ use Evenement\EventEmitterInterface; use Evenement\EventEmitterTrait; +use Ratchet\RFC6455\Handshake\PermessageDeflateOptions; use Ratchet\RFC6455\Messaging\CloseFrameChecker; use Ratchet\RFC6455\Messaging\Frame; use Ratchet\RFC6455\Messaging\Message; @@ -17,9 +18,19 @@ class WebSocketConnection implements EventEmitterInterface private $stream; - public function __construct(DuplexStreamInterface $stream) + /** @var WebSocketOptions */ + private $webSocketOptions; + + /** @var PermessageDeflateOptions */ + private $permessageDeflateOptions; + + private $messageBuffer; + + public function __construct(DuplexStreamInterface $stream, WebSocketOptions $webSocketOptions, PermessageDeflateOptions $permessageDeflateOptions) { - $this->stream = $stream; + $this->stream = $stream; + $this->webSocketOptions = $webSocketOptions; + $this->permessageDeflateOptions = $permessageDeflateOptions; $mb = new MessageBuffer( new CloseFrameChecker(), @@ -46,19 +57,32 @@ function (Frame $frame) { return; } }, - true + true, + null, + $this->webSocketOptions->getMaxMessagePayloadSize(), + $this->webSocketOptions->getMaxFramePayloadSize(), + [$this->stream, 'write'], + $this->permessageDeflateOptions ); + $this->messageBuffer = $mb; + $stream->on('data', [$mb, 'onData']); } public function send($data) { - if (!($data instanceof MessageInterface)) { - $data = new Frame($data, true, Frame::OP_TEXT); + if ($data instanceof Frame) { + $this->messageBuffer->sendFrame($data); + return; + } + + if ($data instanceof MessageInterface) { + $this->messageBuffer->sendMessage($data->getPayload(), true, $data->isBinary()); + return; } - $this->stream->write($data->getContents()); + $this->messageBuffer->sendMessage($data); } public function close($code = 1000, $reason = '') diff --git a/src/WebSocketMiddleware.php b/src/WebSocketMiddleware.php index 1ef8c8f..b90ee9d 100644 --- a/src/WebSocketMiddleware.php +++ b/src/WebSocketMiddleware.php @@ -3,6 +3,7 @@ namespace Voryx\WebSocketMiddleware; use Psr\Http\Message\ServerRequestInterface; +use Ratchet\RFC6455\Handshake\PermessageDeflateOptions; use Ratchet\RFC6455\Handshake\RequestVerifier; use Ratchet\RFC6455\Handshake\ServerNegotiator; use React\Http\Message\Response; @@ -14,12 +15,14 @@ final class WebSocketMiddleware private $paths; private $connectionHandler = null; private $subProtocols; + private $webSocketOptions = null; - public function __construct(array $paths = [], callable $connectionHandler = null, array $subProtocols = []) + public function __construct(array $paths = [], callable $connectionHandler = null, array $subProtocols = [], WebSocketOptions $webSocketOptions = null) { $this->paths = $paths; $this->connectionHandler = $connectionHandler ?: function () {}; $this->subProtocols = $subProtocols; + $this->webSocketOptions = $webSocketOptions ?? WebSocketOptions::getDefault(); } public function __invoke(ServerRequestInterface $request, callable $next = null) @@ -35,7 +38,7 @@ public function __invoke(ServerRequestInterface $request, callable $next = null) } } - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), $this->webSocketOptions->isPermessageDeflateEnabled()); $negotiator->setSupportedSubProtocols($this->subProtocols); $negotiator->setStrictSubProtocolCheck(true); @@ -50,6 +53,13 @@ public function __invoke(ServerRequestInterface $request, callable $next = null) return $next($request); } + try { + $permessageDeflateOptions = PermessageDeflateOptions::fromRequestOrResponse($request); + } catch (\Exception $e) { + // 500 - Internal server error + return new Response(500, [], 'Error negotiating websocket permessage-deflate: ' . $e->getMessage()); + } + $inStream = new ThroughStream(); $outStream = new ThroughStream(); @@ -62,7 +72,11 @@ public function __invoke(ServerRequestInterface $request, callable $next = null) ) ); - $conn = new WebSocketConnection(new CompositeStream($inStream, $outStream)); + $conn = new WebSocketConnection( + new CompositeStream($inStream, $outStream), + $this->webSocketOptions, + $permessageDeflateOptions[0] + ); call_user_func($this->connectionHandler, $conn, $request, $response); diff --git a/src/WebSocketOptions.php b/src/WebSocketOptions.php new file mode 100644 index 0000000..9509961 --- /dev/null +++ b/src/WebSocketOptions.php @@ -0,0 +1,69 @@ +permessageDeflateEnabled = true; + + return $c; + } + + public function withoutPermessageDeflate() + { + $c = clone $this; + $c->permessageDeflateEnabled = false; + + return $c; + } + + public function withMaxMessagePayloadSize($maxSize) + { + $c = clone $this; + $c->maxMessagePayloadSize = $maxSize; + + return $c; + } + + public function withMaxFramePayloadSize($maxSize) + { + $c = clone $this; + $c->maxFramePayloadSize = $maxSize; + + return $c; + } + + public function isPermessageDeflateEnabled() + { + return $this->permessageDeflateEnabled; + } + + public function getMaxMessagePayloadSize() + { + return $this->maxMessagePayloadSize; + } + + public function getMaxFramePayloadSize() + { + return $this->maxFramePayloadSize; + } +} \ No newline at end of file diff --git a/tests/ab/fuzzingclient.json b/tests/ab/fuzzingclient.json index 73232d1..6abadb4 100644 --- a/tests/ab/fuzzingclient.json +++ b/tests/ab/fuzzingclient.json @@ -7,6 +7,6 @@ ] , "cases": ["*"] - , "exclude-cases": ["10.*", "11.*", "12.*", "13.*"] + , "exclude-cases": [] , "exclude-agent-cases": {} } diff --git a/tests/ab/testServer.php b/tests/ab/testServer.php index 8c457b5..1ca9f36 100644 --- a/tests/ab/testServer.php +++ b/tests/ab/testServer.php @@ -14,11 +14,11 @@ $conn->on('message', function (Message $message) use ($conn) { $conn->send($message); }); -}); +}, [], \Voryx\WebSocketMiddleware\WebSocketOptions::getDefault()->withPermessageDeflate()); $server = new Server($loop, $ws); -$server->listen(new \React\Socket\Server('127.0.0.1:4321', $loop)); +$server->listen(new \React\Socket\Server('0.0.0.0:4321', $loop)); $loop->run(); From e062ad968a7da85ac959dbdc581975ae6ab9d8e3 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Mon, 17 Aug 2020 00:15:21 -0400 Subject: [PATCH 2/3] Use disabled `PermessageDeflateOptions` if disabled --- src/WebSocketMiddleware.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/WebSocketMiddleware.php b/src/WebSocketMiddleware.php index b90ee9d..1880b88 100644 --- a/src/WebSocketMiddleware.php +++ b/src/WebSocketMiddleware.php @@ -60,6 +60,12 @@ public function __invoke(ServerRequestInterface $request, callable $next = null) return new Response(500, [], 'Error negotiating websocket permessage-deflate: ' . $e->getMessage()); } + if (!$this->webSocketOptions->isPermessageDeflateEnabled()) { + $permessageDeflateOptions = [ + PermessageDeflateOptions::createDisabled() + ]; + } + $inStream = new ThroughStream(); $outStream = new ThroughStream(); From 349eae9e3eaef446b545ad28ccf40f185341cfe4 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Mon, 17 Aug 2020 00:30:41 -0400 Subject: [PATCH 3/3] Update README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02b574e..303754f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ A simple echo server: ```php use Ratchet\RFC6455\Messaging\Message; use React\EventLoop\Factory; -use React\Http\MiddlewareRunner; use React\Http\Server; use Voryx\WebSocketMiddleware\WebSocketConnection; use Voryx\WebSocketMiddleware\WebSocketMiddleware; @@ -28,3 +27,16 @@ $server->listen(new \React\Socket\Server('127.0.0.1:4321', $loop)); $loop->run(); ``` +# Options +By default `WebSocketMiddleware` uses the `ratchet/rfc6455` default max sizes for messages and frames and also disables compression. +These settings can be overridden with the `WebSocketOptions` object. +```php +$ws = new WebSocketMiddleware( + [], + $connectionHandler, + [], + WebSocketOptions::getDefault() + ->withMaxFramePayloadSize(2048) + ->withMaxMessagePayloadSize(4096) + ->withPermessageDeflate()); +```