diff --git a/composer.json b/composer.json index b350a9a..73ee8bf 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ }, "require": { "php": ">=7.4", - "guzzlehttp/psr7": "^2 || ^1.7", + "psr/http-factory-implementation": "^1.0", "symfony/polyfill-php80": "^1.15" }, "require-dev": { diff --git a/src/Handshake/ClientNegotiator.php b/src/Handshake/ClientNegotiator.php index a7a9125..cfa6575 100644 --- a/src/Handshake/ClientNegotiator.php +++ b/src/Handshake/ClientNegotiator.php @@ -3,22 +3,28 @@ namespace Ratchet\RFC6455\Handshake; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; -use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\RequestFactoryInterface; class ClientNegotiator { private ResponseVerifier $verifier; private RequestInterface $defaultHeader; - public function __construct(PermessageDeflateOptions $perMessageDeflateOptions = null) { - $this->verifier = new ResponseVerifier; + private RequestFactoryInterface $requestFactory; - $this->defaultHeader = new Request('GET', '', [ - 'Connection' => 'Upgrade' - , 'Upgrade' => 'websocket' - , 'Sec-WebSocket-Version' => $this->getVersion() - , 'User-Agent' => "Ratchet" - ]); + public function __construct( + RequestFactoryInterface $requestFactory, + PermessageDeflateOptions $perMessageDeflateOptions = null + ) { + $this->verifier = new ResponseVerifier; + $this->requestFactory = $requestFactory; + + $this->defaultHeader = $this->requestFactory + ->createRequest('GET', '') + ->withHeader('Connection' , 'Upgrade') + ->withHeader('Upgrade' , 'websocket') + ->withHeader('Sec-WebSocket-Version', $this->getVersion()) + ->withHeader('User-Agent' , 'Ratchet'); $perMessageDeflateOptions ??= PermessageDeflateOptions::createDisabled(); @@ -38,7 +44,7 @@ class ClientNegotiator { public function generateRequest(UriInterface $uri): RequestInterface { return $this->defaultHeader->withUri($uri) - ->withHeader("Sec-WebSocket-Key", $this->generateKey()); + ->withHeader('Sec-WebSocket-Key', $this->generateKey()); } public function validateResponse(RequestInterface $request, ResponseInterface $response): bool { diff --git a/src/Handshake/ServerNegotiator.php b/src/Handshake/ServerNegotiator.php index ab13e81..94f0549 100644 --- a/src/Handshake/ServerNegotiator.php +++ b/src/Handshake/ServerNegotiator.php @@ -1,6 +1,7 @@ verifier = $requestVerifier; + $this->responseFactory = $responseFactory; // https://bugs.php.net/bug.php?id=73373 // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18 @@ -51,47 +59,49 @@ class ServerNegotiator implements NegotiatorInterface { * {@inheritdoc} */ public function handshake(RequestInterface $request): ResponseInterface { + $response = $this->responseFactory->createResponse(); if (true !== $this->verifier->verifyMethod($request->getMethod())) { - return new Response(405, ['Allow' => 'GET']); + return $response->withHeader('Allow', 'GET')->withStatus(405); } if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) { - return new Response(505); + return $response->withStatus(505); } if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) { - return new Response(400); + return $response->withStatus(400); } if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) { - return new Response(400); + return $response->withStatus(400); } - $upgradeSuggestion = [ - 'Connection' => 'Upgrade', - 'Upgrade' => 'websocket', - 'Sec-WebSocket-Version' => $this->getVersionNumber() - ]; + $upgradeResponse = $response + ->withHeader('Connection' , 'Upgrade') + ->withHeader('Upgrade' , 'websocket') + ->withHeader('Sec-WebSocket-Version', $this->getVersionNumber()); + if (count($this->_supportedSubProtocols) > 0) { - $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', array_keys($this->_supportedSubProtocols)); + $upgradeResponse = $upgradeResponse->withHeader( + 'Sec-WebSocket-Protocol', implode(', ', array_keys($this->_supportedSubProtocols)) + ); } if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) { - return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided'); + return $upgradeResponse->withStatus(426, 'Upgrade header MUST be provided'); } if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) { - return new Response(400, [], null, '1.1', 'Connection Upgrade MUST be requested'); + return $response->withStatus(400, 'Connection Upgrade MUST be requested'); } if (true !== $this->verifier->verifyKey($request->getHeader('Sec-WebSocket-Key'))) { - return new Response(400, [], null, '1.1', 'Invalid Sec-WebSocket-Key'); + return $response->withStatus(400, 'Invalid Sec-WebSocket-Key'); } if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) { - return new Response(426, $upgradeSuggestion); + return $upgradeResponse->withStatus(426); } - $headers = []; $subProtocols = $request->getHeader('Sec-WebSocket-Protocol'); if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) { $subProtocols = array_map('trim', explode(',', implode(',', $subProtocols))); @@ -99,20 +109,20 @@ class ServerNegotiator implements NegotiatorInterface { $match = array_reduce($subProtocols, fn ($accumulator, $protocol) => $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null), null); if ($this->_strictSubProtocols && null === $match) { - return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported'); + return $upgradeResponse->withStatus(426, 'No Sec-WebSocket-Protocols requested supported'); } if (null !== $match) { - $headers['Sec-WebSocket-Protocol'] = $match; + $response = $response->withHeader('Sec-WebSocket-Protocol', $match); } } - $response = new Response(101, array_merge($headers, [ - 'Upgrade' => 'websocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]) - , 'X-Powered-By' => 'Ratchet' - ])); + $response = $response + ->withStatus(101) + ->withHeader('Upgrade' , 'websocket') + ->withHeader('Connection' , 'Upgrade') + ->withHeader('Sec-WebSocket-Accept', $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])) + ->withHeader('X-Powered-By' , 'Ratchet'); try { $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; diff --git a/tests/ab/clientRunner.php b/tests/ab/clientRunner.php index c186167..32841e1 100644 --- a/tests/ab/clientRunner.php +++ b/tests/ab/clientRunner.php @@ -12,6 +12,7 @@ use Ratchet\RFC6455\Messaging\MessageInterface; use React\Promise\Deferred; use Ratchet\RFC6455\Messaging\Frame; use React\Promise\PromiseInterface; +use GuzzleHttp\Psr7\HttpFactory; use React\Socket\ConnectionInterface; use React\Socket\Connector; @@ -58,7 +59,7 @@ function getTestCases(): PromiseInterface { $deferred = new Deferred(); $connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $testServer): void { - $cn = new ClientNegotiator(); + $cn = new ClientNegotiator(new HttpFactory()); $cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002/getCaseCount')); $rawResponse = ""; @@ -110,6 +111,7 @@ function getTestCases(): PromiseInterface { } $cn = new ClientNegotiator( + new HttpFactory(), PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null); function runTest(int $case) @@ -124,6 +126,7 @@ function runTest(int $case) $connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $casePath, $case, $testServer): void { $cn = new ClientNegotiator( + new HttpFactory(), PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null); $cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $casePath)); @@ -185,7 +188,7 @@ function createReport(): PromiseInterface { // $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true"; // we will stop it using docker now instead of just shutting down $reportPath = "/updateReports?agent=" . AGENT; - $cn = new ClientNegotiator(); + $cn = new ClientNegotiator(new HttpFactory()); $cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $reportPath)); $rawResponse = ""; diff --git a/tests/ab/run_ab_tests.sh b/tests/ab/run_ab_tests.sh old mode 100644 new mode 100755 diff --git a/tests/ab/startServer.php b/tests/ab/startServer.php index dde3365..0f6fc44 100644 --- a/tests/ab/startServer.php +++ b/tests/ab/startServer.php @@ -10,6 +10,7 @@ use Ratchet\RFC6455\Messaging\MessageBuffer; use Ratchet\RFC6455\Messaging\MessageInterface; use Ratchet\RFC6455\Messaging\FrameInterface; use Ratchet\RFC6455\Messaging\Frame; +use GuzzleHttp\Psr7\HttpFactory; require_once __DIR__ . "/../bootstrap.php"; @@ -18,7 +19,12 @@ $loop = \React\EventLoop\Factory::create(); $socket = new \React\Socket\Server('0.0.0.0:9001', $loop); $closeFrameChecker = new CloseFrameChecker; -$negotiator = new ServerNegotiator(new RequestVerifier, PermessageDeflateOptions::permessageDeflateSupported()); + +$negotiator = new ServerNegotiator( + new RequestVerifier, + new HttpFactory(), + PermessageDeflateOptions::permessageDeflateSupported() +); $uException = new \UnderflowException; diff --git a/tests/unit/Handshake/ServerNegotiatorTest.php b/tests/unit/Handshake/ServerNegotiatorTest.php index b82784e..382d96a 100644 --- a/tests/unit/Handshake/ServerNegotiatorTest.php +++ b/tests/unit/Handshake/ServerNegotiatorTest.php @@ -3,6 +3,7 @@ namespace Ratchet\RFC6455\Test\Unit\Handshake; use GuzzleHttp\Psr7\Message; +use GuzzleHttp\Psr7\HttpFactory; use Ratchet\RFC6455\Handshake\RequestVerifier; use Ratchet\RFC6455\Handshake\ServerNegotiator; use PHPUnit\Framework\TestCase; @@ -13,7 +14,7 @@ use PHPUnit\Framework\TestCase; class ServerNegotiatorTest extends TestCase { public function testNoUpgradeRequested(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $requestText = 'GET / HTTP/1.1 Host: 127.0.0.1:6789 @@ -41,7 +42,7 @@ Accept-Language: en-US,en;q=0.8 } public function testNoConnectionUpgradeRequested(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $requestText = 'GET / HTTP/1.1 Host: 127.0.0.1:6789 @@ -67,7 +68,7 @@ Accept-Language: en-US,en;q=0.8 } public function testInvalidSecWebsocketKey(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $requestText = 'GET / HTTP/1.1 Host: 127.0.0.1:6789 @@ -94,7 +95,7 @@ Accept-Language: en-US,en;q=0.8 } public function testInvalidSecWebsocketVersion(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $requestText = 'GET / HTTP/1.1 Host: 127.0.0.1:6789 @@ -124,7 +125,7 @@ Accept-Language: en-US,en;q=0.8 } public function testBadSubprotocolResponse(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $negotiator->setStrictSubProtocolCheck(true); $negotiator->setSupportedSubProtocols([]); @@ -158,7 +159,7 @@ Accept-Language: en-US,en;q=0.8 } public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $negotiator->setStrictSubProtocolCheck(false); $negotiator->setSupportedSubProtocols(['someproto']); @@ -192,7 +193,7 @@ Accept-Language: en-US,en;q=0.8 public function testSuggestsAppropriateSubprotocol(): void { - $negotiator = new ServerNegotiator(new RequestVerifier()); + $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); $negotiator->setStrictSubProtocolCheck(true); $negotiator->setSupportedSubProtocols(['someproto']);