From c11ecd9fb4867ac71854732ffe9e31d2e8090f78 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Mon, 17 Feb 2014 11:23:35 -0500 Subject: [PATCH 01/52] [Sessions] Decouple interface from WS, align with HTTP --- src/Ratchet/Session/SessionProvider.php | 20 +++++------------ tests/unit/Session/SessionComponentTest.php | 25 +-------------------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/src/Ratchet/Session/SessionProvider.php b/src/Ratchet/Session/SessionProvider.php index 9a885e8..445f08a 100644 --- a/src/Ratchet/Session/SessionProvider.php +++ b/src/Ratchet/Session/SessionProvider.php @@ -2,7 +2,8 @@ namespace Ratchet\Session; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; -use Ratchet\WebSocket\WsServerInterface; +use Ratchet\Http\HttpServerInterface; +use Guzzle\Http\Message\RequestInterface; use Ratchet\Session\Storage\VirtualSessionStorage; use Ratchet\Session\Serialize\HandlerInterface; use Symfony\Component\HttpFoundation\Session\Session; @@ -14,7 +15,7 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; * Your website must also use Symfony HttpFoundation Sessions to read your sites session data * If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer) */ -class SessionProvider implements MessageComponentInterface, WsServerInterface { +class SessionProvider implements HttpServerInterface { /** * @var \Ratchet\MessageComponentInterface */ @@ -76,8 +77,8 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { /** * {@inheritdoc} */ - function onOpen(ConnectionInterface $conn) { - if (!isset($conn->WebSocket) || null === ($id = $conn->WebSocket->request->getCookie(ini_get('session.name')))) { + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + if (null === $request || null === ($id = $request->getCookie(ini_get('session.name')))) { $saveHandler = $this->_null; $id = ''; } else { @@ -116,17 +117,6 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { return $this->_app->onError($conn, $e); } - /** - * {@inheritdoc} - */ - public function getSubProtocols() { - if ($this->_app instanceof WsServerInterface) { - return $this->_app->getSubProtocols(); - } else { - return array(); - } - } - /** * Set all the php session. ini options * © Symfony diff --git a/tests/unit/Session/SessionComponentTest.php b/tests/unit/Session/SessionComponentTest.php index c91f13e..4ad69aa 100644 --- a/tests/unit/Session/SessionComponentTest.php +++ b/tests/unit/Session/SessionComponentTest.php @@ -1,11 +1,9 @@ getMock('Guzzle\\Http\\Message\\Request', array('getCookie'), array('POST', '/', array())); $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue($sessionId)); - $connection->WebSocket = new \StdClass; - $connection->WebSocket->request = $headers; - - $component->onOpen($connection); + $component->onOpen($connection, $headers); $this->assertEquals('world', $connection->Session->get('hello')); } @@ -99,9 +94,6 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { $headers = $this->getMock('Guzzle\Http\Message\Request', array('getCookie'), array('POST', '/', array())); $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null)); - $conn->WebSocket = new \StdClass; - $conn->WebSocket->request = $headers; - return $conn; } @@ -111,21 +103,6 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { $this->_serv->onMessage($this->_conn, $message); } - public function testGetSubProtocolsReturnsArray() { - $mock = $this->getMock('Ratchet\\MessageComponentInterface'); - $comp = new SessionProvider($mock, new NullSessionHandler); - - $this->assertInternalType('array', $comp->getSubProtocols()); - } - - public function testGetSubProtocolsGetFromApp() { - $mock = $this->getMock('Ratchet\WebSocket\Stub\WsMessageComponentInterface'); - $mock->expects($this->once())->method('getSubProtocols')->will($this->returnValue(array('hello', 'world'))); - $comp = new SessionProvider($mock, new NullSessionHandler); - - $this->assertGreaterThanOrEqual(2, count($comp->getSubProtocols())); - } - public function testRejectInvalidSeralizers() { if (!function_exists('wddx_serialize_value')) { $this->markTestSkipped(); From 930130cab3b23b34b4582f0fb34a3623796cca11 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Mon, 17 Feb 2014 11:29:43 -0500 Subject: [PATCH 02/52] [Session] Construct/onOpen use HttpServerInterface --- src/Ratchet/Session/SessionProvider.php | 6 +++--- tests/unit/Session/SessionComponentTest.php | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Ratchet/Session/SessionProvider.php b/src/Ratchet/Session/SessionProvider.php index 445f08a..1315ad9 100644 --- a/src/Ratchet/Session/SessionProvider.php +++ b/src/Ratchet/Session/SessionProvider.php @@ -39,13 +39,13 @@ class SessionProvider implements HttpServerInterface { protected $_serializer; /** - * @param \Ratchet\MessageComponentInterface $app + * @param \Ratchet\HttpServerInterface $app * @param \SessionHandlerInterface $handler * @param array $options * @param \Ratchet\Session\Serialize\HandlerInterface $serializer * @throws \RuntimeException */ - public function __construct(MessageComponentInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { + public function __construct(HttpServerInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { $this->_app = $app; $this->_handler = $handler; $this->_null = new NullSessionHandler; @@ -91,7 +91,7 @@ class SessionProvider implements HttpServerInterface { $conn->Session->start(); } - return $this->_app->onOpen($conn); + return $this->_app->onOpen($conn, $request); } /** diff --git a/tests/unit/Session/SessionComponentTest.php b/tests/unit/Session/SessionComponentTest.php index 4ad69aa..c7df77d 100644 --- a/tests/unit/Session/SessionComponentTest.php +++ b/tests/unit/Session/SessionComponentTest.php @@ -33,7 +33,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { } public function getComponentClassString() { - return '\Ratchet\MessageComponentInterface'; + return '\Ratchet\Http\HttpServerInterface'; } public function classCaseProvider() { @@ -51,7 +51,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { $method = $ref->getMethod('toClassCase'); $method->setAccessible(true); - $component = new SessionProvider($this->getMock('Ratchet\\MessageComponentInterface'), $this->getMock('\SessionHandlerInterface')); + $component = new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); $this->assertEquals($out, $method->invokeArgs($component, array($in))); } @@ -77,7 +77,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { $pdo->exec(vsprintf("CREATE TABLE %s (%s VARCHAR(255) PRIMARY KEY, %s TEXT, %s INTEGER)", $dbOptions)); $pdo->prepare(vsprintf("INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)", $dbOptions))->execute(array($sessionId, base64_encode('_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}'), time())); - $component = new SessionProvider($this->getMock('Ratchet\\MessageComponentInterface'), new PdoSessionHandler($pdo, $dbOptions), array('auto_start' => 1)); + $component = new SessionProvider($this->getMock($this->getComponentClassString()), new PdoSessionHandler($pdo, $dbOptions), array('auto_start' => 1)); $connection = $this->getMock('Ratchet\\ConnectionInterface'); $headers = $this->getMock('Guzzle\\Http\\Message\\Request', array('getCookie'), array('POST', '/', array())); @@ -110,6 +110,6 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { ini_set('session.serialize_handler', 'wddx'); $this->setExpectedException('\RuntimeException'); - new SessionProvider($this->getMock('\Ratchet\MessageComponentInterface'), $this->getMock('\SessionHandlerInterface')); + new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); } } From a744aea1f06b013b9b7c5be49610e22f5f0b930d Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 30 May 2015 10:19:30 -0400 Subject: [PATCH 03/52] PSR-7 + RFC Http components and APIs now use PSR-7 interfaces No longer using deprecated Guzzle dependency Use RFC6455 repo for WebSocket message handling Remove Hixie76 (refs #201) --- Makefile | 3 + composer.json | 3 +- src/Ratchet/Http/CloseResponseTrait.php | 22 + .../Guzzle/Http/Message/RequestFactory.php | 34 -- src/Ratchet/Http/HttpRequestParser.php | 6 +- src/Ratchet/Http/HttpServer.php | 18 +- src/Ratchet/Http/HttpServerInterface.php | 4 +- src/Ratchet/Http/OriginCheck.php | 30 +- src/Ratchet/Http/Router.php | 52 +- src/Ratchet/WebSocket/ConnectionContext.php | 79 +++ .../Encoding/ToggleableValidator.php | 31 -- src/Ratchet/WebSocket/Encoding/Validator.php | 93 ---- .../WebSocket/Encoding/ValidatorInterface.php | 12 - .../WebSocket/Version/DataInterface.php | 28 -- .../WebSocket/Version/FrameInterface.php | 38 -- src/Ratchet/WebSocket/Version/Hixie76.php | 120 ----- .../WebSocket/Version/Hixie76/Connection.php | 26 - .../WebSocket/Version/Hixie76/Frame.php | 86 ---- src/Ratchet/WebSocket/Version/HyBi10.php | 15 - .../WebSocket/Version/MessageInterface.php | 15 - src/Ratchet/WebSocket/Version/RFC6455.php | 271 ----------- .../WebSocket/Version/RFC6455/Frame.php | 451 ------------------ .../Version/RFC6455/HandshakeVerifier.php | 137 ------ .../WebSocket/Version/RFC6455/Message.php | 107 ----- .../WebSocket/Version/VersionInterface.php | 57 --- src/Ratchet/WebSocket/VersionManager.php | 90 ---- .../Connection.php => WsConnection.php} | 7 +- src/Ratchet/WebSocket/WsServer.php | 155 ++---- 28 files changed, 194 insertions(+), 1796 deletions(-) create mode 100644 src/Ratchet/Http/CloseResponseTrait.php delete mode 100644 src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php create mode 100644 src/Ratchet/WebSocket/ConnectionContext.php delete mode 100644 src/Ratchet/WebSocket/Encoding/ToggleableValidator.php delete mode 100644 src/Ratchet/WebSocket/Encoding/Validator.php delete mode 100644 src/Ratchet/WebSocket/Encoding/ValidatorInterface.php delete mode 100644 src/Ratchet/WebSocket/Version/DataInterface.php delete mode 100644 src/Ratchet/WebSocket/Version/FrameInterface.php delete mode 100644 src/Ratchet/WebSocket/Version/Hixie76.php delete mode 100644 src/Ratchet/WebSocket/Version/Hixie76/Connection.php delete mode 100644 src/Ratchet/WebSocket/Version/Hixie76/Frame.php delete mode 100644 src/Ratchet/WebSocket/Version/HyBi10.php delete mode 100644 src/Ratchet/WebSocket/Version/MessageInterface.php delete mode 100644 src/Ratchet/WebSocket/Version/RFC6455.php delete mode 100644 src/Ratchet/WebSocket/Version/RFC6455/Frame.php delete mode 100644 src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php delete mode 100644 src/Ratchet/WebSocket/Version/RFC6455/Message.php delete mode 100644 src/Ratchet/WebSocket/Version/VersionInterface.php delete mode 100644 src/Ratchet/WebSocket/VersionManager.php rename src/Ratchet/WebSocket/{Version/RFC6455/Connection.php => WsConnection.php} (82%) diff --git a/Makefile b/Makefile index 9bd90f0..4ec818f 100644 --- a/Makefile +++ b/Makefile @@ -13,16 +13,19 @@ abtests: ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-noutf8.php 8003 StreamSelect & ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv & wstest -m testeeserver -w ws://localhost:8000 & + sleep 1 wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json killall php wstest abtest: ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect & + sleep 1 wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json killall php profile: php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent & + sleep 1 wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json killall php diff --git a/composer.json b/composer.json index c4a7809..79b22b5 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ , "require": { "php": ">=5.3.9" , "react/socket": "^0.3 || ^0.4" - , "guzzle/http": "^3.6" + , "guzzlehttp/psr7": "^1.0" + , "ratchet/rfc6455": "dev-psr7" , "symfony/http-foundation": "^2.2" , "symfony/routing": "^2.2" } diff --git a/src/Ratchet/Http/CloseResponseTrait.php b/src/Ratchet/Http/CloseResponseTrait.php new file mode 100644 index 0000000..abdf5c4 --- /dev/null +++ b/src/Ratchet/Http/CloseResponseTrait.php @@ -0,0 +1,22 @@ + \Ratchet\VERSION + ], $additional_headers)); + + $conn->send(gPsr\str($response)); + $conn->close(); + } +} \ No newline at end of file diff --git a/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php b/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php deleted file mode 100644 index 8f68e5e..0000000 --- a/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php +++ /dev/null @@ -1,34 +0,0 @@ -entityEnclosingRequestClass; - $request = new $c($method, $url, $headers); - $request->setBody(EntityBody::factory($body)); - - return $request; - } -} diff --git a/src/Ratchet/Http/HttpRequestParser.php b/src/Ratchet/Http/HttpRequestParser.php index cbb5bbd..286a3cb 100644 --- a/src/Ratchet/Http/HttpRequestParser.php +++ b/src/Ratchet/Http/HttpRequestParser.php @@ -2,7 +2,7 @@ namespace Ratchet\Http; use Ratchet\MessageInterface; use Ratchet\ConnectionInterface; -use Ratchet\Http\Guzzle\Http\Message\RequestFactory; +use GuzzleHttp\Psr7 as g7; /** * This class receives streaming data from a client request @@ -22,7 +22,7 @@ class HttpRequestParser implements MessageInterface { /** * @param \Ratchet\ConnectionInterface $context * @param string $data Data stream to buffer - * @return \Guzzle\Http\Message\RequestInterface|null + * @return \Psr\Http\Message\RequestInterface * @throws \OverflowException If the message buffer has become too large */ public function onMessage(ConnectionInterface $context, $data) { @@ -37,7 +37,7 @@ class HttpRequestParser implements MessageInterface { } if ($this->isEom($context->httpBuffer)) { - $request = RequestFactory::getInstance()->fromMessage($context->httpBuffer); + $request = g7\parse_request($context->httpBuffer); unset($context->httpBuffer); diff --git a/src/Ratchet/Http/HttpServer.php b/src/Ratchet/Http/HttpServer.php index f1b8f69..bbd8d53 100644 --- a/src/Ratchet/Http/HttpServer.php +++ b/src/Ratchet/Http/HttpServer.php @@ -2,9 +2,10 @@ namespace Ratchet\Http; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; -use Guzzle\Http\Message\Response; class HttpServer implements MessageComponentInterface { + use CloseResponseTrait; + /** * Buffers incoming HTTP requests returning a Guzzle Request when coalesced * @var HttpRequestParser @@ -72,19 +73,4 @@ class HttpServer implements MessageComponentInterface { $this->close($conn, 500); } } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - * @return null - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } } diff --git a/src/Ratchet/Http/HttpServerInterface.php b/src/Ratchet/Http/HttpServerInterface.php index 79b7d55..2c37c49 100644 --- a/src/Ratchet/Http/HttpServerInterface.php +++ b/src/Ratchet/Http/HttpServerInterface.php @@ -2,12 +2,12 @@ namespace Ratchet\Http; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; -use Guzzle\Http\Message\RequestInterface; +use Psr\Http\Message\RequestInterface; interface HttpServerInterface extends MessageComponentInterface { /** * @param \Ratchet\ConnectionInterface $conn - * @param \Guzzle\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!! + * @param \Psr\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!! * @throws \UnexpectedValueException if a RequestInterface is not passed */ public function onOpen(ConnectionInterface $conn, RequestInterface $request = null); diff --git a/src/Ratchet/Http/OriginCheck.php b/src/Ratchet/Http/OriginCheck.php index 640d3c7..2bdc0f7 100644 --- a/src/Ratchet/Http/OriginCheck.php +++ b/src/Ratchet/Http/OriginCheck.php @@ -1,9 +1,8 @@ _component = $component; $this->allowedOrigins += $allowed; } @@ -31,7 +32,7 @@ class OriginCheck implements HttpServerInterface { * {@inheritdoc} */ public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { - $header = (string)$request->getHeader('Origin'); + $header = (string)$request->getHeader('Origin')[0]; $origin = parse_url($header, PHP_URL_HOST) ?: $header; if (!in_array($origin, $this->allowedOrigins)) { @@ -61,19 +62,4 @@ class OriginCheck implements HttpServerInterface { function onError(ConnectionInterface $conn, \Exception $e) { return $this->_component->onError($conn, $e); } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - * @return null - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } -} +} \ No newline at end of file diff --git a/src/Ratchet/Http/Router.php b/src/Ratchet/Http/Router.php index bfc8193..834115a 100644 --- a/src/Ratchet/Http/Router.php +++ b/src/Ratchet/Http/Router.php @@ -1,14 +1,14 @@ getUri(); + $context = $this->_matcher->getContext(); $context->setMethod($request->getMethod()); - $context->setHost($request->getHost()); + $context->setHost($uri->getHost()); try { - $route = $this->_matcher->match($request->getPath()); + $route = $this->_matcher->match($uri->getPath()); } catch (MethodNotAllowedException $nae) { return $this->close($conn, 403); } catch (ResourceNotFoundException $nfe) { @@ -47,17 +49,18 @@ class Router implements HttpServerInterface { throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); } - $parameters = array(); - foreach($route as $key => $value) { - if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { - $parameters[$key] = $value; - } - } - $parameters = array_merge($parameters, $request->getQuery()->getAll()); - - $url = Url::factory($request->getPath()); - $url->setQuery($parameters); - $request->setUrl($url); +// TODO: Apply Symfony default params to request +// $parameters = []; +// foreach($route as $key => $value) { +// if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { +// $parameters[$key] = $value; +// } +// } +// $parameters = array_merge($parameters, gPsr\parse_query($uri->getQuery())); +// +// $url = Url::factory($request->getPath()); +// $url->setQuery($parameters); +// $request->setUrl($url); $conn->controller = $route['_controller']; $conn->controller->onOpen($conn, $request); @@ -87,19 +90,4 @@ class Router implements HttpServerInterface { $conn->controller->onError($conn, $e); } } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - * @return null - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } -} +} \ No newline at end of file diff --git a/src/Ratchet/WebSocket/ConnectionContext.php b/src/Ratchet/WebSocket/ConnectionContext.php new file mode 100644 index 0000000..92b8ef6 --- /dev/null +++ b/src/Ratchet/WebSocket/ConnectionContext.php @@ -0,0 +1,79 @@ +conn = $conn; + $this->component = $component; + } + + public function detach() { + $conn = $this->conn; + + $this->frame = null; + $this->message = null; + + $this->component = null; + $this->conn = null; + + return $conn; + } + + public function onError(\Exception $e) { + $this->component->onError($this->conn, $e); + } + + public function setFrame(FrameInterface $frame = null) { + $this->frame = $frame; + } + + /** + * @return \Ratchet\RFC6455\Messaging\Protocol\FrameInterface + */ + public function getFrame() { + return $this->frame; + } + + public function setMessage(MessageInterface $message = null) { + $this->message = $message; + } + + /** + * @return \Ratchet\RFC6455\Messaging\Protocol\MessageInterface + */ + public function getMessage() { + return $this->message; + } + + public function onMessage(MessageInterface $msg) { + $this->component->onMessage($this->conn, $msg->getPayload()); + } + + public function onPing(FrameInterface $frame) { + $pong = new \Ratchet\RFC6455\Messaging\Protocol\Frame($frame->getPayload(), true, $frame::OP_PONG); + + $this->conn->send($pong); + } + + public function onPong(FrameInterface $frame) { + // TODO: Implement onPong() method. + } + + /** + * @param $code int + */ + public function onClose($code) { + $this->conn->close($code); + } +} \ No newline at end of file diff --git a/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php b/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php deleted file mode 100644 index edf14bc..0000000 --- a/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php +++ /dev/null @@ -1,31 +0,0 @@ -validator = new Validator; - $this->on = (boolean)$on; - } - - /** - * {@inheritdoc} - */ - public function checkEncoding($str, $encoding) { - if (!(boolean)$this->on) { - return true; - } - - return $this->validator->checkEncoding($str, $encoding); - } -} diff --git a/src/Ratchet/WebSocket/Encoding/Validator.php b/src/Ratchet/WebSocket/Encoding/Validator.php deleted file mode 100644 index 3b02230..0000000 --- a/src/Ratchet/WebSocket/Encoding/Validator.php +++ /dev/null @@ -1,93 +0,0 @@ -hasMbString = extension_loaded('mbstring'); - $this->hasIconv = extension_loaded('iconv'); - } - - /** - * @param string $str The value to check the encoding - * @param string $against The type of encoding to check against - * @return bool - */ - public function checkEncoding($str, $against) { - if ('UTF-8' == $against) { - return $this->isUtf8($str); - } - - if ($this->hasMbString) { - return mb_check_encoding($str, $against); - } elseif ($this->hasIconv) { - return ($str == iconv($against, "{$against}//IGNORE", $str)); - } - - return true; - } - - protected function isUtf8($str) { - if ($this->hasMbString) { - if (false === mb_check_encoding($str, 'UTF-8')) { - return false; - } - } elseif ($this->hasIconv) { - if ($str != iconv('UTF-8', 'UTF-8//IGNORE', $str)) { - return false; - } - } - - $state = static::UTF8_ACCEPT; - - for ($i = 0, $len = strlen($str); $i < $len; $i++) { - $state = static::$dfa[256 + ($state << 4) + static::$dfa[ord($str[$i])]]; - - if (static::UTF8_REJECT === $state) { - return false; - } - } - - return true; - } -} diff --git a/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php b/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php deleted file mode 100644 index 374f220..0000000 --- a/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -getHeader('Sec-WebSocket-Key2')); - } - - /** - * {@inheritdoc} - */ - public function getVersionNumber() { - return 0; - } - - /** - * @param \Guzzle\Http\Message\RequestInterface $request - * @return \Guzzle\Http\Message\Response - * @throws \UnderflowException If there hasn't been enough data received - */ - public function handshake(RequestInterface $request) { - $body = substr($request->getBody(), 0, 8); - if (8 !== strlen($body)) { - throw new \UnderflowException("Not enough data received to issue challenge response"); - } - - $challenge = $this->sign((string)$request->getHeader('Sec-WebSocket-Key1'), (string)$request->getHeader('Sec-WebSocket-Key2'), $body); - - $headers = array( - 'Upgrade' => 'WebSocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Origin' => (string)$request->getHeader('Origin') - , 'Sec-WebSocket-Location' => 'ws://' . (string)$request->getHeader('Host') . $request->getPath() - ); - - $response = new Response(101, $headers, $challenge); - $response->setStatus(101, 'WebSocket Protocol Handshake'); - - return $response; - } - - /** - * {@inheritdoc} - */ - public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { - $upgraded = new Connection($conn); - - if (!isset($upgraded->WebSocket)) { - $upgraded->WebSocket = new \StdClass; - } - - $upgraded->WebSocket->coalescedCallback = $coalescedCallback; - - return $upgraded; - } - - public function onMessage(ConnectionInterface $from, $data) { - $overflow = ''; - - if (!isset($from->WebSocket->frame)) { - $from->WebSocket->frame = $this->newFrame(); - } - - $from->WebSocket->frame->addBuffer($data); - if ($from->WebSocket->frame->isCoalesced()) { - $overflow = $from->WebSocket->frame->extractOverflow(); - - $parsed = $from->WebSocket->frame->getPayload(); - unset($from->WebSocket->frame); - - $from->WebSocket->coalescedCallback->onMessage($from, $parsed); - - unset($from->WebSocket->frame); - } - - if (strlen($overflow) > 0) { - $this->onMessage($from, $overflow); - } - } - - public function newFrame() { - return new Frame; - } - - public function generateKeyNumber($key) { - if (0 === substr_count($key, ' ')) { - return 0; - } - - return preg_replace('[\D]', '', $key) / substr_count($key, ' '); - } - - protected function sign($key1, $key2, $code) { - return md5( - pack('N', $this->generateKeyNumber($key1)) - . pack('N', $this->generateKeyNumber($key2)) - . $code - , true); - } -} diff --git a/src/Ratchet/WebSocket/Version/Hixie76/Connection.php b/src/Ratchet/WebSocket/Version/Hixie76/Connection.php deleted file mode 100644 index e3d0834..0000000 --- a/src/Ratchet/WebSocket/Version/Hixie76/Connection.php +++ /dev/null @@ -1,26 +0,0 @@ -WebSocket->closing) { - $this->getConnection()->send(chr(0) . $msg . chr(255)); - } - - return $this; - } - - public function close() { - if (!$this->WebSocket->closing) { - $this->getConnection()->send(chr(255)); - $this->getConnection()->close(); - - $this->WebSocket->closing = true; - } - } -} diff --git a/src/Ratchet/WebSocket/Version/Hixie76/Frame.php b/src/Ratchet/WebSocket/Version/Hixie76/Frame.php deleted file mode 100644 index 28eb90e..0000000 --- a/src/Ratchet/WebSocket/Version/Hixie76/Frame.php +++ /dev/null @@ -1,86 +0,0 @@ -_data[0] == chr(0) && substr($this->_data, -1) == chr(255)); - } - - /** - * {@inheritdoc} - */ - public function addBuffer($buf) { - $this->_data .= (string)$buf; - } - - /** - * {@inheritdoc} - */ - public function isFinal() { - return true; - } - - /** - * {@inheritdoc} - */ - public function isMasked() { - return false; - } - - /** - * {@inheritdoc} - */ - public function getOpcode() { - return 1; - } - - /** - * {@inheritdoc} - */ - public function getPayloadLength() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Not enough of the message has been buffered to determine the length of the payload'); - } - - return strlen($this->_data) - 2; - } - - /** - * {@inheritdoc} - */ - public function getMaskingKey() { - return ''; - } - - /** - * {@inheritdoc} - */ - public function getPayload() { - if (!$this->isCoalesced()) { - return new \UnderflowException('Not enough data buffered to read payload'); - } - - return substr($this->_data, 1, strlen($this->_data) - 2); - } - - public function getContents() { - return $this->_data; - } - - public function extractOverflow() { - return ''; - } -} diff --git a/src/Ratchet/WebSocket/Version/HyBi10.php b/src/Ratchet/WebSocket/Version/HyBi10.php deleted file mode 100644 index a53d338..0000000 --- a/src/Ratchet/WebSocket/Version/HyBi10.php +++ /dev/null @@ -1,15 +0,0 @@ -getHeader('Sec-WebSocket-Version'); - - return ($version >= 6 && $version < 13); - } - - public function getVersionNumber() { - return 6; - } -} diff --git a/src/Ratchet/WebSocket/Version/MessageInterface.php b/src/Ratchet/WebSocket/Version/MessageInterface.php deleted file mode 100644 index 476c091..0000000 --- a/src/Ratchet/WebSocket/Version/MessageInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -_verifier = new HandshakeVerifier; - $this->setCloseCodes(); - - if (null === $validator) { - $validator = new Validator; - } - - $this->validator = $validator; - } - - /** - * {@inheritdoc} - */ - public function isProtocol(RequestInterface $request) { - $version = (int)(string)$request->getHeader('Sec-WebSocket-Version'); - - return ($this->getVersionNumber() === $version); - } - - /** - * {@inheritdoc} - */ - public function getVersionNumber() { - return 13; - } - - /** - * {@inheritdoc} - */ - public function handshake(RequestInterface $request) { - if (true !== $this->_verifier->verifyAll($request)) { - return new Response(400); - } - - return new Response(101, array( - 'Upgrade' => 'websocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')) - )); - } - - /** - * @param \Ratchet\ConnectionInterface $conn - * @param \Ratchet\MessageInterface $coalescedCallback - * @return \Ratchet\WebSocket\Version\RFC6455\Connection - */ - public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { - $upgraded = new Connection($conn); - - if (!isset($upgraded->WebSocket)) { - $upgraded->WebSocket = new \StdClass; - } - - $upgraded->WebSocket->coalescedCallback = $coalescedCallback; - - return $upgraded; - } - - /** - * @param \Ratchet\WebSocket\Version\RFC6455\Connection $from - * @param string $data - */ - public function onMessage(ConnectionInterface $from, $data) { - $overflow = ''; - - if (!isset($from->WebSocket->message)) { - $from->WebSocket->message = $this->newMessage(); - } - - // There is a frame fragment attached to the connection, add to it - if (!isset($from->WebSocket->frame)) { - $from->WebSocket->frame = $this->newFrame(); - } - - $from->WebSocket->frame->addBuffer($data); - if ($from->WebSocket->frame->isCoalesced()) { - $frame = $from->WebSocket->frame; - - if (false !== $frame->getRsv1() || - false !== $frame->getRsv2() || - false !== $frame->getRsv3() - ) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - if (!$frame->isMasked()) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - $opcode = $frame->getOpcode(); - - if ($opcode > 2) { - if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - switch ($opcode) { - case $frame::OP_CLOSE: - $closeCode = 0; - - $bin = $frame->getPayload(); - - if (empty($bin)) { - return $from->close(); - } - - if (strlen($bin) >= 2) { - list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2))); - } - - if (!$this->isValidCloseCode($closeCode)) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) { - return $from->close($frame::CLOSE_BAD_PAYLOAD); - } - - return $from->close($frame); - break; - case $frame::OP_PING: - $from->send($this->newFrame($frame->getPayload(), true, $frame::OP_PONG)); - break; - case $frame::OP_PONG: - break; - default: - return $from->close($frame::CLOSE_PROTOCOL); - break; - } - - $overflow = $from->WebSocket->frame->extractOverflow(); - - unset($from->WebSocket->frame, $frame, $opcode); - - if (strlen($overflow) > 0) { - $this->onMessage($from, $overflow); - } - - return; - } - - $overflow = $from->WebSocket->frame->extractOverflow(); - - if ($frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($from->WebSocket->message)) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - if (count($from->WebSocket->message) > 0 && $frame::OP_CONTINUE != $frame->getOpcode()) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - $from->WebSocket->message->addFrame($from->WebSocket->frame); - unset($from->WebSocket->frame); - } - - if ($from->WebSocket->message->isCoalesced()) { - $parsed = $from->WebSocket->message->getPayload(); - unset($from->WebSocket->message); - - if (!$this->validator->checkEncoding($parsed, 'UTF-8')) { - return $from->close(Frame::CLOSE_BAD_PAYLOAD); - } - - $from->WebSocket->coalescedCallback->onMessage($from, $parsed); - } - - if (strlen($overflow) > 0) { - $this->onMessage($from, $overflow); - } - } - - /** - * @return RFC6455\Message - */ - public function newMessage() { - return new Message; - } - - /** - * @param string|null $payload - * @param bool|null $final - * @param int|null $opcode - * @return RFC6455\Frame - */ - public function newFrame($payload = null, $final = null, $opcode = null) { - return new Frame($payload, $final, $opcode); - } - - /** - * Used when doing the handshake to encode the key, verifying client/server are speaking the same language - * @param string $key - * @return string - * @internal - */ - public function sign($key) { - return base64_encode(sha1($key . static::GUID, true)); - } - - /** - * Determine if a close code is valid - * @param int|string - * @return bool - */ - public function isValidCloseCode($val) { - if (array_key_exists($val, $this->closeCodes)) { - return true; - } - - if ($val >= 3000 && $val <= 4999) { - return true; - } - - return false; - } - - /** - * Creates a private lookup of valid, private close codes - */ - protected function setCloseCodes() { - $this->closeCodes[Frame::CLOSE_NORMAL] = true; - $this->closeCodes[Frame::CLOSE_GOING_AWAY] = true; - $this->closeCodes[Frame::CLOSE_PROTOCOL] = true; - $this->closeCodes[Frame::CLOSE_BAD_DATA] = true; - //$this->closeCodes[Frame::CLOSE_NO_STATUS] = true; - //$this->closeCodes[Frame::CLOSE_ABNORMAL] = true; - $this->closeCodes[Frame::CLOSE_BAD_PAYLOAD] = true; - $this->closeCodes[Frame::CLOSE_POLICY] = true; - $this->closeCodes[Frame::CLOSE_TOO_BIG] = true; - $this->closeCodes[Frame::CLOSE_MAND_EXT] = true; - $this->closeCodes[Frame::CLOSE_SRV_ERR] = true; - //$this->closeCodes[Frame::CLOSE_TLS] = true; - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/Frame.php b/src/Ratchet/WebSocket/Version/RFC6455/Frame.php deleted file mode 100644 index 77d1258..0000000 --- a/src/Ratchet/WebSocket/Version/RFC6455/Frame.php +++ /dev/null @@ -1,451 +0,0 @@ -defPayLen = strlen($payload); - $this->firstByte = ($final ? 128 : 0) + $opcode; - $this->secondByte = $this->defPayLen; - $this->isCoalesced = true; - - $ext = ''; - if ($this->defPayLen > 65535) { - $ext = pack('NN', 0, $this->defPayLen); - $this->secondByte = 127; - } elseif ($this->defPayLen > 125) { - $ext = pack('n', $this->defPayLen); - $this->secondByte = 126; - } - - $this->data = chr($this->firstByte) . chr($this->secondByte) . $ext . $payload; - $this->bytesRecvd = 2 + strlen($ext) + $this->defPayLen; - } - - /** - * {@inheritdoc} - */ - public function isCoalesced() { - if (true === $this->isCoalesced) { - return true; - } - - try { - $payload_length = $this->getPayloadLength(); - $payload_start = $this->getPayloadStartingByte(); - } catch (\UnderflowException $e) { - return false; - } - - $this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start; - - return $this->isCoalesced; - } - - /** - * {@inheritdoc} - */ - public function addBuffer($buf) { - $len = strlen($buf); - - $this->data .= $buf; - $this->bytesRecvd += $len; - - if ($this->firstByte === -1 && $this->bytesRecvd !== 0) { - $this->firstByte = ord($this->data[0]); - } - - if ($this->secondByte === -1 && $this->bytesRecvd >= 2) { - $this->secondByte = ord($this->data[1]); - } - } - - /** - * {@inheritdoc} - */ - public function isFinal() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message'); - } - - return 128 === ($this->firstByte & 128); - } - - /** - * @return boolean - * @throws \UnderflowException - */ - public function getRsv1() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine reserved bit'); - } - - return 64 === ($this->firstByte & 64); - } - - /** - * @return boolean - * @throws \UnderflowException - */ - public function getRsv2() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine reserved bit'); - } - - return 32 === ($this->firstByte & 32); - } - - /** - * @return boolean - * @throws \UnderflowException - */ - public function getRsv3() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine reserved bit'); - } - - return 16 == ($this->firstByte & 16); - } - - /** - * {@inheritdoc} - */ - public function isMasked() { - if (-1 === $this->secondByte) { - throw new \UnderflowException("Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); - } - - return 128 === ($this->secondByte & 128); - } - - /** - * {@inheritdoc} - */ - public function getMaskingKey() { - if (!$this->isMasked()) { - return ''; - } - - $start = 1 + $this->getNumPayloadBytes(); - - if ($this->bytesRecvd < $start + static::MASK_LENGTH) { - throw new \UnderflowException('Not enough data buffered to calculate the masking key'); - } - - return substr($this->data, $start, static::MASK_LENGTH); - } - - /** - * Create a 4 byte masking key - * @return string - */ - public function generateMaskingKey() { - $mask = ''; - - for ($i = 1; $i <= static::MASK_LENGTH; $i++) { - $mask .= chr(rand(32, 126)); - } - - return $mask; - } - - /** - * Apply a mask to the payload - * @param string|null If NULL is passed a masking key will be generated - * @throws \OutOfBoundsException - * @throws \InvalidArgumentException If there is an issue with the given masking key - * @return Frame - */ - public function maskPayload($maskingKey = null) { - if (null === $maskingKey) { - $maskingKey = $this->generateMaskingKey(); - } - - if (static::MASK_LENGTH !== strlen($maskingKey)) { - throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters"); - } - - if (extension_loaded('mbstring') && true !== mb_check_encoding($maskingKey, 'US-ASCII')) { - throw new \OutOfBoundsException("Masking key MUST be ASCII"); - } - - $this->unMaskPayload(); - - $this->secondByte = $this->secondByte | 128; - $this->data[1] = chr($this->secondByte); - - $this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0); - - $this->bytesRecvd += static::MASK_LENGTH; - $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); - - return $this; - } - - /** - * Remove a mask from the payload - * @throws \UnderFlowException If the frame is not coalesced - * @return Frame - */ - public function unMaskPayload() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Frame must be coalesced before applying mask'); - } - - if (!$this->isMasked()) { - return $this; - } - - $maskingKey = $this->getMaskingKey(); - - $this->secondByte = $this->secondByte & ~128; - $this->data[1] = chr($this->secondByte); - - $this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH); - - $this->bytesRecvd -= static::MASK_LENGTH; - $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); - - return $this; - } - - /** - * Apply a mask to a string or the payload of the instance - * @param string $maskingKey The 4 character masking key to be applied - * @param string|null $payload A string to mask or null to use the payload - * @throws \UnderflowException If using the payload but enough hasn't been buffered - * @return string The masked string - */ - public function applyMask($maskingKey, $payload = null) { - if (null === $payload) { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Frame must be coalesced to apply a mask'); - } - - $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); - } - - $applied = ''; - for ($i = 0, $len = strlen($payload); $i < $len; $i++) { - $applied .= $payload[$i] ^ $maskingKey[$i % static::MASK_LENGTH]; - } - - return $applied; - } - - /** - * {@inheritdoc} - */ - public function getOpcode() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine opcode'); - } - - return ($this->firstByte & ~240); - } - - /** - * Gets the decimal value of bits 9 (10th) through 15 inclusive - * @return int - * @throws \UnderflowException If the buffer doesn't have enough data to determine this - */ - protected function getFirstPayloadVal() { - if (-1 === $this->secondByte) { - throw new \UnderflowException('Not enough bytes received'); - } - - return $this->secondByte & 127; - } - - /** - * @return int (7|23|71) Number of bits defined for the payload length in the fame - * @throws \UnderflowException - */ - protected function getNumPayloadBits() { - if (-1 === $this->secondByte) { - throw new \UnderflowException('Not enough bytes received'); - } - - // By default 7 bits are used to describe the payload length - // These are bits 9 (10th) through 15 inclusive - $bits = 7; - - // Get the value of those bits - $check = $this->getFirstPayloadVal(); - - // If the value is 126 the 7 bits plus the next 16 are used to describe the payload length - if ($check >= 126) { - $bits += 16; - } - - // If the value of the initial payload length are is 127 an additional 48 bits are used to describe length - // Note: The documentation specifies the length is to be 63 bits, but I think that's a typo and is 64 (16+48) - if ($check === 127) { - $bits += 48; - } - - return $bits; - } - - /** - * This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits) - * @see getNumPayloadBits - */ - protected function getNumPayloadBytes() { - return (1 + $this->getNumPayloadBits()) / 8; - } - - /** - * {@inheritdoc} - */ - public function getPayloadLength() { - if ($this->defPayLen !== -1) { - return $this->defPayLen; - } - - $this->defPayLen = $this->getFirstPayloadVal(); - if ($this->defPayLen <= 125) { - return $this->getPayloadLength(); - } - - $byte_length = $this->getNumPayloadBytes(); - if ($this->bytesRecvd < 1 + $byte_length) { - $this->defPayLen = -1; - throw new \UnderflowException('Not enough data buffered to determine payload length'); - } - - $len = 0; - for ($i = 2; $i <= $byte_length; $i++) { - $len <<= 8; - $len += ord($this->data[$i]); - } - - $this->defPayLen = $len; - - return $this->getPayloadLength(); - } - - /** - * {@inheritdoc} - */ - public function getPayloadStartingByte() { - return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); - } - - /** - * {@inheritdoc} - * @todo Consider not checking mask, always returning the payload, masked or not - */ - public function getPayload() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Can not return partial message'); - } - - $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); - - if ($this->isMasked()) { - $payload = $this->applyMask($this->getMaskingKey(), $payload); - } - - return $payload; - } - - /** - * Get the raw contents of the frame - * @todo This is untested, make sure the substr is right - trying to return the frame w/o the overflow - */ - public function getContents() { - return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); - } - - /** - * Sometimes clients will concatenate more than one frame over the wire - * This method will take the extra bytes off the end and return them - * @todo Consider returning new Frame - * @return string - */ - public function extractOverflow() { - if ($this->isCoalesced()) { - $endPoint = $this->getPayloadLength(); - $endPoint += $this->getPayloadStartingByte(); - - if ($this->bytesRecvd > $endPoint) { - $overflow = substr($this->data, $endPoint); - $this->data = substr($this->data, 0, $endPoint); - - return $overflow; - } - } - - return ''; - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php b/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php deleted file mode 100644 index fd783f6..0000000 --- a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php +++ /dev/null @@ -1,137 +0,0 @@ -verifyMethod($request->getMethod()); - $passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion()); - $passes += (int)$this->verifyRequestURI($request->getPath()); - $passes += (int)$this->verifyHost((string)$request->getHeader('Host')); - $passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade')); - $passes += (int)$this->verifyConnection((string)$request->getHeader('Connection')); - $passes += (int)$this->verifyKey((string)$request->getHeader('Sec-WebSocket-Key')); - //$passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']); // Temporarily breaking functionality - - return (7 === $passes); - } - - /** - * Test the HTTP method. MUST be "GET" - * @param string - * @return bool - */ - public function verifyMethod($val) { - return ('get' === strtolower($val)); - } - - /** - * Test the HTTP version passed. MUST be 1.1 or greater - * @param string|int - * @return bool - */ - public function verifyHTTPVersion($val) { - return (1.1 <= (double)$val); - } - - /** - * @param string - * @return bool - */ - public function verifyRequestURI($val) { - if ($val[0] != '/') { - return false; - } - - if (false !== strstr($val, '#')) { - return false; - } - - if (!extension_loaded('mbstring')) { - return true; - } - - return mb_check_encoding($val, 'US-ASCII'); - } - - /** - * @param string|null - * @return bool - * @todo Find out if I can find the master socket, ensure the port is attached to header if not 80 or 443 - not sure if this is possible, as I tried to hide it - * @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ? - */ - public function verifyHost($val) { - return (null !== $val); - } - - /** - * Verify the Upgrade request to WebSockets. - * @param string $val MUST equal "websocket" - * @return bool - */ - public function verifyUpgradeRequest($val) { - return ('websocket' === strtolower($val)); - } - - /** - * Verify the Connection header - * @param string $val MUST equal "Upgrade" - * @return bool - */ - public function verifyConnection($val) { - $val = strtolower($val); - - if ('upgrade' === $val) { - return true; - } - - $vals = explode(',', str_replace(', ', ',', $val)); - - return (false !== array_search('upgrade', $vals)); - } - - /** - * This function verifies the nonce is valid (64 big encoded, 16 bytes random string) - * @param string|null - * @return bool - * @todo The spec says we don't need to base64_decode - can I just check if the length is 24 and not decode? - * @todo Check the spec to see what the encoding of the key could be - */ - public function verifyKey($val) { - return (16 === strlen(base64_decode((string)$val))); - } - - /** - * Verify the version passed matches this RFC - * @param string|int MUST equal 13|"13" - * @return bool - * @todo Ran in to a problem here...I'm having HyBi use the RFC files, this breaks it! oops - */ - public function verifyVersion($val) { - return (13 === (int)$val); - } - - /** - * @todo Write logic for this method. See section 4.2.1.8 - */ - public function verifyProtocol($val) { - } - - /** - * @todo Write logic for this method. See section 4.2.1.9 - */ - public function verifyExtensions($val) { - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/Message.php b/src/Ratchet/WebSocket/Version/RFC6455/Message.php deleted file mode 100644 index a839f2d..0000000 --- a/src/Ratchet/WebSocket/Version/RFC6455/Message.php +++ /dev/null @@ -1,107 +0,0 @@ -_frames = new \SplDoublyLinkedList; - } - - /** - * {@inheritdoc} - */ - public function count() { - return count($this->_frames); - } - - /** - * {@inheritdoc} - */ - public function isCoalesced() { - if (count($this->_frames) == 0) { - return false; - } - - $last = $this->_frames->top(); - - return ($last->isCoalesced() && $last->isFinal()); - } - - /** - * {@inheritdoc} - * @todo Also, I should perhaps check the type...control frames (ping/pong/close) are not to be considered part of a message - */ - public function addFrame(FrameInterface $fragment) { - $this->_frames->push($fragment); - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getOpcode() { - if (count($this->_frames) == 0) { - throw new \UnderflowException('No frames have been added to this message'); - } - - return $this->_frames->bottom()->getOpcode(); - } - - /** - * {@inheritdoc} - */ - public function getPayloadLength() { - $len = 0; - - foreach ($this->_frames as $frame) { - try { - $len += $frame->getPayloadLength(); - } catch (\UnderflowException $e) { - // Not an error, want the current amount buffered - } - } - - return $len; - } - - /** - * {@inheritdoc} - */ - public function getPayload() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Message has not been put back together yet'); - } - - $buffer = ''; - - foreach ($this->_frames as $frame) { - $buffer .= $frame->getPayload(); - } - - return $buffer; - } - - /** - * {@inheritdoc} - */ - public function getContents() { - if (!$this->isCoalesced()) { - throw new \UnderflowException("Message has not been put back together yet"); - } - - $buffer = ''; - - foreach ($this->_frames as $frame) { - $buffer .= $frame->getContents(); - } - - return $buffer; - } -} diff --git a/src/Ratchet/WebSocket/Version/VersionInterface.php b/src/Ratchet/WebSocket/Version/VersionInterface.php deleted file mode 100644 index 5bbe534..0000000 --- a/src/Ratchet/WebSocket/Version/VersionInterface.php +++ /dev/null @@ -1,57 +0,0 @@ -versions as $version) { - if ($version->isProtocol($request)) { - return $version; - } - } - - throw new \InvalidArgumentException("Version not found"); - } - - /** - * @param \Guzzle\Http\Message\RequestInterface - * @return bool - */ - public function isVersionEnabled(RequestInterface $request) { - foreach ($this->versions as $version) { - if ($version->isProtocol($request)) { - return true; - } - } - - return false; - } - - /** - * Enable support for a specific version of the WebSocket protocol - * @param \Ratchet\WebSocket\Version\VersionInterface $version - * @return VersionManager - */ - public function enableVersion(VersionInterface $version) { - $this->versions[$version->getVersionNumber()] = $version; - - if (empty($this->versionString)) { - $this->versionString = (string)$version->getVersionNumber(); - } else { - $this->versionString .= ", {$version->getVersionNumber()}"; - } - - return $this; - } - - /** - * Disable support for a specific WebSocket protocol version - * @param int $versionId The version ID to un-support - * @return VersionManager - */ - public function disableVersion($versionId) { - unset($this->versions[$versionId]); - - $this->versionString = implode(',', array_keys($this->versions)); - - return $this; - } - - /** - * Get a string of version numbers supported (comma delimited) - * @return string - */ - public function getSupportedVersionString() { - return $this->versionString; - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/Connection.php b/src/Ratchet/WebSocket/WsConnection.php similarity index 82% rename from src/Ratchet/WebSocket/Version/RFC6455/Connection.php rename to src/Ratchet/WebSocket/WsConnection.php index a17e382..a4adce2 100644 --- a/src/Ratchet/WebSocket/Version/RFC6455/Connection.php +++ b/src/Ratchet/WebSocket/WsConnection.php @@ -1,13 +1,14 @@ versioner = new VersionManager; - $this->validator = new ToggleableValidator; - - $this->versioner - ->enableVersion(new Version\RFC6455($this->validator)) - ->enableVersion(new Version\HyBi10($this->validator)) - ->enableVersion(new Version\Hixie76) - ; - $this->component = $component; $this->connections = new \SplObjectStorage; + + $encodingValidator = new \Ratchet\RFC6455\Encoding\Validator; + $this->handshakeNegotiator = new \Ratchet\RFC6455\Handshake\Negotiator($encodingValidator); + $this->messageStreamer = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($encodingValidator); } /** @@ -76,12 +62,33 @@ class WsServer implements HttpServerInterface { throw new \UnexpectedValueException('$request can not be null'); } - $conn->WebSocket = new \StdClass; - $conn->WebSocket->request = $request; - $conn->WebSocket->established = false; - $conn->WebSocket->closing = false; + $conn->httpRequest = $request; // This will replace ->WebSocket->request - $this->attemptUpgrade($conn); + $conn->WebSocket = new \StdClass; + $conn->WebSocket->closing = false; + $conn->WebSocket->request = $request; // deprecated + + $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); + +// Probably moved to RFC lib +// $subHeader = $conn->WebSocket->request->getHeader('Sec-WebSocket-Protocol'); +// if (count($subHeader) > 0) { +// if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader))) { +// $response = $response->withHeader('Sec-WebSocket-Protocol', $agreedSubProtocols); +// } +// } + + $conn->send(gPsr\str($response)); + + if (101 != $response->getStatusCode()) { + return $conn->close(); + } + + $wsConn = new WsConnection($conn); + $context = new ConnectionContext($wsConn, $this->component); + $this->connections->attach($conn, $context); + + return $this->component->onOpen($wsConn); } /** @@ -92,50 +99,9 @@ class WsServer implements HttpServerInterface { return; } - if (true === $from->WebSocket->established) { - return $from->WebSocket->version->onMessage($this->connections[$from], $msg); - } + $context = $this->connections[$from]; - $this->attemptUpgrade($from, $msg); - } - - protected function attemptUpgrade(ConnectionInterface $conn, $data = '') { - if ('' !== $data) { - $conn->WebSocket->request->getBody()->write($data); - } else { - if (!$this->versioner->isVersionEnabled($conn->WebSocket->request)) { - return $this->close($conn); - } - - $conn->WebSocket->version = $this->versioner->getVersion($conn->WebSocket->request); - } - - try { - $response = $conn->WebSocket->version->handshake($conn->WebSocket->request); - } catch (\UnderflowException $e) { - return; - } - - if (null !== ($subHeader = $conn->WebSocket->request->getHeader('Sec-WebSocket-Protocol'))) { - if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader->normalize()))) { - $response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols); - } - } - - $response->setHeader('X-Powered-By', \Ratchet\VERSION); - $conn->send((string)$response); - - if (101 != $response->getStatusCode()) { - return $conn->close(); - } - - $upgraded = $conn->WebSocket->version->upgradeConnection($conn, $this->component); - - $this->connections->attach($conn, $upgraded); - - $upgraded->WebSocket->established = true; - - return $this->component->onOpen($upgraded); + $this->messageStreamer->onData($msg, $context); } /** @@ -146,7 +112,9 @@ class WsServer implements HttpServerInterface { $decor = $this->connections[$conn]; $this->connections->detach($conn); - $this->component->onClose($decor); + $conn = $decor->detach(); + + $this->component->onClose($conn); } } @@ -154,31 +122,21 @@ class WsServer implements HttpServerInterface { * {@inheritdoc} */ public function onError(ConnectionInterface $conn, \Exception $e) { - if ($conn->WebSocket->established && $this->connections->contains($conn)) { - $this->component->onError($this->connections[$conn], $e); + if ($this->connections->contains($conn)) { + $context = $this->connections[$conn]; + $context->onError($e); } else { $conn->close(); } } - /** - * Disable a specific version of the WebSocket protocol - * @param int $versionId Version ID to disable - * @return WsServer - */ - public function disableVersion($versionId) { - $this->versioner->disableVersion($versionId); - - return $this; - } - /** * Toggle weather to check encoding of incoming messages * @param bool * @return WsServer */ public function setEncodingChecks($opt) { - $this->validator->on = (boolean)$opt; +// $this->validator->on = (boolean)$opt; return $this; } @@ -214,19 +172,4 @@ class WsServer implements HttpServerInterface { return ''; } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'Sec-WebSocket-Version' => $this->versioner->getSupportedVersionString() - , 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } -} +} \ No newline at end of file From d2384e15e6c760445d39410e984362f0398fb56b Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 30 May 2015 11:02:07 -0400 Subject: [PATCH 04/52] Merge with 0.4 branch + session to PSR-7 --- src/Ratchet/Session/SessionProvider.php | 98 ++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/src/Ratchet/Session/SessionProvider.php b/src/Ratchet/Session/SessionProvider.php index 043b3ab..392a13d 100644 --- a/src/Ratchet/Session/SessionProvider.php +++ b/src/Ratchet/Session/SessionProvider.php @@ -1,9 +1,8 @@ getCookie(ini_get('session.name')))) { + $sessionName = ini_get('session.name'); + + $id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) { + if ($accumulator) { + return $accumulator; + } + + $crumbs = $this->parseCookie($cookie); + + return isset($crumbs[$sessionName]) ? $crumbs[$sessionName] : false; + }, false); + + if (null === $request || false === $id) { $saveHandler = $this->_null; $id = ''; } else { @@ -148,4 +159,85 @@ class SessionProvider implements HttpServerInterface { protected function toClassCase($langDef) { return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef))); } + + /** + * Taken from Guzzle3 + */ + private static $cookieParts = array( + 'domain' => 'Domain', + 'path' => 'Path', + 'max_age' => 'Max-Age', + 'expires' => 'Expires', + 'version' => 'Version', + 'secure' => 'Secure', + 'port' => 'Port', + 'discard' => 'Discard', + 'comment' => 'Comment', + 'comment_url' => 'Comment-Url', + 'http_only' => 'HttpOnly' + ); + + /** + * Taken from Guzzle3 + */ + private function parseCookie($cookie, $host = null, $path = null, $decode = false) { + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return false; + } + + // Create the default return array + $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array( + 'cookies' => array(), + 'data' => array(), + 'path' => $path ?: '/', + 'http_only' => false, + 'discard' => false, + 'domain' => $host + )); + $foundNonCookies = 0; + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + + if (count($cookieParts) == 1) { + // Can be a single value (e.g. secure, httpOnly) + $value = true; + } else { + // Be sure to strip wrapping quotes + $value = trim($cookieParts[1], " \n\r\t\0\x0B\""); + if ($decode) { + $value = urldecode($value); + } + } + + // Only check for non-cookies when cookies have been found + if (!empty($data['cookies'])) { + foreach (self::$cookieParts as $mapValue => $search) { + if (!strcasecmp($search, $key)) { + $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value; + $foundNonCookies++; + continue 2; + } + } + } + + // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a + // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data. + $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value; + } + + // Calculate the expires date + if (!$data['expires'] && $data['max_age']) { + $data['expires'] = time() + (int) $data['max_age']; + } + + return $data; + } } From d0d7b67ad7d72bffdbceca6ed4263ccff4157517 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 31 May 2015 13:54:43 -0400 Subject: [PATCH 05/52] Ues pecl_http if available, cleanup pecl_http if available to parse HTTP requests (5x faster) Update ConnectionContext to match latest RFC interface Removed Guzzle integration test (now using PSR-7 API) --- composer.json | 5 +- src/Ratchet/Http/HttpRequestParser.php | 26 ++++++- src/Ratchet/WebSocket/ConnectionContext.php | 7 +- .../Http/Message/RequestFactoryTest.php | 67 ------------------- 4 files changed, 33 insertions(+), 72 deletions(-) delete mode 100644 tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php diff --git a/composer.json b/composer.json index 79b22b5..72a8283 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "cboden/ratchet" , "type": "library" , "description": "PHP WebSocket library" - , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets"] + , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets", "WebSocket"] , "homepage": "http://socketo.me" , "license": "MIT" , "authors": [ @@ -22,6 +22,9 @@ "Ratchet\\": "src/Ratchet" } } + , "suggest": { + "ext-pecl_http": "^2.0" + } , "require": { "php": ">=5.3.9" , "react/socket": "^0.3 || ^0.4" diff --git a/src/Ratchet/Http/HttpRequestParser.php b/src/Ratchet/Http/HttpRequestParser.php index 286a3cb..25bf489 100644 --- a/src/Ratchet/Http/HttpRequestParser.php +++ b/src/Ratchet/Http/HttpRequestParser.php @@ -2,11 +2,11 @@ namespace Ratchet\Http; use Ratchet\MessageInterface; use Ratchet\ConnectionInterface; -use GuzzleHttp\Psr7 as g7; +use GuzzleHttp\Psr7 as gPsr; /** * This class receives streaming data from a client request - * and parses HTTP headers, returning a Guzzle Request object + * and parses HTTP headers, returning a PSR-7 Request object * once it's been buffered */ class HttpRequestParser implements MessageInterface { @@ -37,7 +37,7 @@ class HttpRequestParser implements MessageInterface { } if ($this->isEom($context->httpBuffer)) { - $request = g7\parse_request($context->httpBuffer); + $request = $this->parse($context->httpBuffer); unset($context->httpBuffer); @@ -53,4 +53,24 @@ class HttpRequestParser implements MessageInterface { public function isEom($message) { return (boolean)strpos($message, static::EOM); } + + /** + * @param string $headers + * @return \Psr\Http\Message\RequestInterface + */ + public function parse($headers) { + if (function_exists('http_parse_message')) { + $parts = http_parse_message($headers); + + return new gPsr\Request( + $parts->requestMethod + , $parts->requestUrl + , $parts->headers + , null + , $parts->httpVersion + ); + } else { + return gPsr\parse_request($headers); + } + } } diff --git a/src/Ratchet/WebSocket/ConnectionContext.php b/src/Ratchet/WebSocket/ConnectionContext.php index 92b8ef6..20fb58b 100644 --- a/src/Ratchet/WebSocket/ConnectionContext.php +++ b/src/Ratchet/WebSocket/ConnectionContext.php @@ -2,6 +2,7 @@ namespace Ratchet\WebSocket; use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; +use Ratchet\RFC6455\Messaging\Protocol\Frame; use Ratchet\RFC6455\Messaging\Protocol\FrameInterface; use Ratchet\RFC6455\Messaging\Protocol\MessageInterface; use Ratchet\RFC6455\Messaging\Streaming\ContextInterface; @@ -36,6 +37,8 @@ class ConnectionContext implements ContextInterface { public function setFrame(FrameInterface $frame = null) { $this->frame = $frame; + + return $frame; } /** @@ -47,6 +50,8 @@ class ConnectionContext implements ContextInterface { public function setMessage(MessageInterface $message = null) { $this->message = $message; + + return $message; } /** @@ -61,7 +66,7 @@ class ConnectionContext implements ContextInterface { } public function onPing(FrameInterface $frame) { - $pong = new \Ratchet\RFC6455\Messaging\Protocol\Frame($frame->getPayload(), true, $frame::OP_PONG); + $pong = new Frame($frame->getPayload(), true, Frame::OP_PONG); $this->conn->send($pong); } diff --git a/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php b/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php deleted file mode 100644 index 7860f72..0000000 --- a/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php +++ /dev/null @@ -1,67 +0,0 @@ -factory = RequestFactory::getInstance(); - } - - public function testMessageProvider() { - return array( - 'status' => 'GET / HTTP/1.1' - , 'headers' => array( - 'Upgrade' => 'WebSocket' - , 'Connection' => 'Upgrade' - , 'Host' => 'localhost:8000' - , 'Sec-WebSocket-Key1' => '> b3lU Z0 fh f 3+83394 6 (zG4' - , 'Sec-WebSocket-Key2' => ',3Z0X0677 dV-d [159 Z*4' - ) - , 'body' => "123456\r\n\r\n" - ); - } - - public function combineMessage($status, array $headers, $body = '') { - $message = $status . "\r\n"; - - foreach ($headers as $key => $val) { - $message .= "{$key}: {$val}\r\n"; - } - - $message .= "\r\n{$body}"; - - return $message; - } - - public function testExpectedDataFromGuzzleHeaders() { - $parts = $this->testMessageProvider(); - $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); - $object = $this->factory->fromMessage($message); - - foreach ($parts['headers'] as $key => $val) { - $this->assertEquals($val, $object->getHeader($key, true)); - } - } - - public function testExpectedDataFromNonGuzzleHeaders() { - $parts = $this->testMessageProvider(); - $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); - $object = $this->factory->fromMessage($message); - - $this->assertNull($object->getHeader('Nope', true)); - $this->assertNull($object->getHeader('Nope')); - } - - public function testExpectedDataFromNonGuzzleBody() { - $parts = $this->testMessageProvider(); - $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); - $object = $this->factory->fromMessage($message); - - $this->assertEquals($parts['body'], (string)$object->getBody()); - } -} From 46487e756c527cc7e423619a7e1ff3e0806106f3 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 6 Jun 2015 10:00:55 -0400 Subject: [PATCH 06/52] Fixed sub-protocol negotiation support --- src/Ratchet/WebSocket/WsServer.php | 44 ++++-------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index f9ca425..8f71538 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -52,6 +52,10 @@ class WsServer implements HttpServerInterface { $encodingValidator = new \Ratchet\RFC6455\Encoding\Validator; $this->handshakeNegotiator = new \Ratchet\RFC6455\Handshake\Negotiator($encodingValidator); $this->messageStreamer = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($encodingValidator); + + if ($component instanceof WsServerInterface) { + $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); + } } /** @@ -70,14 +74,6 @@ class WsServer implements HttpServerInterface { $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); -// Probably moved to RFC lib -// $subHeader = $conn->WebSocket->request->getHeader('Sec-WebSocket-Protocol'); -// if (count($subHeader) > 0) { -// if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader))) { -// $response = $response->withHeader('Sec-WebSocket-Protocol', $agreedSubProtocols); -// } -// } - $conn->send(gPsr\str($response)); if (101 != $response->getStatusCode()) { @@ -141,35 +137,7 @@ class WsServer implements HttpServerInterface { return $this; } - /** - * @param string - * @return boolean - */ - public function isSubProtocolSupported($name) { - if (!$this->isSpGenerated) { - if ($this->component instanceof WsServerInterface) { - $this->acceptedSubProtocols = array_flip($this->component->getSubProtocols()); - } - - $this->isSpGenerated = true; - } - - return array_key_exists($name, $this->acceptedSubProtocols); - } - - /** - * @param \Traversable|null $requested - * @return string - */ - protected function getSubProtocolString(\Traversable $requested = null) { - if (null !== $requested) { - foreach ($requested as $sub) { - if ($this->isSubProtocolSupported($sub)) { - return $sub; - } - } - } - - return ''; + public function setStrictSubProtocolCheck($enable) { + $this->handshakeNegotiator->setStrictSubProtocolCheck($enable); } } \ No newline at end of file From f9b052d85eeaeaf4979f693c04193abbad83a65a Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 10 Feb 2016 18:52:42 -0500 Subject: [PATCH 07/52] New RFC interfaces, heartbeat init Cherrypicked from 6b6a5f0d6d9a10547291a0d8c027584448481daf :-/ --- composer.json | 2 +- src/Ratchet/WebSocket/ConnectionContext.php | 84 -------------- src/Ratchet/WebSocket/WsConnection.php | 6 +- src/Ratchet/WebSocket/WsServer.php | 117 ++++++++++++++------ 4 files changed, 85 insertions(+), 124 deletions(-) delete mode 100644 src/Ratchet/WebSocket/ConnectionContext.php diff --git a/composer.json b/composer.json index 72a8283..a34968e 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "php": ">=5.3.9" , "react/socket": "^0.3 || ^0.4" , "guzzlehttp/psr7": "^1.0" - , "ratchet/rfc6455": "dev-psr7" + , "ratchet/rfc6455": "dev-psr7-multi-streamer" , "symfony/http-foundation": "^2.2" , "symfony/routing": "^2.2" } diff --git a/src/Ratchet/WebSocket/ConnectionContext.php b/src/Ratchet/WebSocket/ConnectionContext.php deleted file mode 100644 index 20fb58b..0000000 --- a/src/Ratchet/WebSocket/ConnectionContext.php +++ /dev/null @@ -1,84 +0,0 @@ -conn = $conn; - $this->component = $component; - } - - public function detach() { - $conn = $this->conn; - - $this->frame = null; - $this->message = null; - - $this->component = null; - $this->conn = null; - - return $conn; - } - - public function onError(\Exception $e) { - $this->component->onError($this->conn, $e); - } - - public function setFrame(FrameInterface $frame = null) { - $this->frame = $frame; - - return $frame; - } - - /** - * @return \Ratchet\RFC6455\Messaging\Protocol\FrameInterface - */ - public function getFrame() { - return $this->frame; - } - - public function setMessage(MessageInterface $message = null) { - $this->message = $message; - - return $message; - } - - /** - * @return \Ratchet\RFC6455\Messaging\Protocol\MessageInterface - */ - public function getMessage() { - return $this->message; - } - - public function onMessage(MessageInterface $msg) { - $this->component->onMessage($this->conn, $msg->getPayload()); - } - - public function onPing(FrameInterface $frame) { - $pong = new Frame($frame->getPayload(), true, Frame::OP_PONG); - - $this->conn->send($pong); - } - - public function onPong(FrameInterface $frame) { - // TODO: Implement onPong() method. - } - - /** - * @param $code int - */ - public function onClose($code) { - $this->conn->close($code); - } -} \ No newline at end of file diff --git a/src/Ratchet/WebSocket/WsConnection.php b/src/Ratchet/WebSocket/WsConnection.php index a4adce2..175f152 100644 --- a/src/Ratchet/WebSocket/WsConnection.php +++ b/src/Ratchet/WebSocket/WsConnection.php @@ -1,8 +1,8 @@ WebSocket->closing) { diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 8f71538..5228105 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -6,6 +6,11 @@ use Ratchet\Http\HttpServerInterface; use Ratchet\Http\CloseResponseTrait; use Psr\Http\Message\RequestInterface; use GuzzleHttp\Psr7 as gPsr; +use Ratchet\RFC6455\Messaging\FrameInterface; +use Ratchet\RFC6455\Messaging\MessageInterface; +use Ratchet\RFC6455\Messaging\MessageBuffer; +use React\EventLoop\LoopInterface; +use Ratchet\RFC6455\Messaging\Frame; /** * The adapter to handle WebSocket requests/responses @@ -20,7 +25,7 @@ class WsServer implements HttpServerInterface { * Decorated component * @var \Ratchet\MessageComponentInterface */ - public $component; + private $delegate; /** * @var \SplObjectStorage @@ -28,34 +33,34 @@ class WsServer implements HttpServerInterface { protected $connections; /** - * Holder of accepted protocols, implement through WampServerInterface + * @var \Ratchet\RFC6455\Messaging\CloseFrameChecker */ - protected $acceptedSubProtocols = []; + private $closeFrameChecker; /** - * Flag if we have checked the decorated component for sub-protocols - * @var boolean + * @var \Ratchet\RFC6455\Handshake\Negotiator */ - private $isSpGenerated = false; - private $handshakeNegotiator; - private $messageStreamer; + + private $pongReceiver; /** * @param \Ratchet\MessageComponentInterface $component Your application to run with WebSockets * If you want to enable sub-protocols have your component implement WsServerInterface as well */ public function __construct(MessageComponentInterface $component) { - $this->component = $component; + $this->delegate = $component; $this->connections = new \SplObjectStorage; - $encodingValidator = new \Ratchet\RFC6455\Encoding\Validator; - $this->handshakeNegotiator = new \Ratchet\RFC6455\Handshake\Negotiator($encodingValidator); - $this->messageStreamer = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($encodingValidator); +// $this->encodingValidator = new \Ratchet\RFC6455\Encoding\Validator; + $this->closeFrameChecker = new \Ratchet\RFC6455\Messaging\CloseFrameChecker; + $this->handshakeNegotiator = new \Ratchet\RFC6455\Handshake\ServerNegotiator; if ($component instanceof WsServerInterface) { $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); } + + $this->pongReceiver = function() {}; } /** @@ -68,23 +73,33 @@ class WsServer implements HttpServerInterface { $conn->httpRequest = $request; // This will replace ->WebSocket->request - $conn->WebSocket = new \StdClass; - $conn->WebSocket->closing = false; - $conn->WebSocket->request = $request; // deprecated + $conn->WebSocket = new \StdClass; + $conn->WebSocket->closing = false; + $conn->WebSocket->request = $request; // deprecated $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); $conn->send(gPsr\str($response)); - if (101 != $response->getStatusCode()) { + if (101 !== $response->getStatusCode()) { return $conn->close(); } - $wsConn = new WsConnection($conn); - $context = new ConnectionContext($wsConn, $this->component); - $this->connections->attach($conn, $context); + $wsConn = new WsConnection($conn); - return $this->component->onOpen($wsConn); + $streamer = new MessageBuffer( + $this->closeFrameChecker, + function(MessageInterface $msg) use ($wsConn) { + $this->delegate->onMessage($wsConn, $msg); + }, + function(FrameInterface $frame) use ($wsConn) { + $this->onControlFrame($frame, $wsConn); + } + ); + + $this->connections->attach($conn, [$wsConn, $streamer]); + + return $this->delegate->onOpen($wsConn); } /** @@ -96,8 +111,7 @@ class WsServer implements HttpServerInterface { } $context = $this->connections[$from]; - - $this->messageStreamer->onData($msg, $context); + $context[1]->onData($msg); } /** @@ -105,12 +119,10 @@ class WsServer implements HttpServerInterface { */ public function onClose(ConnectionInterface $conn) { if ($this->connections->contains($conn)) { - $decor = $this->connections[$conn]; + $context = $this->connections[$conn]; $this->connections->detach($conn); - $conn = $decor->detach(); - - $this->component->onClose($conn); + $this->delegate->onClose($context[0]); } } @@ -120,24 +132,57 @@ class WsServer implements HttpServerInterface { public function onError(ConnectionInterface $conn, \Exception $e) { if ($this->connections->contains($conn)) { $context = $this->connections[$conn]; - $context->onError($e); + $this->delegate->onError($context[0], $e); } else { $conn->close(); } } - /** - * Toggle weather to check encoding of incoming messages - * @param bool - * @return WsServer - */ - public function setEncodingChecks($opt) { -// $this->validator->on = (boolean)$opt; - - return $this; + public function onControlFrame(FrameInterface $frame, WsConnection $conn) { + switch ($frame->getOpCode()) { + case Frame::OP_CLOSE: + $conn->close($frame); + break; + case Frame::OP_PING: + $conn->send(new Frame($frame->getPayload(), true, Frame::OP_PONG)); + break; + case Frame::OP_PONG: + $pongReceiver = $this->pongReceiver; + $pongReceiver($frame, $conn); + break; + } } public function setStrictSubProtocolCheck($enable) { $this->handshakeNegotiator->setStrictSubProtocolCheck($enable); } + + public function enableKeepAlive(LoopInterface $loop, $interval = 30) { + $lastPing = null; + $pingedConnections = new \SplObjectStorage; + $splClearer = new \SplObjectStorage; + + $this->pongReceiver = function(FrameInterface $frame, $wsConn) use ($pingedConnections, &$lastPing) { + if ($frame->getPayload() === $lastPing->getPayload()) { + $pingedConnections->detach($wsConn); + } + }; + + $loop->addPeriodicTimer((int)$interval, function() use ($pingedConnections, &$lastPing, $splClearer) { + foreach ($pingedConnections as $wsConn) { + $wsConn->close(); + } + $pingedConnections->removeAllExcept($splClearer); + + $lastPing = new Frame(uniqid(), true, Frame::OP_PING); + + foreach ($this->connections as $key => $conn) { + $context = $this->connections[$conn]; + $wsConn = $context[0]; + + $wsConn->send($lastPing); + $pingedConnections->attach($wsConn); + } + }); + } } \ No newline at end of file From 7f532e1747678ec6a6fca8dba8a10f6a8d85bd9c Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 10 Feb 2016 18:55:23 -0500 Subject: [PATCH 08/52] Perf update Cherrypicked from 075f122e1489b1cd1b4f332719e64f80be500021 Re-use same exception for Frame buffering flow control --- src/Ratchet/WebSocket/WsServer.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 5228105..e9d2e7c 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -42,6 +42,14 @@ class WsServer implements HttpServerInterface { */ private $handshakeNegotiator; + /** + * @var \Closure + */ + private $ueFlowFactory; + + /** + * @var \Closure + */ private $pongReceiver; /** @@ -52,7 +60,6 @@ class WsServer implements HttpServerInterface { $this->delegate = $component; $this->connections = new \SplObjectStorage; -// $this->encodingValidator = new \Ratchet\RFC6455\Encoding\Validator; $this->closeFrameChecker = new \Ratchet\RFC6455\Messaging\CloseFrameChecker; $this->handshakeNegotiator = new \Ratchet\RFC6455\Handshake\ServerNegotiator; @@ -61,6 +68,11 @@ class WsServer implements HttpServerInterface { } $this->pongReceiver = function() {}; + + $reusableUnderflowException = new \UnderflowException; + $this->ueFlowFactory = function() use ($reusableUnderflowException) { + return $reusableUnderflowException; + }; } /** @@ -94,7 +106,9 @@ class WsServer implements HttpServerInterface { }, function(FrameInterface $frame) use ($wsConn) { $this->onControlFrame($frame, $wsConn); - } + }, + true, + $this->ueFlowFactory ); $this->connections->attach($conn, [$wsConn, $streamer]); From e986a76cbbc51fe36b8a671ce3de6d1dda4101fa Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 11 Feb 2016 09:49:46 -0500 Subject: [PATCH 09/52] Keepalive in App, doc fix --- src/Ratchet/App.php | 4 ++++ src/Ratchet/WebSocket/WsConnection.php | 2 +- src/Ratchet/WebSocket/WsServer.php | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index b7d0e55..da144ec 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -113,6 +113,10 @@ class App { $decorated = $controller; } + if ($decorated instanceof WsServer) { + $decorated->enableKeepAlive($this->_server->loop, 30); + } + if ($httpHost === null) { $httpHost = $this->httpHost; } diff --git a/src/Ratchet/WebSocket/WsConnection.php b/src/Ratchet/WebSocket/WsConnection.php index 175f152..d2d04ef 100644 --- a/src/Ratchet/WebSocket/WsConnection.php +++ b/src/Ratchet/WebSocket/WsConnection.php @@ -25,7 +25,7 @@ class WsConnection extends AbstractConnectionDecorator { } /** - * @param int|\Ratchet\RFC6455\Messaging\Protocol\DataInterface + * @param int|\Ratchet\RFC6455\Messaging\DataInterface */ public function close($code = 1000) { if ($this->WebSocket->closing) { diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index e9d2e7c..9878495 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -38,7 +38,7 @@ class WsServer implements HttpServerInterface { private $closeFrameChecker; /** - * @var \Ratchet\RFC6455\Handshake\Negotiator + * @var \Ratchet\RFC6455\Handshake\ServerNegotiator */ private $handshakeNegotiator; From 2f79840f3087a9c47c17210d8bcdb376ea07b537 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 11 Feb 2016 17:52:33 -0500 Subject: [PATCH 10/52] Fixed Router/tests with PSR-7 integration --- src/Ratchet/Http/Router.php | 22 +++++++------- tests/unit/Http/RouterTest.php | 55 ++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/Ratchet/Http/Router.php b/src/Ratchet/Http/Router.php index 834115a..8931b6e 100644 --- a/src/Ratchet/Http/Router.php +++ b/src/Ratchet/Http/Router.php @@ -5,6 +5,7 @@ use Psr\Http\Message\RequestInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use GuzzleHttp\Psr7 as gPsr; class Router implements HttpServerInterface { use CloseResponseTrait; @@ -49,18 +50,15 @@ class Router implements HttpServerInterface { throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); } -// TODO: Apply Symfony default params to request -// $parameters = []; -// foreach($route as $key => $value) { -// if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { -// $parameters[$key] = $value; -// } -// } -// $parameters = array_merge($parameters, gPsr\parse_query($uri->getQuery())); -// -// $url = Url::factory($request->getPath()); -// $url->setQuery($parameters); -// $request->setUrl($url); + $parameters = []; + foreach($route as $key => $value) { + if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { + $parameters[$key] = $value; + } + } + $parameters = array_merge($parameters, gPsr\parse_query($uri->getQuery() ?: '')); + + $request = $request->withUri($uri->withQuery(gPsr\build_query($parameters))); $conn->controller = $route['_controller']; $conn->controller->onOpen($conn, $request); diff --git a/tests/unit/Http/RouterTest.php b/tests/unit/Http/RouterTest.php index 5a1128e..0799519 100644 --- a/tests/unit/Http/RouterTest.php +++ b/tests/unit/Http/RouterTest.php @@ -1,6 +1,5 @@ getMock('Guzzle\Http\QueryString'); - $queryMock - ->expects($this->any()) - ->method('getAll') - ->will($this->returnValue(array())); - - $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); - $this->_req = $this->getMock('\Guzzle\Http\Message\RequestInterface'); + $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_uri = $this->getMock('Psr\Http\Message\UriInterface'); + $this->_req = $this->getMock('\Psr\Http\Message\RequestInterface'); $this->_req ->expects($this->any()) - ->method('getQuery') - ->will($this->returnValue($queryMock)); + ->method('getUri') + ->will($this->returnValue($this->_uri)); $this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); $this->_matcher ->expects($this->any()) @@ -34,7 +29,14 @@ class RouterTest extends \PHPUnit_Framework_TestCase { ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))); $this->_router = new Router($this->_matcher); - $this->_req->expects($this->any())->method('getPath')->will($this->returnValue('/whatever')); + $this->_uri->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); + $this->_uri->expects($this->any())->method('withQuery')->with($this->callback(function($val) { + $this->setResult($val); + + return true; + }))->will($this->returnSelf()); + $this->_uri->expects($this->any())->method('getQuery')->will($this->returnCallback([$this, 'getResult'])); + $this->_req->expects($this->any())->method('withUri')->will($this->returnSelf()); } public function testFourOhFour() { @@ -103,38 +105,39 @@ class RouterTest extends \PHPUnit_Framework_TestCase { $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); /** @var $matcher UrlMatcherInterface */ $this->_matcher->expects($this->any())->method('match')->will( - $this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) + $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) ); $conn = $this->getMock('Ratchet\Mock\Connection'); - $request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', 'ws://random.url'), '', false); - $request->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); - - $request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); - $request->setUrl('ws://doesnt.matter/'); - $router = new Router($this->_matcher); - $router->onOpen($conn, $request); + $router->onOpen($conn, $this->_req); - $this->assertEquals(array('foo' => 'bar', 'baz' => 'qux'), $request->getQuery()->getAll()); + $this->assertEquals('foo=bar&baz=qux', $this->_req->getUri()->getQuery()); } public function testQueryParams() { $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); $this->_matcher->expects($this->any())->method('match')->will( - $this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) + $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) ); $conn = $this->getMock('Ratchet\Mock\Connection'); - $request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', ''), '', false); + $request = $this->getMock('Psr\Http\Message\RequestInterface'); + $uri = new \GuzzleHttp\Psr7\Uri('ws://doesnt.matter/endpoint?hello=world&foo=nope'); - $request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); - $request->setUrl('ws://doesnt.matter?hello=world&foo=nope'); + $request->expects($this->any())->method('getUri')->will($this->returnCallback(function() use (&$uri) { + return $uri; + })); + $request->expects($this->any())->method('withUri')->with($this->callback(function($url) use (&$uri) { + $uri = $url; + + return true; + }))->will($this->returnSelf()); $router = new Router($this->_matcher); $router->onOpen($conn, $request); - $this->assertEquals(array('foo' => 'nope', 'baz' => 'qux', 'hello' => 'world'), $request->getQuery()->getAll()); + $this->assertEquals('foo=nope&baz=qux&hello=world', $request->getUri()->getQuery()); } } From 524f8799542dd1c0bc775094d82d4d53e8be64ec Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 11 Feb 2016 18:24:29 -0500 Subject: [PATCH 11/52] Fixed failing HTTP unit tests from PSR-7 --- tests/unit/Http/HttpRequestParserTest.php | 3 +-- tests/unit/Http/OriginCheckTest.php | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/unit/Http/HttpRequestParserTest.php b/tests/unit/Http/HttpRequestParserTest.php index 4df7d8d..6af8402 100644 --- a/tests/unit/Http/HttpRequestParserTest.php +++ b/tests/unit/Http/HttpRequestParserTest.php @@ -1,6 +1,5 @@ getMock('\Ratchet\ConnectionInterface'); $return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"); - $this->assertInstanceOf('\Guzzle\Http\Message\RequestInterface', $return); + $this->assertInstanceOf('\Psr\Http\Message\RequestInterface', $return); } } diff --git a/tests/unit/Http/OriginCheckTest.php b/tests/unit/Http/OriginCheckTest.php index 34db439..c1c4012 100644 --- a/tests/unit/Http/OriginCheckTest.php +++ b/tests/unit/Http/OriginCheckTest.php @@ -9,8 +9,8 @@ class OriginCheckTest extends AbstractMessageComponentTestCase { protected $_reqStub; public function setUp() { - $this->_reqStub = $this->getMock('Guzzle\Http\Message\RequestInterface'); - $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue('localhost')); + $this->_reqStub = $this->getMock('Psr\Http\Message\RequestInterface'); + $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue(['localhost'])); parent::setUp(); @@ -34,7 +34,7 @@ class OriginCheckTest extends AbstractMessageComponentTestCase { } public function testCloseOnNonMatchingOrigin() { - $this->_serv->allowedOrigins = array('socketo.me'); + $this->_serv->allowedOrigins = ['socketo.me']; $this->_conn->expects($this->once())->method('close'); $this->_serv->onOpen($this->_conn, $this->_reqStub); From 1523f399562195e923f5bb29f2f1e47840545fae Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 11 Feb 2016 18:30:35 -0500 Subject: [PATCH 12/52] Fixed Session unit tests from PSR-7 --- src/Ratchet/Session/SessionProvider.php | 4 ++-- tests/unit/Session/SessionComponentTest.php | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Ratchet/Session/SessionProvider.php b/src/Ratchet/Session/SessionProvider.php index 392a13d..44276c5 100644 --- a/src/Ratchet/Session/SessionProvider.php +++ b/src/Ratchet/Session/SessionProvider.php @@ -38,7 +38,7 @@ class SessionProvider implements HttpServerInterface { protected $_serializer; /** - * @param \Ratchet\HttpServerInterface $app + * @param \Ratchet\Http\HttpServerInterface $app * @param \SessionHandlerInterface $handler * @param array $options * @param \Ratchet\Session\Serialize\HandlerInterface $serializer @@ -80,7 +80,7 @@ class SessionProvider implements HttpServerInterface { $crumbs = $this->parseCookie($cookie); - return isset($crumbs[$sessionName]) ? $crumbs[$sessionName] : false; + return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false; }, false); if (null === $request || false === $id) { diff --git a/tests/unit/Session/SessionComponentTest.php b/tests/unit/Session/SessionComponentTest.php index e889637..ebfdde4 100644 --- a/tests/unit/Session/SessionComponentTest.php +++ b/tests/unit/Session/SessionComponentTest.php @@ -1,7 +1,6 @@ getMock($this->getComponentClassString()), $pdoHandler, array('auto_start' => 1)); $connection = $this->getMock('Ratchet\\ConnectionInterface'); - $headers = $this->getMock('Guzzle\\Http\\Message\\Request', array('getCookie'), array('POST', '/', array())); - $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue($sessionId)); + $headers = $this->getMock('Psr\Http\Message\RequestInterface'); + $headers->expects($this->once())->method('getHeader')->will($this->returnValue([ini_get('session.name') . "={$sessionId};"])); $component->onOpen($connection, $headers); @@ -94,7 +93,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { protected function newConn() { $conn = $this->getMock('Ratchet\ConnectionInterface'); - $headers = $this->getMock('Guzzle\Http\Message\Request', array('getCookie'), array('POST', '/', array())); + $headers = $this->getMock('Psr\Http\Message\Request', array('getCookie'), array('POST', '/', array())); $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null)); return $conn; @@ -115,4 +114,11 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { $this->setExpectedException('\RuntimeException'); new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); } + + protected function doOpen($conn) { + $request = $this->getMock('Psr\Http\Message\RequestInterface'); + $request->expects($this->any())->method('getHeader')->will($this->returnValue([])); + + $this->_serv->onOpen($conn, $request); + } } From c15e275584cf9d8b5a3f93bef5ce7d0495ed9f3f Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 11 Feb 2016 18:33:50 -0500 Subject: [PATCH 13/52] Deleted deprecated tests from PSR-7 refactor GuzzleTest -> no longer using Guzzle HTTP interfaces WebSocket tests have been moved to RFC library --- tests/integration/GuzzleTest.php | 53 -- tests/unit/WebSocket/Version/Hixie76Test.php | 103 ---- tests/unit/WebSocket/Version/HyBi10Test.php | 67 --- .../WebSocket/Version/RFC6455/FrameTest.php | 543 ------------------ .../Version/RFC6455/HandshakeVerifierTest.php | 170 ------ .../WebSocket/Version/RFC6455/MessageTest.php | 63 -- tests/unit/WebSocket/Version/RFC6455Test.php | 151 ----- tests/unit/WebSocket/VersionManagerTest.php | 91 --- tests/unit/WebSocket/WsServerTest.php | 51 -- 9 files changed, 1292 deletions(-) delete mode 100644 tests/integration/GuzzleTest.php delete mode 100644 tests/unit/WebSocket/Version/Hixie76Test.php delete mode 100644 tests/unit/WebSocket/Version/HyBi10Test.php delete mode 100644 tests/unit/WebSocket/Version/RFC6455/FrameTest.php delete mode 100644 tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php delete mode 100644 tests/unit/WebSocket/Version/RFC6455/MessageTest.php delete mode 100644 tests/unit/WebSocket/Version/RFC6455Test.php delete mode 100644 tests/unit/WebSocket/VersionManagerTest.php delete mode 100644 tests/unit/WebSocket/WsServerTest.php diff --git a/tests/integration/GuzzleTest.php b/tests/integration/GuzzleTest.php deleted file mode 100644 index 5e4d8aa..0000000 --- a/tests/integration/GuzzleTest.php +++ /dev/null @@ -1,53 +0,0 @@ - 'websocket' - , 'Connection' => 'Upgrade' - , 'Host' => 'localhost:8080' - , 'Origin' => 'chrome://newtab' - , 'Sec-WebSocket-Protocol' => 'one, two, three' - , 'Sec-WebSocket-Key' => '9bnXNp3ae6FbFFRtPdiPXA==' - , 'Sec-WebSocket-Version' => '13' - ); - - public function setUp() { - $this->_request = new Request('GET', 'http://localhost', $this->_headers); - } - - public function testGetHeaderString() { - $this->assertEquals('Upgrade', (string)$this->_request->getHeader('connection')); - $this->assertEquals('9bnXNp3ae6FbFFRtPdiPXA==', (string)$this->_request->getHeader('Sec-Websocket-Key')); - } - - public function testGetHeaderInteger() { - $this->assertSame('13', (string)$this->_request->getHeader('Sec-Websocket-Version')); - $this->assertSame(13, (int)(string)$this->_request->getHeader('Sec-WebSocket-Version')); - } - - public function testGetHeaderObject() { - $this->assertInstanceOf('Guzzle\Http\Message\Header', $this->_request->getHeader('Origin')); - $this->assertNull($this->_request->getHeader('Non-existant-header')); - } - - public function testHeaderObjectNormalizeValues() { - $expected = 1 + substr_count($this->_headers['Sec-WebSocket-Protocol'], ','); - $protocols = $this->_request->getHeader('Sec-WebSocket-Protocol')->normalize(); - $count = 0; - - foreach ($protocols as $protocol) { - $count++; - } - - $this->assertEquals($expected, $count); - $this->assertEquals($expected, count($protocols)); - } - - public function testRequestFactoryCreateSignature() { - $ref = new \ReflectionMethod('Guzzle\Http\Message\RequestFactory', 'create'); - $this->assertEquals(2, $ref->getNumberOfRequiredParameters()); - } -} diff --git a/tests/unit/WebSocket/Version/Hixie76Test.php b/tests/unit/WebSocket/Version/Hixie76Test.php deleted file mode 100644 index 75998aa..0000000 --- a/tests/unit/WebSocket/Version/Hixie76Test.php +++ /dev/null @@ -1,103 +0,0 @@ -_version = new Hixie76; - } - - public function testClassImplementsVersionInterface() { - $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); - $this->assertThat($this->_version, $constraint); - } - - /** - * @dataProvider keyProvider - */ - public function testKeySigningForHandshake($accept, $key) { - $this->assertEquals($accept, $this->_version->generateKeyNumber($key)); - } - - public static function keyProvider() { - return array( - array(179922739, '17 9 G`ZD9 2 2b 7X 3 /r90') - , array(906585445, '3e6b263 4 17 80') - , array(0, '3e6b26341780') - ); - } - - public function headerProvider() { - $key1 = base64_decode('QTN+ICszNiA2IDJvICBWOG4gNyAgc08yODhZ'); - $key2 = base64_decode('TzEyICAgeVsgIFFSNDUgM1IgLiAyOFggNC00dn4z'); - - $headers = "GET / HTTP/1.1"; - $headers .= "Upgrade: WebSocket{$this->_crlf}"; - $headers .= "Connection: Upgrade{$this->_crlf}"; - $headers .= "Host: socketo.me{$this->_crlf}"; - $headers .= "Origin: http://fiddle.jshell.net{$this->_crlf}"; - $headers .= "Sec-WebSocket-Key1:17 Z4< F94 N3 7P41 7{$this->_crlf}"; - $headers .= "Sec-WebSocket-Key2:1 23C3:,2% 1-29 4 f0{$this->_crlf}"; - $headers .= "(Key3):70:00:EE:6E:33:20:90:69{$this->_crlf}"; - $headers .= $this->_crlf; - - return $headers; - } - - public function testNoUpgradeBeforeBody() { - $headers = $this->headerProvider(); - - $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); - $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - - $server = new HttpServer(new WsServer($mockApp)); - $server->onOpen($mockConn); - $mockApp->expects($this->exactly(0))->method('onOpen'); - $server->onMessage($mockConn, $headers); - } - - public function testTcpFragmentedUpgrade() { - $headers = $this->headerProvider(); - $body = base64_decode($this->_body); - - $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); - $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - - $server = new HttpServer(new WsServer($mockApp)); - $server->onOpen($mockConn); - $server->onMessage($mockConn, $headers); - - $mockApp->expects($this->once())->method('onOpen'); - $server->onMessage($mockConn, $body . $this->_crlf . $this->_crlf); - } - - public function testTcpFragmentedBodyUpgrade() { - $headers = $this->headerProvider(); - $body = base64_decode($this->_body); - $body1 = substr($body, 0, 4); - $body2 = substr($body, 4); - - $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); - $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - - $server = new HttpServer(new WsServer($mockApp)); - $server->onOpen($mockConn); - $server->onMessage($mockConn, $headers); - - $mockApp->expects($this->once())->method('onOpen'); - - $server->onMessage($mockConn, $body1); - $server->onMessage($mockConn, $body2); - $server->onMessage($mockConn, $this->_crlf . $this->_crlf); - } -} diff --git a/tests/unit/WebSocket/Version/HyBi10Test.php b/tests/unit/WebSocket/Version/HyBi10Test.php deleted file mode 100644 index 1d9e8a9..0000000 --- a/tests/unit/WebSocket/Version/HyBi10Test.php +++ /dev/null @@ -1,67 +0,0 @@ -_version = new HyBi10(); - } - - /** - * Is this useful? - */ - public function testClassImplementsVersionInterface() { - $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); - $this->assertThat($this->_version, $constraint); - } - - /** - * @dataProvider HandshakeProvider - */ - public function testKeySigningForHandshake($key, $accept) { - $this->assertEquals($accept, $this->_version->sign($key)); - } - - public static function HandshakeProvider() { - return array( - array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') - , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') - ); - } - - /** - * @dataProvider UnframeMessageProvider - */ - public function testUnframeMessage($message, $framed) { -// $decoded = $this->_version->unframe(base64_decode($framed)); - $frame = new Frame; - $frame->addBuffer(base64_decode($framed)); - - $this->assertEquals($message, $frame->getPayload()); - } - - public static function UnframeMessageProvider() { - return array( - array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7') - , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') - , array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow==') - , array("The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') - ); - } - - public function testUnframeMatchesPreFraming() { - $string = 'Hello World!'; - $framed = $this->_version->newFrame($string)->getContents(); - - $frame = new Frame; - $frame->addBuffer($framed); - - $this->assertEquals($string, $frame->getPayload()); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455/FrameTest.php b/tests/unit/WebSocket/Version/RFC6455/FrameTest.php deleted file mode 100644 index eff9513..0000000 --- a/tests/unit/WebSocket/Version/RFC6455/FrameTest.php +++ /dev/null @@ -1,543 +0,0 @@ -_frame = new Frame; - } - - /** - * Encode the fake binary string to send over the wire - * @param string of 1's and 0's - * @return string - */ - public static function encode($in) { - if (strlen($in) > 8) { - $out = ''; - - while (strlen($in) >= 8) { - $out .= static::encode(substr($in, 0, 8)); - $in = substr($in, 8); - } - - return $out; - } - - return chr(bindec($in)); - } - - /** - * This is a data provider - * @param string The UTF8 message - * @param string The WebSocket framed message, then base64_encoded - */ - public static function UnframeMessageProvider() { - return array( - array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7') - , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') - , array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow==') - , array("The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') - ); - } - - public static function underflowProvider() { - return array( - array('isFinal', '') - , array('getRsv1', '') - , array('getRsv2', '') - , array('getRsv3', '') - , array('getOpcode', '') - , array('isMasked', '10000001') - , array('getPayloadLength', '10000001') - , array('getPayloadLength', '1000000111111110') - , array('getMaskingKey', '1000000110000111') - , array('getPayload', '100000011000000100011100101010101001100111110100') - ); - } - - /** - * @dataProvider underflowProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - */ - public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) { - $this->setExpectedException('\UnderflowException'); - - if (!empty($bin)) { - $this->_frame->addBuffer(static::encode($bin)); - } - - call_user_func(array($this->_frame, $method)); - } - - /** - * A data provider for testing the first byte of a WebSocket frame - * @param bool Given, is the byte indicate this is the final frame - * @param int Given, what is the expected opcode - * @param string of 0|1 Each character represents a bit in the byte - */ - public static function firstByteProvider() { - return array( - array(false, false, false, true, 8, '00011000') - , array(true, false, true, false, 10, '10101010') - , array(false, false, false, false, 15, '00001111') - , array(true, false, false, false, 1, '10000001') - , array(true, true, true, true, 15, '11111111') - , array(true, true, false, false, 7, '11000111') - ); - } - - /** - * @dataProvider firstByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal - */ - public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { - $this->_frame->addBuffer(static::encode($bin)); - $this->assertEquals($fin, $this->_frame->isFinal()); - } - - /** - * @dataProvider firstByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3 - */ - public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { - $this->_frame->addBuffer(static::encode($bin)); - - $this->assertEquals($rsv1, $this->_frame->getRsv1()); - $this->assertEquals($rsv2, $this->_frame->getRsv2()); - $this->assertEquals($rsv3, $this->_frame->getRsv3()); - } - - /** - * @dataProvider firstByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode - */ - public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { - $this->_frame->addBuffer(static::encode($bin)); - $this->assertEquals($opcode, $this->_frame->getOpcode()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal - */ - public function testFinCodeFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertTrue($this->_frame->isFinal()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode - */ - public function testOpcodeFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertEquals(1, $this->_frame->getOpcode()); - } - - public static function payloadLengthDescriptionProvider() { - return array( - array(7, '01110101') - , array(7, '01111101') - , array(23, '01111110') - , array(71, '01111111') - , array(7, '00000000') // Should this throw an exception? Can a payload be empty? - , array(7, '00000001') - ); - } - - /** - * @dataProvider payloadLengthDescriptionProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::addBuffer - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal - */ - public function testFirstPayloadDesignationValue($bits, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getFirstPayloadVal'); - $cb->setAccessible(true); - - $this->assertEquals(bindec($bin), $cb->invoke($this->_frame)); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal - */ - public function testFirstPayloadValUnderflow() { - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getFirstPayloadVal'); - $cb->setAccessible(true); - - $this->setExpectedException('UnderflowException'); - $cb->invoke($this->_frame); - } - - /** - * @dataProvider payloadLengthDescriptionProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits - */ - public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getNumPayloadBits'); - $cb->setAccessible(true); - - $this->assertEquals($expected_bits, $cb->invoke($this->_frame)); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits - */ - public function testgetNumPayloadBitsUnderflow() { - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getNumPayloadBits'); - $cb->setAccessible(true); - - $this->setExpectedException('UnderflowException'); - $cb->invoke($this->_frame); - } - - public function secondByteProvider() { - return array( - array(true, 1, '10000001') - , array(false, 1, '00000001') - , array(true, 125, $this->_secondByteMaskedSPL) - ); - } - - /** - * @dataProvider secondByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked - */ - public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $this->assertEquals($masked, $this->_frame->isMasked()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked - */ - public function testIsMaskedFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertTrue($this->_frame->isMasked()); - } - - /** - * @dataProvider secondByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - */ - public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $this->assertEquals($payload_length, $this->_frame->getPayloadLength()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @todo Not yet testing when second additional payload length descriptor - */ - public function testGetPayloadLengthFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength()); - } - - public function maskingKeyProvider() { - $frame = new Frame; - - return array( - array($frame->generateMaskingKey()) - , array($frame->generateMaskingKey()) - , array($frame->generateMaskingKey()) - ); - } - - /** - * @dataProvider maskingKeyProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey - * @todo I I wrote the dataProvider incorrectly, skipping for now - */ - public function testGetMaskingKey($mask) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL)); - $this->_frame->addBuffer($mask); - - $this->assertEquals($mask, $this->_frame->getMaskingKey()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey - */ - public function testGetMaskingKeyOnUnmaskedPayload() { - $frame = new Frame('Hello World!'); - - $this->assertEquals('', $frame->getMaskingKey()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - * @todo Move this test to bottom as it requires all methods of the class - */ - public function testUnframeFullMessage($unframed, $base_framed) { - $this->_frame->addBuffer(base64_decode($base_framed)); - $this->assertEquals($unframed, $this->_frame->getPayload()); - } - - public static function messageFragmentProvider() { - return array( - array(false, '', '', '', '', '') - ); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - */ - public function testCheckPiecingTogetherMessage($msg, $encoded) { - $framed = base64_decode($encoded); - for ($i = 0, $len = strlen($framed);$i < $len; $i++) { - $this->_frame->addBuffer(substr($framed, $i, 1)); - } - - $this->assertEquals($msg, $this->_frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - */ - public function testLongCreate() { - $len = 65525; - $pl = $this->generateRandomString($len); - - $frame = new Frame($pl, true, Frame::OP_PING); - - $this->assertTrue($frame->isFinal()); - $this->assertEquals(Frame::OP_PING, $frame->getOpcode()); - $this->assertFalse($frame->isMasked()); - $this->assertEquals($len, $frame->getPayloadLength()); - $this->assertEquals($pl, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - */ - public function testReallyLongCreate() { - $len = 65575; - - $frame = new Frame($this->generateRandomString($len)); - - $this->assertEquals($len, $frame->getPayloadLength()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow - */ - public function testExtractOverflow() { - $string1 = $this->generateRandomString(); - $frame1 = new Frame($string1); - - $string2 = $this->generateRandomString(); - $frame2 = new Frame($string2); - - $cat = new Frame; - $cat->addBuffer($frame1->getContents() . $frame2->getContents()); - - $this->assertEquals($frame1->getContents(), $cat->getContents()); - $this->assertEquals($string1, $cat->getPayload()); - - $uncat = new Frame; - $uncat->addBuffer($cat->extractOverflow()); - - $this->assertEquals($string1, $cat->getPayload()); - $this->assertEquals($string2, $uncat->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow - */ - public function testEmptyExtractOverflow() { - $string = $this->generateRandomString(); - $frame = new Frame($string); - - $this->assertEquals($string, $frame->getPayload()); - $this->assertEquals('', $frame->extractOverflow()); - $this->assertEquals($string, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getContents - */ - public function testGetContents() { - $msg = 'The quick brown fox jumps over the lazy dog.'; - - $frame1 = new Frame($msg); - $frame2 = new Frame($msg); - $frame2->maskPayload(); - - $this->assertNotEquals($frame1->getContents(), $frame2->getContents()); - $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents())); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload - */ - public function testMasking() { - $msg = 'The quick brown fox jumps over the lazy dog.'; - $frame = new Frame($msg); - $frame->maskPayload(); - - $this->assertTrue($frame->isMasked()); - $this->assertEquals($msg, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::unMaskPayload - */ - public function testUnMaskPayload() { - $string = $this->generateRandomString(); - $frame = new Frame($string); - $frame->maskPayload()->unMaskPayload(); - - $this->assertFalse($frame->isMasked()); - $this->assertEquals($string, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::generateMaskingKey - */ - public function testGenerateMaskingKey() { - $dupe = false; - $done = array(); - - for ($i = 0; $i < 10; $i++) { - $new = $this->_frame->generateMaskingKey(); - - if (in_array($new, $done)) { - $dupe = true; - } - - $done[] = $new; - } - - $this->assertEquals(4, strlen($new)); - $this->assertFalse($dupe); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload - */ - public function testGivenMaskIsValid() { - $this->setExpectedException('InvalidArgumentException'); - $this->_frame->maskPayload('hello world'); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload - */ - public function testGivenMaskIsValidAscii() { - if (!extension_loaded('mbstring')) { - return $this->markTestSkipped("mbstring required for this test"); - } - - $this->setExpectedException('OutOfBoundsException'); - $this->_frame->maskPayload('x✖'); - } - - protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) { - $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง - - $useChars = array(); - for($i = 0; $i < $length; $i++) { - $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)]; - } - - if($addSpaces === true) { - array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); - } - - if($addNumbers === true) { - array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9)); - } - - shuffle($useChars); - - $randomString = trim(implode('', $useChars)); - $randomString = substr($randomString, 0, $length); - - return $randomString; - } - - /** - * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than - * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame - * to set the payload length to 126 and then not recalculate it once the full length information was available. - * - * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown. - * - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow - */ - public function testFrameDeliveredOneByteAtATime() { - $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final - $framePayload = str_repeat("*", 256); - $rawOverflow = "xyz"; - $rawFrame = $startHeader . $framePayload . $rawOverflow; - - $frame = new Frame(); - $payloadLen = 256; - - for ($i = 0; $i < strlen($rawFrame); $i++) { - $frame->addBuffer($rawFrame[$i]); - - try { - // payloadLen will - $payloadLen = $frame->getPayloadLength(); - } catch (\UnderflowException $e) { - if ($i > 2) { // we should get an underflow on 0,1,2 - $this->fail("Underflow exception when the frame length should be available"); - } - } - - if ($payloadLen !== 256) { - $this->fail("Payload length of " . $payloadLen . " should have been 256."); - } - } - - // make sure the overflow is good - $this->assertEquals($rawOverflow, $frame->extractOverflow()); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php b/tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php deleted file mode 100644 index 6761c32..0000000 --- a/tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php +++ /dev/null @@ -1,170 +0,0 @@ -_v = new HandshakeVerifier; - } - - public static function methodProvider() { - return array( - array(true, 'GET') - , array(true, 'get') - , array(true, 'Get') - , array(false, 'POST') - , array(false, 'DELETE') - , array(false, 'PUT') - , array(false, 'PATCH') - ); - } - - /** - * @dataProvider methodProvider - */ - public function testMethodMustBeGet($result, $in) { - $this->assertEquals($result, $this->_v->verifyMethod($in)); - } - - public static function httpVersionProvider() { - return array( - array(true, 1.1) - , array(true, '1.1') - , array(true, 1.2) - , array(true, '1.2') - , array(true, 2) - , array(true, '2') - , array(true, '2.0') - , array(false, '1.0') - , array(false, 1) - , array(false, '0.9') - , array(false, '') - , array(false, 'hello') - ); - } - - /** - * @dataProvider httpVersionProvider - */ - public function testHttpVersionIsAtLeast1Point1($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyHTTPVersion($in)); - } - - public static function uRIProvider() { - return array( - array(true, '/chat') - , array(true, '/hello/world?key=val') - , array(false, '/chat#bad') - , array(false, 'nope') - , array(false, '/ ಠ_ಠ ') - , array(false, '/✖') - ); - } - - /** - * @dataProvider URIProvider - */ - public function testRequestUri($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyRequestURI($in)); - } - - public static function hostProvider() { - return array( - array(true, 'server.example.com') - , array(false, null) - ); - } - - /** - * @dataProvider HostProvider - */ - public function testVerifyHostIsSet($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyHost($in)); - } - - public static function upgradeProvider() { - return array( - array(true, 'websocket') - , array(true, 'Websocket') - , array(true, 'webSocket') - , array(false, null) - , array(false, '') - ); - } - - /** - * @dataProvider upgradeProvider - */ - public function testVerifyUpgradeIsWebSocket($expected, $val) { - $this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val)); - } - - public static function connectionProvider() { - return array( - array(true, 'Upgrade') - , array(true, 'upgrade') - , array(true, 'keep-alive, Upgrade') - , array(true, 'Upgrade, keep-alive') - , array(true, 'keep-alive, Upgrade, something') - , array(false, '') - , array(false, null) - ); - } - - /** - * @dataProvider connectionProvider - */ - public function testConnectionHeaderVerification($expected, $val) { - $this->assertEquals($expected, $this->_v->verifyConnection($val)); - } - - public static function keyProvider() { - return array( - array(true, 'hkfa1L7uwN6DCo4IS3iWAw==') - , array(true, '765vVoQpKSGJwPzJIMM2GA==') - , array(true, 'AQIDBAUGBwgJCgsMDQ4PEC==') - , array(true, 'axa2B/Yz2CdpfQAY2Q5P7w==') - , array(false, 0) - , array(false, 'Hello World') - , array(false, '1234567890123456') - , array(false, '123456789012345678901234') - , array(true, base64_encode('UTF8allthngs+✓')) - , array(true, 'dGhlIHNhbXBsZSBub25jZQ==') - ); - } - - /** - * @dataProvider keyProvider - */ - public function testKeyIsBase64Encoded16BitNonce($expected, $val) { - $this->assertEquals($expected, $this->_v->verifyKey($val)); - } - - public static function versionProvider() { - return array( - array(true, 13) - , array(true, '13') - , array(false, 12) - , array(false, 14) - , array(false, '14') - , array(false, 'hi') - , array(false, '') - , array(false, null) - ); - } - - /** - * @dataProvider versionProvider - */ - public function testVersionEquals13($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyVersion($in)); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455/MessageTest.php b/tests/unit/WebSocket/Version/RFC6455/MessageTest.php deleted file mode 100644 index b2d21d2..0000000 --- a/tests/unit/WebSocket/Version/RFC6455/MessageTest.php +++ /dev/null @@ -1,63 +0,0 @@ -message = new Message; - } - - public function testNoFrames() { - $this->assertFalse($this->message->isCoalesced()); - } - - public function testNoFramesOpCode() { - $this->setExpectedException('UnderflowException'); - $this->message->getOpCode(); - } - - public function testFragmentationPayload() { - $a = 'Hello '; - $b = 'World!'; - - $f1 = new Frame($a, false); - $f2 = new Frame($b, true, Frame::OP_CONTINUE); - - $this->message->addFrame($f1)->addFrame($f2); - - $this->assertEquals(strlen($a . $b), $this->message->getPayloadLength()); - $this->assertEquals($a . $b, $this->message->getPayload()); - } - - public function testUnbufferedFragment() { - $this->message->addFrame(new Frame('The quick brow', false)); - - $this->setExpectedException('UnderflowException'); - $this->message->getPayload(); - } - - public function testGetOpCode() { - $this->message - ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) - ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) - ->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE)) - ; - - $this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode()); - } - - public function testGetUnBufferedPayloadLength() { - $this->message - ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) - ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) - ; - - $this->assertEquals(28, $this->message->getPayloadLength()); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455Test.php b/tests/unit/WebSocket/Version/RFC6455Test.php deleted file mode 100644 index 86e5631..0000000 --- a/tests/unit/WebSocket/Version/RFC6455Test.php +++ /dev/null @@ -1,151 +0,0 @@ -version = new RFC6455; - } - - /** - * @dataProvider handshakeProvider - */ - public function testKeySigningForHandshake($key, $accept) { - $this->assertEquals($accept, $this->version->sign($key)); - } - - public static function handshakeProvider() { - return array( - array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') - , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') - ); - } - - /** - * @dataProvider UnframeMessageProvider - */ - public function testUnframeMessage($message, $framed) { - $frame = new Frame; - $frame->addBuffer(base64_decode($framed)); - - $this->assertEquals($message, $frame->getPayload()); - } - - public static function UnframeMessageProvider() { - return array( - array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7') - , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') - , array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow==') - , array("The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') - ); - } - - public function testUnframeMatchesPreFraming() { - $string = 'Hello World!'; - $framed = $this->version->newFrame($string)->getContents(); - - $frame = new Frame; - $frame->addBuffer($framed); - - $this->assertEquals($string, $frame->getPayload()); - } - - public static $good_rest = 'GET /chat HTTP/1.1'; - - public static $good_header = array( - 'Host' => 'server.example.com' - , 'Upgrade' => 'websocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Key' => 'dGhlIHNhbXBsZSBub25jZQ==' - , 'Origin' => 'http://example.com' - , 'Sec-WebSocket-Protocol' => 'chat, superchat' - , 'Sec-WebSocket-Version' => 13 - ); - - public function caseVariantProvider() { - return array( - array('Sec-Websocket-Version') - , array('sec-websocket-version') - , array('SEC-WEBSOCKET-VERSION') - , array('sEC-wEBsOCKET-vERSION') - ); - } - - /** - * @dataProvider caseVariantProvider - */ - public function testIsProtocolWithCaseInsensitivity($headerName) { - $header = static::$good_header; - unset($header['Sec-WebSocket-Version']); - $header[$headerName] = 13; - - $this->assertTrue($this->version->isProtocol(new EntityEnclosingRequest('get', '/', $header))); - } - - /** - * A helper function to try and quickly put together a valid WebSocket HTTP handshake - * but optionally replace a piece to an invalid value for failure testing - */ - public static function getAndSpliceHeader($key = null, $val = null) { - $headers = static::$good_header; - - if (null !== $key && null !== $val) { - $headers[$key] = $val; - } - - $header = ''; - foreach ($headers as $key => $val) { - if (!empty($key)) { - $header .= "{$key}: "; - } - - $header .= "{$val}\r\n"; - } - $header .= "\r\n"; - - return $header; - } - - public static function headerHandshakeProvider() { - return array( - array(false, "GET /test HTTP/1.0\r\n" . static::getAndSpliceHeader()) - , array(true, static::$good_rest . "\r\n" . static::getAndSpliceHeader()) - , array(false, "POST / HTTP:/1.1\r\n" . static::getAndSpliceHeader()) - , array(false, static::$good_rest . "\r\n" . static::getAndSpliceHeader('Upgrade', 'useless')) - , array(false, "GET /ಠ_ಠ HTTP/1.1\r\n" . static::getAndSpliceHeader()) - , array(true, static::$good_rest . "\r\n" . static::getAndSpliceHeader('Connection', 'Herp, Upgrade, Derp')) - ); - } - - /** - * @dataProvider headerHandshakeProvider - */ - public function testVariousHeadersToCheckHandshakeTolerance($pass, $header) { - $request = RequestFactory::getInstance()->fromMessage($header); - $response = $this->version->handshake($request); - - $this->assertInstanceOf('\\Guzzle\\Http\\Message\\Response', $response); - - if ($pass) { - $this->assertEquals(101, $response->getStatusCode()); - } else { - $this->assertGreaterThanOrEqual(400, $response->getStatusCode()); - } - } - - public function testNewMessage() { - $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Message', $this->version->newMessage()); - } - - public function testNewFrame() { - $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Frame', $this->version->newFrame()); - } -} diff --git a/tests/unit/WebSocket/VersionManagerTest.php b/tests/unit/WebSocket/VersionManagerTest.php deleted file mode 100644 index d9c55fe..0000000 --- a/tests/unit/WebSocket/VersionManagerTest.php +++ /dev/null @@ -1,91 +0,0 @@ -vm = new VersionManager; - } - - public function testFluentInterface() { - $rfc = new RFC6455; - - $this->assertSame($this->vm, $this->vm->enableVersion($rfc)); - $this->assertSame($this->vm, $this->vm->disableVersion(13)); - } - - public function testGetVersion() { - $rfc = new RFC6455; - $this->vm->enableVersion($rfc); - - $req = new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 13 - )); - - $this->assertSame($rfc, $this->vm->getVersion($req)); - } - - public function testGetNopeVersionAndDisable() { - $req = new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 13 - )); - - $this->setExpectedException('InvalidArgumentException'); - - $this->vm->getVersion($req); - } - - public function testYesIsVersionEnabled() { - $this->vm->enableVersion(new RFC6455); - - $this->assertTrue($this->vm->isVersionEnabled(new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 13 - )))); - } - - public function testNoIsVersionEnabled() { - $this->assertFalse($this->vm->isVersionEnabled(new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 9000 - )))); - } - - public function testGetSupportedVersionString() { - $v1 = new RFC6455; - $v2 = new HyBi10; - - $this->vm->enableVersion($v1); - $this->vm->enableVersion($v2); - - $string = $this->vm->getSupportedVersionString(); - $values = explode(',', $string); - - $this->assertContains($v1->getVersionNumber(), $values); - $this->assertContains($v2->getVersionNumber(), $values); - } - - public function testGetSupportedVersionAfterRemoval() { - $this->vm->enableVersion(new RFC6455); - $this->vm->enableVersion(new HyBi10); - $this->vm->enableVersion(new Hixie76); - - $this->vm->disableVersion(0); - - $values = explode(',', $this->vm->getSupportedVersionString()); - - $this->assertEquals(2, count($values)); - $this->assertFalse(array_search(0, $values)); - } -} diff --git a/tests/unit/WebSocket/WsServerTest.php b/tests/unit/WebSocket/WsServerTest.php deleted file mode 100644 index 3f4aa43..0000000 --- a/tests/unit/WebSocket/WsServerTest.php +++ /dev/null @@ -1,51 +0,0 @@ -comp = new MockComponent; - $this->serv = new WsServer($this->comp); - } - - public function testIsSubProtocolSupported() { - $this->comp->protocols = array('hello', 'world'); - - $this->assertTrue($this->serv->isSubProtocolSupported('hello')); - $this->assertFalse($this->serv->isSubProtocolSupported('nope')); - } - - public function protocolProvider() { - return array( - array('hello', array('hello', 'world'), array('hello', 'world')) - , array('', array('hello', 'world'), array('wamp')) - , array('', array(), null) - , array('wamp', array('hello', 'wamp', 'world'), array('herp', 'derp', 'wamp')) - , array('wamp', array('wamp'), array('wamp')) - ); - } - - /** - * @dataProvider protocolProvider - */ - public function testGetSubProtocolString($expected, $supported, $requested) { - $this->comp->protocols = $supported; - $req = (null === $requested ? $requested : new \ArrayIterator($requested)); - - $class = new \ReflectionClass('Ratchet\\WebSocket\\WsServer'); - $method = $class->getMethod('getSubProtocolString'); - $method->setAccessible(true); - - $this->assertSame($expected, $method->invokeArgs($this->serv, array($req))); - } -} From cc031e164b9dd1f1581978bc2c947fb94d99861f Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Mon, 15 Feb 2016 17:19:29 -0500 Subject: [PATCH 14/52] Inject RequestVerifier into ServerNegotiator --- src/Ratchet/WebSocket/WsServer.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 9878495..fa36e5f 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -5,12 +5,15 @@ use Ratchet\ConnectionInterface; use Ratchet\Http\HttpServerInterface; use Ratchet\Http\CloseResponseTrait; use Psr\Http\Message\RequestInterface; -use GuzzleHttp\Psr7 as gPsr; -use Ratchet\RFC6455\Messaging\FrameInterface; use Ratchet\RFC6455\Messaging\MessageInterface; -use Ratchet\RFC6455\Messaging\MessageBuffer; -use React\EventLoop\LoopInterface; +use Ratchet\RFC6455\Messaging\FrameInterface; use Ratchet\RFC6455\Messaging\Frame; +use Ratchet\RFC6455\Messaging\MessageBuffer; +use Ratchet\RFC6455\Messaging\CloseFrameChecker; +use Ratchet\RFC6455\Handshake\ServerNegotiator; +use Ratchet\RFC6455\Handshake\RequestVerifier; +use React\EventLoop\LoopInterface; +use GuzzleHttp\Psr7 as gPsr; /** * The adapter to handle WebSocket requests/responses @@ -60,8 +63,8 @@ class WsServer implements HttpServerInterface { $this->delegate = $component; $this->connections = new \SplObjectStorage; - $this->closeFrameChecker = new \Ratchet\RFC6455\Messaging\CloseFrameChecker; - $this->handshakeNegotiator = new \Ratchet\RFC6455\Handshake\ServerNegotiator; + $this->closeFrameChecker = new CloseFrameChecker; + $this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier); if ($component instanceof WsServerInterface) { $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); From 5937851faca4d39d51e4fd74b956be46188de590 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 17 Feb 2016 17:29:03 -0500 Subject: [PATCH 15/52] Skip PDO test if extension missing --- .../Storage/VirtualSessionStoragePDOTest.php | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php b/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php index 9909bce..2727484 100644 --- a/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php +++ b/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php @@ -1,15 +1,11 @@ markTestSkipped('Session test requires PDO and pdo_sqlite'); + } + $schema = <<_virtualSessionStorage->registerBag(new AttributeBag()); } - public function tearDown() - { + public function tearDown() { unlink($this->_pathToDB); } - public function testStartWithDSN() - { + public function testStartWithDSN() { $this->_virtualSessionStorage->start(); $this->assertTrue($this->_virtualSessionStorage->isStarted()); } - - } From 214a8db846b3b4e97bfc065e25707afe2381601d Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 21 Feb 2016 13:19:28 -0500 Subject: [PATCH 16/52] Use RFC tag --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a34968e..93e7ade 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "php": ">=5.3.9" , "react/socket": "^0.3 || ^0.4" , "guzzlehttp/psr7": "^1.0" - , "ratchet/rfc6455": "dev-psr7-multi-streamer" + , "ratchet/rfc6455": "^0.2" , "symfony/http-foundation": "^2.2" , "symfony/routing": "^2.2" } From 25c3e4fb3bc8e711401e68aaae2a4cc9d49689ba Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 21 Feb 2016 13:21:52 -0500 Subject: [PATCH 17/52] Re-enable strict sub protocol check --- src/Ratchet/WebSocket/WsServer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index fa36e5f..61d42b6 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -65,6 +65,7 @@ class WsServer implements HttpServerInterface { $this->closeFrameChecker = new CloseFrameChecker; $this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier); + $this->handshakeNegotiator->setStrictSubProtocolCheck(true); if ($component instanceof WsServerInterface) { $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); @@ -202,4 +203,4 @@ class WsServer implements HttpServerInterface { } }); } -} \ No newline at end of file +} From 2283bdf28856db73eda842accdd584256ccdd0e8 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 24 Feb 2016 18:55:04 -0500 Subject: [PATCH 18/52] Binary messaging support Least obtrusive interface --- src/Ratchet/WebSocket/BinaryMessageInterface.php | 8 ++++++++ src/Ratchet/WebSocket/WsConnection.php | 4 ++-- tests/autobahn/fuzzingclient-quick.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/Ratchet/WebSocket/BinaryMessageInterface.php diff --git a/src/Ratchet/WebSocket/BinaryMessageInterface.php b/src/Ratchet/WebSocket/BinaryMessageInterface.php new file mode 100644 index 0000000..db28c0b --- /dev/null +++ b/src/Ratchet/WebSocket/BinaryMessageInterface.php @@ -0,0 +1,8 @@ +WebSocket->closing) { if (!($msg instanceof DataInterface)) { - $msg = new Frame($msg); + $msg = new Frame($msg, true, ((boolean)$isBinary ? Frame::OP_TEXT : Frame::OP_BINARY)); } $this->getConnection()->send($msg->getContents()); diff --git a/tests/autobahn/fuzzingclient-quick.json b/tests/autobahn/fuzzingclient-quick.json index 3023fb8..c92e805 100644 --- a/tests/autobahn/fuzzingclient-quick.json +++ b/tests/autobahn/fuzzingclient-quick.json @@ -7,6 +7,6 @@ ] , "cases": ["*"] - , "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] + , "exclude-cases": [] , "exclude-agent-cases": {} } From bfbeea645861845af3142205438c6dfbafb7aa9b Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 25 Feb 2016 08:58:53 -0500 Subject: [PATCH 19/52] Remove 5.3 references --- .travis.yml | 1 - README.md | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0b9273..927d1d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 5.3 - 5.4 - 5.5 - 5.6 diff --git a/README.md b/README.md index 5cc5976..2674282 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://secure.travis-ci.org/ratchetphp/Ratchet.png?branch=master)](http://travis-ci.org/ratchetphp/Ratchet) [![Latest Stable Version](https://poser.pugx.org/cboden/ratchet/v/stable.png)](https://packagist.org/packages/cboden/ratchet) -A PHP 5.3 library for asynchronously serving WebSockets. +A PHP 5.4 library for asynchronously serving WebSockets. Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components. ##WebSocket Compliance @@ -19,8 +19,6 @@ To avoid proxy/firewall blockage it's recommended WebSockets are requested on po In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines. You can find more details in the [server conf docs](http://socketo.me/docs/deploy#serverconfiguration). -PHP 5.3.9 (or higher) is required. If you have access, PHP 5.4 (or higher) is *highly* recommended for its performance improvements. - ### Documentation User and API documentation is available on Ratchet's website: http://socketo.me @@ -87,4 +85,4 @@ class MyChat implements MessageComponentInterface { var conn = new WebSocket('ws://localhost:8080/echo'); conn.onmessage = function(e) { console.log(e.data); }; conn.send('Hello Me!'); -``` \ No newline at end of file +``` From d97ca0f3cbfbbff54092cc25288ca6f8d2c28fac Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 25 Feb 2016 19:18:46 -0500 Subject: [PATCH 20/52] Switched index array storage to class container --- src/Ratchet/WebSocket/ConnContext.php | 20 ++++++++++++++++++++ src/Ratchet/WebSocket/WsServer.php | 10 +++++----- 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/Ratchet/WebSocket/ConnContext.php diff --git a/src/Ratchet/WebSocket/ConnContext.php b/src/Ratchet/WebSocket/ConnContext.php new file mode 100644 index 0000000..7aa6322 --- /dev/null +++ b/src/Ratchet/WebSocket/ConnContext.php @@ -0,0 +1,20 @@ +connection = $conn; + $this->streamer = $streamer; + } +} diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 61d42b6..83fbce2 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -115,7 +115,7 @@ class WsServer implements HttpServerInterface { $this->ueFlowFactory ); - $this->connections->attach($conn, [$wsConn, $streamer]); + $this->connections->attach($conn, new ConnContext($wsConn, $streamer)); return $this->delegate->onOpen($wsConn); } @@ -129,7 +129,7 @@ class WsServer implements HttpServerInterface { } $context = $this->connections[$from]; - $context[1]->onData($msg); + $context->streamer->onData($msg); } /** @@ -140,7 +140,7 @@ class WsServer implements HttpServerInterface { $context = $this->connections[$conn]; $this->connections->detach($conn); - $this->delegate->onClose($context[0]); + $this->delegate->onClose($context->conn); } } @@ -150,7 +150,7 @@ class WsServer implements HttpServerInterface { public function onError(ConnectionInterface $conn, \Exception $e) { if ($this->connections->contains($conn)) { $context = $this->connections[$conn]; - $this->delegate->onError($context[0], $e); + $this->delegate->onError($context->connection, $e); } else { $conn->close(); } @@ -196,7 +196,7 @@ class WsServer implements HttpServerInterface { foreach ($this->connections as $key => $conn) { $context = $this->connections[$conn]; - $wsConn = $context[0]; + $wsConn = $context->connection; $wsConn->send($lastPing); $pingedConnections->attach($wsConn); From 5137c2122a96f24b31a473570cdb4b5b3f36d5e5 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 25 Feb 2016 19:19:58 -0500 Subject: [PATCH 21/52] Use httpRequest in favour of WebSocket->request --- src/Ratchet/WebSocket/WsServer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 83fbce2..57f1ebe 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -87,11 +87,10 @@ class WsServer implements HttpServerInterface { throw new \UnexpectedValueException('$request can not be null'); } - $conn->httpRequest = $request; // This will replace ->WebSocket->request + $conn->httpRequest = $request; $conn->WebSocket = new \StdClass; $conn->WebSocket->closing = false; - $conn->WebSocket->request = $request; // deprecated $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); From 01e1d159e830b2bc7f4f243c845bdddb13d23755 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 25 Feb 2016 19:26:42 -0500 Subject: [PATCH 22/52] Naming conventions --- src/Ratchet/WebSocket/ConnContext.php | 6 +++--- src/Ratchet/WebSocket/WsServer.php | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Ratchet/WebSocket/ConnContext.php b/src/Ratchet/WebSocket/ConnContext.php index 7aa6322..2eba782 100644 --- a/src/Ratchet/WebSocket/ConnContext.php +++ b/src/Ratchet/WebSocket/ConnContext.php @@ -11,10 +11,10 @@ class ConnContext { /** * @var \Ratchet\RFC6455\Messaging\MessageBuffer; */ - public $streamer; + public $buffer; - public function __construct(WsConnection $conn, MessageBuffer $streamer) { + public function __construct(WsConnection $conn, MessageBuffer $buffer) { $this->connection = $conn; - $this->streamer = $streamer; + $this->buffer = $buffer; } } diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 57f1ebe..0238e23 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -127,8 +127,7 @@ class WsServer implements HttpServerInterface { return; } - $context = $this->connections[$from]; - $context->streamer->onData($msg); + $this->connections[$from]->buffer->onData($msg); } /** @@ -139,7 +138,7 @@ class WsServer implements HttpServerInterface { $context = $this->connections[$conn]; $this->connections->detach($conn); - $this->delegate->onClose($context->conn); + $this->delegate->onClose($context->connection); } } @@ -148,8 +147,7 @@ class WsServer implements HttpServerInterface { */ public function onError(ConnectionInterface $conn, \Exception $e) { if ($this->connections->contains($conn)) { - $context = $this->connections[$conn]; - $this->delegate->onError($context->connection, $e); + $this->delegate->onError($this->connections[$from]->connection, $e); } else { $conn->close(); } @@ -194,8 +192,7 @@ class WsServer implements HttpServerInterface { $lastPing = new Frame(uniqid(), true, Frame::OP_PING); foreach ($this->connections as $key => $conn) { - $context = $this->connections[$conn]; - $wsConn = $context->connection; + $wsConn = $this->connections[$from]->connection; $wsConn->send($lastPing); $pingedConnections->attach($wsConn); From bbced3b7657e16254ce1af1a689b7aed3af55475 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 27 Feb 2016 13:02:56 -0500 Subject: [PATCH 23/52] Fixed frame creation type bug --- src/Ratchet/WebSocket/WsConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ratchet/WebSocket/WsConnection.php b/src/Ratchet/WebSocket/WsConnection.php index 4082fa9..3c34c9f 100644 --- a/src/Ratchet/WebSocket/WsConnection.php +++ b/src/Ratchet/WebSocket/WsConnection.php @@ -15,7 +15,7 @@ class WsConnection extends AbstractConnectionDecorator { public function send($msg, $isBinary = false) { if (!$this->WebSocket->closing) { if (!($msg instanceof DataInterface)) { - $msg = new Frame($msg, true, ((boolean)$isBinary ? Frame::OP_TEXT : Frame::OP_BINARY)); + $msg = new Frame($msg, true, ((boolean)$isBinary ? Frame::OP_BINARY : Frame::OP_TEXT)); } $this->getConnection()->send($msg->getContents()); From 365e8702fff6b65353c951785a68ca996c1e922b Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 27 Feb 2016 13:03:12 -0500 Subject: [PATCH 24/52] WebSocket Binary message parent change --- src/Ratchet/WebSocket/BinaryMessageInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/BinaryMessageInterface.php b/src/Ratchet/WebSocket/BinaryMessageInterface.php index db28c0b..e5dc022 100644 --- a/src/Ratchet/WebSocket/BinaryMessageInterface.php +++ b/src/Ratchet/WebSocket/BinaryMessageInterface.php @@ -1,8 +1,8 @@ Date: Sat, 27 Feb 2016 13:04:10 -0500 Subject: [PATCH 25/52] Pass message value, isBinary indicator Passing message contents instead of string to keep BC Passing isBinary indicator regardless of interface --- src/Ratchet/WebSocket/WsServer.php | 2 +- tests/autobahn/bin/fuzzingserver.php | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 61d42b6..0bcb50a 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -106,7 +106,7 @@ class WsServer implements HttpServerInterface { $streamer = new MessageBuffer( $this->closeFrameChecker, function(MessageInterface $msg) use ($wsConn) { - $this->delegate->onMessage($wsConn, $msg); + $this->delegate->onMessage($wsConn, $msg->getPayload(), $msg->isBinary()); }, function(FrameInterface $frame) use ($wsConn) { $this->onControlFrame($frame, $wsConn); diff --git a/tests/autobahn/bin/fuzzingserver.php b/tests/autobahn/bin/fuzzingserver.php index 093a5cf..4b0fb84 100644 --- a/tests/autobahn/bin/fuzzingserver.php +++ b/tests/autobahn/bin/fuzzingserver.php @@ -2,12 +2,18 @@ require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php'; +class BinaryEcho extends \Ratchet\Server\EchoServer implements \Ratchet\WebSocket\BinaryMessageInterface { + public function onMessage(\Ratchet\ConnectionInterface $from, $msg, $isBinary = false) { + $from->send($msg, $isBinary); + } +} + $port = $argc > 1 ? $argv[1] : 8000; $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); $loop = new $impl; $sock = new React\Socket\Server($loop); - $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); + $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new BinaryEcho)); $sock->listen($port, '0.0.0.0'); From e3aecdf02108821e3de447fce479d80a5fbf0c95 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Tue, 1 Mar 2016 14:11:15 -0500 Subject: [PATCH 26/52] Alternative approach to binary messaging A new interface the dev can implement that will pass a Message object to the devs instance. The object has properties regarding binary/text --- .../WebSocket/BinaryMessageInterface.php | 8 ----- .../WebSocket/MessageCallableInterface.php | 8 +++++ .../WebSocket/MessageComponentInterface.php | 6 ++++ src/Ratchet/WebSocket/WsServer.php | 31 +++++++++++++++---- tests/autobahn/bin/fuzzingserver.php | 16 ++++++++-- 5 files changed, 52 insertions(+), 17 deletions(-) delete mode 100644 src/Ratchet/WebSocket/BinaryMessageInterface.php create mode 100644 src/Ratchet/WebSocket/MessageCallableInterface.php create mode 100644 src/Ratchet/WebSocket/MessageComponentInterface.php diff --git a/src/Ratchet/WebSocket/BinaryMessageInterface.php b/src/Ratchet/WebSocket/BinaryMessageInterface.php deleted file mode 100644 index e5dc022..0000000 --- a/src/Ratchet/WebSocket/BinaryMessageInterface.php +++ /dev/null @@ -1,8 +0,0 @@ -msgCb = function(ConnectionInterface $conn, MessageInterface $msg) { + $this->delegate->onMessage($conn, $msg); + }; + } elseif ($component instanceof DataComponentInterface) { + $this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) { + $this->delegate->onMessage($conn, $msg->getPayload()); + }; + } else { + throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface'); + } + $this->delegate = $component; $this->connections = new \SplObjectStorage; @@ -106,7 +124,8 @@ class WsServer implements HttpServerInterface { $streamer = new MessageBuffer( $this->closeFrameChecker, function(MessageInterface $msg) use ($wsConn) { - $this->delegate->onMessage($wsConn, $msg->getPayload(), $msg->isBinary()); + $cb = $this->msgCb; + $cb($wsConn, $msg); }, function(FrameInterface $frame) use ($wsConn) { $this->onControlFrame($frame, $wsConn); diff --git a/tests/autobahn/bin/fuzzingserver.php b/tests/autobahn/bin/fuzzingserver.php index 4b0fb84..5dcbf66 100644 --- a/tests/autobahn/bin/fuzzingserver.php +++ b/tests/autobahn/bin/fuzzingserver.php @@ -1,10 +1,20 @@ send($msg, $isBinary); +class BinaryEcho implements \Ratchet\WebSocket\MessageComponentInterface { + public function onMessage(ConnectionInterface $from, \Ratchet\RFC6455\Messaging\MessageInterface $msg) { + $from->send($msg); + } + + public function onOpen(ConnectionInterface $conn) { + } + + public function onClose(ConnectionInterface $conn) { + } + + public function onError(ConnectionInterface $conn, \Exception $e) { } } From 237615890d6eb84abc14cc8182c0ccd66fd02b76 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Tue, 1 Mar 2016 14:16:35 -0500 Subject: [PATCH 27/52] Remove edited API for sending binary (should pass message object instead) --- src/Ratchet/WebSocket/WsConnection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/WsConnection.php b/src/Ratchet/WebSocket/WsConnection.php index 3c34c9f..d2d04ef 100644 --- a/src/Ratchet/WebSocket/WsConnection.php +++ b/src/Ratchet/WebSocket/WsConnection.php @@ -12,10 +12,10 @@ class WsConnection extends AbstractConnectionDecorator { /** * {@inheritdoc} */ - public function send($msg, $isBinary = false) { + public function send($msg) { if (!$this->WebSocket->closing) { if (!($msg instanceof DataInterface)) { - $msg = new Frame($msg, true, ((boolean)$isBinary ? Frame::OP_BINARY : Frame::OP_TEXT)); + $msg = new Frame($msg); } $this->getConnection()->send($msg->getContents()); From 3541db2dc7d2856b21204a46aaa696c3dc10b5fa Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 12 Mar 2016 10:29:26 -0500 Subject: [PATCH 28/52] Version indicators, remove UTF-8 disabling tests --- Makefile | 1 - src/Ratchet/ConnectionInterface.php | 2 +- tests/autobahn/bin/fuzzingserver-noutf8.php | 17 ----------------- tests/autobahn/fuzzingclient-all.json | 9 ++++----- 4 files changed, 5 insertions(+), 24 deletions(-) delete mode 100644 tests/autobahn/bin/fuzzingserver-noutf8.php diff --git a/Makefile b/Makefile index 4ec818f..e4af1d2 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ cover: abtests: ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8001 LibEvent & ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8002 StreamSelect & - ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-noutf8.php 8003 StreamSelect & ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv & wstest -m testeeserver -w ws://localhost:8000 & sleep 1 diff --git a/src/Ratchet/ConnectionInterface.php b/src/Ratchet/ConnectionInterface.php index 5c07a2d..8e2cee8 100644 --- a/src/Ratchet/ConnectionInterface.php +++ b/src/Ratchet/ConnectionInterface.php @@ -5,7 +5,7 @@ namespace Ratchet; * The version of Ratchet being used * @var string */ -const VERSION = 'Ratchet/0.3.4'; +const VERSION = 'Ratchet/0.4'; /** * A proxy object representing a connection to the application diff --git a/tests/autobahn/bin/fuzzingserver-noutf8.php b/tests/autobahn/bin/fuzzingserver-noutf8.php deleted file mode 100644 index 5ce1cb4..0000000 --- a/tests/autobahn/bin/fuzzingserver-noutf8.php +++ /dev/null @@ -1,17 +0,0 @@ - 1 ? $argv[1] : 8000; - $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); - - $loop = new $impl; - $sock = new React\Socket\Server($loop); - $web = new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer); - $app = new Ratchet\Http\HttpServer($web); - $web->setEncodingChecks(false); - - $sock->listen($port, '0.0.0.0'); - - $server = new Ratchet\Server\IoServer($app, $sock, $loop); - $server->run(); diff --git a/tests/autobahn/fuzzingclient-all.json b/tests/autobahn/fuzzingclient-all.json index dd9cddb..0494cf3 100644 --- a/tests/autobahn/fuzzingclient-all.json +++ b/tests/autobahn/fuzzingclient-all.json @@ -3,14 +3,13 @@ , "outdir": "reports/ab" , "servers": [ - {"agent": "Ratchet/0.3 libevent", "url": "ws://localhost:8001", "options": {"version": 18}} - , {"agent": "Ratchet/0.3 libev", "url": "ws://localhost:8004", "options": {"version": 18}} - , {"agent": "Ratchet/0.3 streams", "url": "ws://localhost:8002", "options": {"version": 18}} - , {"agent": "Ratchet/0.3 -utf8", "url": "ws://localhost:8003", "options": {"version": 18}} + {"agent": "Ratchet/0.4 libevent", "url": "ws://localhost:8001", "options": {"version": 18}} + , {"agent": "Ratchet/0.4 libev", "url": "ws://localhost:8004", "options": {"version": 18}} + , {"agent": "Ratchet/0.4 streams", "url": "ws://localhost:8002", "options": {"version": 18}} , {"agent": "AutobahnTestSuite/0.5.9", "url": "ws://localhost:8000", "options": {"version": 18}} ] , "cases": ["*"] - , "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] + , "exclude-cases": [] , "exclude-agent-cases": {} } From 9f83763a078de390e3b0574700868f06eae7cfbb Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Mon, 14 Mar 2016 11:34:39 -0400 Subject: [PATCH 29/52] Update readme and changelog for upcoming release --- CHANGELOG.md | 11 +++++++++++ README.md | 7 +------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee7f13..6d65ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ CHANGELOG --- +* 0.4 (2016-) + + * BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object + * Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface + * Added heartbeat support via ping/pong in WsServer + * BC: No longer support old (and insecure) Hixie76 and Hybi protocols + * BC: No longer support disabling UTF-8 checks + * BC: The Session component implements HttpServerInterface instead of WsServerInterface + * BC: PHP 5.3 no longer supported + * Significant performance enhancements + * 0.3.4 (2015-12-23) * BF: Edge case where version check wasn't run on message coalesce diff --git a/README.md b/README.md index 2674282..f0c8ac2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,12 @@ #Ratchet [![Build Status](https://secure.travis-ci.org/ratchetphp/Ratchet.png?branch=master)](http://travis-ci.org/ratchetphp/Ratchet) +[![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)](http://socketo.me/reports/ab/) [![Latest Stable Version](https://poser.pugx.org/cboden/ratchet/v/stable.png)](https://packagist.org/packages/cboden/ratchet) A PHP 5.4 library for asynchronously serving WebSockets. Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components. -##WebSocket Compliance - -* Supports the RFC6455, HyBi-10+, and Hixie76 protocol versions (at the same time) -* Tested on Chrome 13+, Firefox 6+, Safari 5+, iOS 4.2+, IE 8+ -* Ratchet [passes](http://socketo.me/reports/ab/) the [Autobahn Testsuite](http://autobahn.ws/testsuite) (non-binary messages) - ##Requirements Shell access is required and root access is recommended. From 340f036d371c38c187935c0ffce3e10378a63b06 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Mon, 14 Mar 2016 11:35:36 -0400 Subject: [PATCH 30/52] Working link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0c8ac2..0209db7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ #Ratchet [![Build Status](https://secure.travis-ci.org/ratchetphp/Ratchet.png?branch=master)](http://travis-ci.org/ratchetphp/Ratchet) -[![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)](http://socketo.me/reports/ab/) +[![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)](http://socketo.me/reports/ab/index.html) [![Latest Stable Version](https://poser.pugx.org/cboden/ratchet/v/stable.png)](https://packagist.org/packages/cboden/ratchet) A PHP 5.4 library for asynchronously serving WebSockets. From 695995d3e71c6ce1064951f1549e8593f2824522 Mon Sep 17 00:00:00 2001 From: Kurairaito Date: Fri, 8 Apr 2016 21:24:28 +0200 Subject: [PATCH 31/52] Update WsServer.php fixed crash when keepalive was called --- src/Ratchet/WebSocket/WsServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index c05fe84..d80b665 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -211,7 +211,7 @@ class WsServer implements HttpServerInterface { $lastPing = new Frame(uniqid(), true, Frame::OP_PING); foreach ($this->connections as $key => $conn) { - $wsConn = $this->connections[$from]->connection; + $wsConn = $this->connections[$conn]->connection; $wsConn->send($lastPing); $pingedConnections->attach($wsConn); From a968ea2e43dd8f3b7f1e857a69ba0102b0e56a55 Mon Sep 17 00:00:00 2001 From: Jaap Moolenaar Date: Tue, 17 May 2016 15:56:59 +0200 Subject: [PATCH 32/52] The variable $from should be the variable (parameter) $conn --- src/Ratchet/WebSocket/WsServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index c05fe84..43c98a6 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -166,7 +166,7 @@ class WsServer implements HttpServerInterface { */ public function onError(ConnectionInterface $conn, \Exception $e) { if ($this->connections->contains($conn)) { - $this->delegate->onError($this->connections[$from]->connection, $e); + $this->delegate->onError($this->connections[$conn]->connection, $e); } else { $conn->close(); } From 85ed94d5cb6f3c51aee0317879a51df61e8e58c6 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Thu, 26 May 2016 00:39:14 -0400 Subject: [PATCH 33/52] Fix unsolicited pong crash with keep alive enabled. Fixes #430 --- src/Ratchet/WebSocket/WsServer.php | 2 +- tests/autobahn/bin/fuzzingserver.php | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 3540059..8ded680 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -192,7 +192,7 @@ class WsServer implements HttpServerInterface { } public function enableKeepAlive(LoopInterface $loop, $interval = 30) { - $lastPing = null; + $lastPing = new Frame(uniqid(), true, Frame::OP_PING); $pingedConnections = new \SplObjectStorage; $splClearer = new \SplObjectStorage; diff --git a/tests/autobahn/bin/fuzzingserver.php b/tests/autobahn/bin/fuzzingserver.php index 5dcbf66..7ab81a9 100644 --- a/tests/autobahn/bin/fuzzingserver.php +++ b/tests/autobahn/bin/fuzzingserver.php @@ -23,7 +23,14 @@ class BinaryEcho implements \Ratchet\WebSocket\MessageComponentInterface { $loop = new $impl; $sock = new React\Socket\Server($loop); - $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new BinaryEcho)); + + $wsServer = new Ratchet\WebSocket\WsServer(new BinaryEcho); + // This is enabled to test https://github.com/ratchetphp/Ratchet/issues/430 + // The time is left at 10 minutes so that it will not try to every ping anything + // This causes the Ratchet server to crash on test 2.7 + $wsServer->enableKeepAlive($loop, 600); + + $app = new Ratchet\Http\HttpServer($wsServer); $sock->listen($port, '0.0.0.0'); From b8967b999af7473e9d0fd150d523120ddb492036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 6 Feb 2017 08:09:41 +0100 Subject: [PATCH 34/52] Update Socket component to v0.5 --- composer.json | 2 +- src/Ratchet/App.php | 12 ++++++------ src/Ratchet/Server/IoServer.php | 8 +++++--- tests/autobahn/bin/fuzzingserver-noutf8.php | 4 +--- tests/autobahn/bin/fuzzingserver.php | 4 +--- tests/unit/Server/IoServerTest.php | 5 ++--- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 80a3aa5..d396d9f 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ } , "require": { "php": ">=5.3.9" - , "react/socket": "^0.3 || ^0.4" + , "react/socket": "^0.5" , "guzzle/http": "^3.6" , "symfony/http-foundation": "^2.2|^3.0" , "symfony/routing": "^2.2|^3.0" diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index c52a2c7..b70fd4b 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -76,8 +76,7 @@ class App { $this->httpHost = $httpHost; $this->port = $port; - $socket = new Reactor($loop); - $socket->listen($port, $address); + $socket = new Reactor($address . ':' . $port, $loop); $this->routes = new RouteCollection; $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); @@ -85,13 +84,14 @@ class App { $policy = new FlashPolicy; $policy->addAllowedAccess($httpHost, 80); $policy->addAllowedAccess($httpHost, $port); - $flashSock = new Reactor($loop); - $this->flashServer = new IoServer($policy, $flashSock); + if (80 == $port) { - $flashSock->listen(843, '0.0.0.0'); + $flashUri = '0.0.0.0:843'; } else { - $flashSock->listen(8843); + $flashUri = 8843; } + $flashSock = new Reactor($flashUri, $loop); + $this->flashServer = new IoServer($policy, $flashSock); } /** diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 921c7b1..9cf0798 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -66,8 +66,7 @@ class IoServer { */ public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { $loop = LoopFactory::create(); - $socket = new Reactor($loop); - $socket->listen($port, $address); + $socket = new Reactor($address . ':' . $port, $loop); return new static($component, $socket, $loop); } @@ -94,7 +93,10 @@ class IoServer { $conn->decor = new IoConnection($conn); $conn->decor->resourceId = (int)$conn->stream; - $conn->decor->remoteAddress = $conn->getRemoteAddress(); + $conn->decor->remoteAddress = trim( + parse_url('tcp://' . $conn->getRemoteAddress(), PHP_URL_HOST), + '[]' + ); $this->app->onOpen($conn->decor); diff --git a/tests/autobahn/bin/fuzzingserver-noutf8.php b/tests/autobahn/bin/fuzzingserver-noutf8.php index 5ce1cb4..0a92ba4 100644 --- a/tests/autobahn/bin/fuzzingserver-noutf8.php +++ b/tests/autobahn/bin/fuzzingserver-noutf8.php @@ -6,12 +6,10 @@ $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); $loop = new $impl; - $sock = new React\Socket\Server($loop); + $sock = new React\Socket\Server('0.0.0.0:' . $port, $loop); $web = new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer); $app = new Ratchet\Http\HttpServer($web); $web->setEncodingChecks(false); - $sock->listen($port, '0.0.0.0'); - $server = new Ratchet\Server\IoServer($app, $sock, $loop); $server->run(); diff --git a/tests/autobahn/bin/fuzzingserver.php b/tests/autobahn/bin/fuzzingserver.php index 093a5cf..62e9bc6 100644 --- a/tests/autobahn/bin/fuzzingserver.php +++ b/tests/autobahn/bin/fuzzingserver.php @@ -6,10 +6,8 @@ $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); $loop = new $impl; - $sock = new React\Socket\Server($loop); + $sock = new React\Socket\Server('0.0.0.0:' . $port, $loop); $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); - $sock->listen($port, '0.0.0.0'); - $server = new Ratchet\Server\IoServer($app, $sock, $loop); $server->run(); diff --git a/tests/unit/Server/IoServerTest.php b/tests/unit/Server/IoServerTest.php index 808098a..ec54b5d 100644 --- a/tests/unit/Server/IoServerTest.php +++ b/tests/unit/Server/IoServerTest.php @@ -20,10 +20,9 @@ class IoServerTest extends \PHPUnit_Framework_TestCase { $this->app = $this->getMock('\\Ratchet\\MessageComponentInterface'); $loop = new StreamSelectLoop; - $this->reactor = new Server($loop); - $this->reactor->listen(0); + $this->reactor = new Server(0, $loop); - $this->port = $this->reactor->getPort(); + $this->port = parse_url('tcp://' . $this->reactor->getAddress(), PHP_URL_PORT); $this->server = new IoServer($this->app, $this->reactor, $loop); } From cc2bc79455f47f772c628dd32e313125f294a243 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 16 Feb 2017 20:34:04 -0500 Subject: [PATCH 35/52] Changelog with new react/socket --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 821414f..959d889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ CHANGELOG * BC: No longer support disabling UTF-8 checks * BC: The Session component implements HttpServerInterface instead of WsServerInterface * BC: PHP 5.3 no longer supported + * BC: Use react/socket 0.5 * Significant performance enhancements * 0.3.6 (2017-01-06) From 369227dc1c0e5e016e729d8ab4789a0fd0ba85e4 Mon Sep 17 00:00:00 2001 From: Eliseu dos Santos Date: Tue, 21 Feb 2017 09:22:38 -0300 Subject: [PATCH 36/52] Add WSS support using recently added class SecureServer from React. --- src/Ratchet/Server/IoServer.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 921c7b1..4db1c7b 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -5,6 +5,7 @@ use React\EventLoop\LoopInterface; use React\Socket\ServerInterface; use React\EventLoop\Factory as LoopFactory; use React\Socket\Server as Reactor; +use React\Socket\SecureServer as SecureReactor; /** * Creates an open-ended socket to listen on a port for incoming connections. @@ -64,9 +65,12 @@ class IoServer { * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) * @return IoServer */ - public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { + public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', $sslconf = array()) { $loop = LoopFactory::create(); $socket = new Reactor($loop); + if (!empty($sslconf)) { + $socket = new SecureReactor($socket, $loop, $sslconf); + } $socket->listen($port, $address); return new static($component, $socket, $loop); From 1fd6f243718c93921a0cc5d0df81a8d3e07f1510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Mar 2017 15:13:39 +0100 Subject: [PATCH 37/52] Fix event arguments --- src/Ratchet/Server/IoServer.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 9cf0798..b2071ed 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -24,6 +24,7 @@ class IoServer { /** * Array of React event handlers * @var \SplFixedArray + * @deprecated exists BC only, now unused */ protected $handlers; @@ -53,9 +54,6 @@ class IoServer { $socket->on('connection', array($this, 'handleConnect')); $this->handlers = new \SplFixedArray(3); - $this->handlers[0] = array($this, 'handleData'); - $this->handlers[1] = array($this, 'handleEnd'); - $this->handlers[2] = array($this, 'handleError'); } /** @@ -100,9 +98,16 @@ class IoServer { $this->app->onOpen($conn->decor); - $conn->on('data', $this->handlers[0]); - $conn->on('end', $this->handlers[1]); - $conn->on('error', $this->handlers[2]); + $that = $this; + $conn->on('data', function ($data) use ($conn) { + $that->handleData($data, $conn); + }); + $conn->on('close', function () use ($conn, $that) { + $that->handleEnd($conn); + }); + $conn->on('error', function (\Exception $e) use ($conn, $that) { + $that->handleError($e, $conn); + }); } /** From b00da833ced5eef568280fe3d2fcce363cb33f96 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Fri, 10 Mar 2017 09:22:43 -0500 Subject: [PATCH 38/52] Use $this in closure, fixed ref to handleData --- src/Ratchet/Server/IoServer.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index b2071ed..eebca3e 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -98,15 +98,14 @@ class IoServer { $this->app->onOpen($conn->decor); - $that = $this; $conn->on('data', function ($data) use ($conn) { - $that->handleData($data, $conn); + $this->handleData($data, $conn); }); - $conn->on('close', function () use ($conn, $that) { - $that->handleEnd($conn); + $conn->on('close', function () use ($conn) { + $this->handleEnd($conn); }); - $conn->on('error', function (\Exception $e) use ($conn, $that) { - $that->handleError($e, $conn); + $conn->on('error', function (\Exception $e) use ($conn) { + $this->handleError($e, $conn); }); } From 22e500d02a99eee614beedb11e15479edb308d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Mar 2017 15:13:39 +0100 Subject: [PATCH 39/52] Fix event arguments --- src/Ratchet/Server/IoServer.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 9cf0798..4031638 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -24,6 +24,7 @@ class IoServer { /** * Array of React event handlers * @var \SplFixedArray + * @deprecated exists BC only, now unused */ protected $handlers; @@ -53,9 +54,6 @@ class IoServer { $socket->on('connection', array($this, 'handleConnect')); $this->handlers = new \SplFixedArray(3); - $this->handlers[0] = array($this, 'handleData'); - $this->handlers[1] = array($this, 'handleEnd'); - $this->handlers[2] = array($this, 'handleError'); } /** @@ -100,9 +98,16 @@ class IoServer { $this->app->onOpen($conn->decor); - $conn->on('data', $this->handlers[0]); - $conn->on('end', $this->handlers[1]); - $conn->on('error', $this->handlers[2]); + $that = $this; + $conn->on('data', function ($data) use ($conn, $that) { + $that->handleData($data, $conn); + }); + $conn->on('close', function () use ($conn, $that) { + $that->handleEnd($conn); + }); + $conn->on('error', function (\Exception $e) use ($conn, $that) { + $that->handleError($e, $conn); + }); } /** From 13c9fdfb152805e441394786602484d9df48e8d8 Mon Sep 17 00:00:00 2001 From: Eliseu dos Santos Date: Mon, 13 Mar 2017 07:27:13 -0300 Subject: [PATCH 40/52] Changed sslConf parameter validation, according to review on PR --- src/Ratchet/Server/IoServer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 4db1c7b..0d18c8d 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -65,10 +65,10 @@ class IoServer { * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) * @return IoServer */ - public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', $sslconf = array()) { + public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', $sslconf = null) { $loop = LoopFactory::create(); $socket = new Reactor($loop); - if (!empty($sslconf)) { + if (is_array($sslconf)) { $socket = new SecureReactor($socket, $loop, $sslconf); } $socket->listen($port, $address); From e3a97b66618ce8d2b444b0b56496b0e8a4118b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 17 Apr 2017 17:36:23 +0200 Subject: [PATCH 41/52] Forward-compatibility with Socket v0.7 and Socket v0.6 Socket v0.7 and v0.6 contain some major changes, but this does not affect usage within Ratchet, so it's actually compatible with all latest releases. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d396d9f..a070212 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ } , "require": { "php": ">=5.3.9" - , "react/socket": "^0.5" + , "react/socket": "^0.7 || ^0.6 || ^0.5" , "guzzle/http": "^3.6" , "symfony/http-foundation": "^2.2|^3.0" , "symfony/routing": "^2.2|^3.0" From a86be3c52696dc02a7c08c0a782b46f67518f21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 19 Jul 2017 16:12:26 +0200 Subject: [PATCH 42/52] Forward-compatibility with future Socket v1.0 and Socket v0.8 Socket v0.8 only contains some minor breaking changes, which can be circumvented by ignoring URI schemes here. Future Socket v1.0 will not contain any BC breaks, so it's actually compatible with the last release. --- composer.json | 2 +- src/Ratchet/Server/IoServer.php | 5 +++-- tests/unit/Server/IoServerTest.php | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index a070212..993e4c7 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ } , "require": { "php": ">=5.3.9" - , "react/socket": "^0.7 || ^0.6 || ^0.5" + , "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5" , "guzzle/http": "^3.6" , "symfony/http-foundation": "^2.2|^3.0" , "symfony/routing": "^2.2|^3.0" diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 4031638..3fcf76c 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -89,10 +89,11 @@ class IoServer { */ public function handleConnect($conn) { $conn->decor = new IoConnection($conn); - $conn->decor->resourceId = (int)$conn->stream; + + $uri = $conn->getRemoteAddress(); $conn->decor->remoteAddress = trim( - parse_url('tcp://' . $conn->getRemoteAddress(), PHP_URL_HOST), + parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST), '[]' ); diff --git a/tests/unit/Server/IoServerTest.php b/tests/unit/Server/IoServerTest.php index ec54b5d..284fbde 100644 --- a/tests/unit/Server/IoServerTest.php +++ b/tests/unit/Server/IoServerTest.php @@ -22,7 +22,8 @@ class IoServerTest extends \PHPUnit_Framework_TestCase { $loop = new StreamSelectLoop; $this->reactor = new Server(0, $loop); - $this->port = parse_url('tcp://' . $this->reactor->getAddress(), PHP_URL_PORT); + $uri = $this->reactor->getAddress(); + $this->port = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_PORT); $this->server = new IoServer($this->app, $this->reactor, $loop); } From 829af203b278682143602c7c487ec11d5f552d98 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 9 Sep 2017 16:59:25 -0400 Subject: [PATCH 43/52] Fix readme headers, update apigen with 0.4 --- Makefile | 17 +++++++++++------ README.md | 6 +++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index e4af1d2..a2526c0 100644 --- a/Makefile +++ b/Makefile @@ -29,9 +29,14 @@ profile: killall php apidocs: - apigen --title Ratchet -d reports/api -s src/ \ - -s vendor/react \ - -s vendor/guzzle \ - -s vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session \ - -s vendor/symfony/routing/Symfony/Component/Routing \ - -s vendor/evenement/evenement/src/Evenement + apigen --title Ratchet -d reports/api \ + -s src/ \ + -s vendor/ratchet/rfc6455/src \ + -s vendor/react/event-loop/src \ + -s vendor/react/socket/src \ + -s vendor/react/stream/src \ + -s vendor/psr/http-message/src \ + -s vendor/symfony/http-foundation/Session \ + -s vendor/symfony/routing \ + -s vendor/evenement/evenement/src/Evenement \ + --exclude=vendor/symfony/routing/Tests \ diff --git a/README.md b/README.md index 883f8e6..3f1a345 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Ratchet +# Ratchet [![Build Status](https://secure.travis-ci.org/ratchetphp/Ratchet.png?branch=master)](http://travis-ci.org/ratchetphp/Ratchet) [![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)](http://socketo.me/reports/ab/index.html) @@ -7,7 +7,7 @@ A PHP library for asynchronously serving WebSockets. Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components. -##Requirements +## Requirements Shell access is required and root access is recommended. To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access. @@ -24,7 +24,7 @@ Need help? Have a question? Want to provide feedback? Write a message on the --- -###A quick example +### A quick example ```php Date: Sun, 10 Sep 2017 11:23:40 -0400 Subject: [PATCH 44/52] Update UTF-8 check and move to WsServer fixes #517 --- CHANGELOG.md | 2 +- src/Ratchet/App.php | 4 ---- src/Ratchet/WebSocket/WsServer.php | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 959d889..102495d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ CHANGELOG ========= -###Legend +### Legend * "BC": Backwards compatibility break (from public component APIs) * "BF": Bug fix diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index 6c084d9..465f270 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -65,10 +65,6 @@ class App { trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING); } - if (3 !== strlen('✓')) { - throw new \DomainException('Bad encoding, length of unicode character ✓ should be 3. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); - } - if (null === $loop) { $loop = LoopFactory::create(); } diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 8ded680..88788b5 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -78,6 +78,10 @@ class WsServer implements HttpServerInterface { throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface'); } + if (bin2hex('✓') === 'e29c93') { + throw new \DomainException('Bad encoding, length of unicode character ✓ should be 3. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); + } + $this->delegate = $component; $this->connections = new \SplObjectStorage; From e1980b2016f266838c5666c6d4b0fffb1fd51734 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 10 Sep 2017 11:44:51 -0400 Subject: [PATCH 45/52] Add missing SSL doc --- src/Ratchet/Server/IoServer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 1669b78..1a992fd 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -52,6 +52,7 @@ class IoServer { * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received * @param int $port The port to server sockets on * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) + * @param array $sslconf An array of PHP stream context options in order to support SSL * @return IoServer */ public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', $sslconf = null) { From f07828701708f07318b873109f997a74821f3a91 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 10 Sep 2017 11:57:09 -0400 Subject: [PATCH 46/52] Add ssl options to App --- src/Ratchet/App.php | 7 ++++++- src/Ratchet/Server/IoServer.php | 14 +++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index 465f270..ae8690a 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -3,6 +3,7 @@ namespace Ratchet; use React\EventLoop\LoopInterface; use React\EventLoop\Factory as LoopFactory; use React\Socket\Server as Reactor; +use React\Socket\SecureServer as SecureReactor; use Ratchet\Http\HttpServerInterface; use Ratchet\Http\OriginCheck; use Ratchet\Wamp\WampServerInterface; @@ -60,7 +61,7 @@ class App { * @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. * @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you. */ - public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null) { + public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null, array $sslContext = null) { if (extension_loaded('xdebug')) { trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING); } @@ -74,6 +75,10 @@ class App { $socket = new Reactor($address . ':' . $port, $loop); + if (is_array($sslContext)) { + $socket = new SecureReactor($socket, $loop, $sslContext); + } + $this->routes = new RouteCollection; $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index 1a992fd..f64c960 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -49,17 +49,17 @@ class IoServer { } /** - * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received - * @param int $port The port to server sockets on - * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) - * @param array $sslconf An array of PHP stream context options in order to support SSL + * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received + * @param int $port The port to server sockets on + * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) + * @param array $sslContext An array of PHP stream context options in order to support SSL * @return IoServer */ - public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', $sslconf = null) { + public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', array $sslContext = null) { $loop = LoopFactory::create(); $socket = new Reactor($address . ':' . $port, $loop); - if (is_array($sslconf)) { - $socket = new SecureReactor($socket, $loop, $sslconf); + if (is_array($sslContext)) { + $socket = new SecureReactor($socket, $loop, $sslContext); } return new static($component, $socket, $loop); From 1113d54176e5646d135c738ad5213c5d28433be3 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 10 Sep 2017 12:21:09 -0400 Subject: [PATCH 47/52] Update from master, remove 5.3 travis --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c751c7..b96f1d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,6 @@ php: dist: trusty matrix: - include: - - php: 5.3 - dist: precise allow_failures: - php: hhvm From 2594ebced4a97aa2dd698437ed251043e76ec61c Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 10 Sep 2017 12:49:48 -0400 Subject: [PATCH 48/52] Fix UTF-8 check --- src/Ratchet/WebSocket/WsServer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 88788b5..8030604 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -78,8 +78,8 @@ class WsServer implements HttpServerInterface { throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface'); } - if (bin2hex('✓') === 'e29c93') { - throw new \DomainException('Bad encoding, length of unicode character ✓ should be 3. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); + if (bin2hex('✓') !== 'e29c93') { + throw new \DomainException('Bad encoding, unicode character ✓ did not match expected value. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); } $this->delegate = $component; From 0cde24bae7cb1d396d1ea82e862e826dee85d438 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 10 Sep 2017 14:41:56 -0400 Subject: [PATCH 49/52] entryption params --- CHANGELOG.md | 2 +- src/Ratchet/App.php | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e200b86..1b58053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ CHANGELOG * BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object * Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface * Added heartbeat support via ping/pong in WsServer - * SSL now supported + * TLS now supported * BC: No longer support old (and insecure) Hixie76 and Hybi protocols * BC: No longer support disabling UTF-8 checks * BC: The Session component implements HttpServerInterface instead of WsServerInterface diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index ae8690a..b5d7fa6 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -56,10 +56,11 @@ class App { protected $_routeCounter = 0; /** - * @param string $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');` - * @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843 - * @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. - * @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you. + * @param string $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');` + * @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843 + * @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. + * @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you. + * @param array $sslContext An array of PHP stream context options in order to support SSL */ public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null, array $sslContext = null) { if (extension_loaded('xdebug')) { From fe4a97400dbc6c26a67e02dee5af7eafb76fc814 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 13 Sep 2017 18:12:50 -0400 Subject: [PATCH 50/52] Remove usage of http_parse_message Found a case where http_parse_message failed to parse a valid request header and returned null, which ended up in an uncaught exception being thrown by Guzzle. --- src/Ratchet/Http/HttpRequestParser.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Ratchet/Http/HttpRequestParser.php b/src/Ratchet/Http/HttpRequestParser.php index 25bf489..9c44114 100644 --- a/src/Ratchet/Http/HttpRequestParser.php +++ b/src/Ratchet/Http/HttpRequestParser.php @@ -59,18 +59,6 @@ class HttpRequestParser implements MessageInterface { * @return \Psr\Http\Message\RequestInterface */ public function parse($headers) { - if (function_exists('http_parse_message')) { - $parts = http_parse_message($headers); - - return new gPsr\Request( - $parts->requestMethod - , $parts->requestUrl - , $parts->headers - , null - , $parts->httpVersion - ); - } else { - return gPsr\parse_request($headers); - } + return gPsr\parse_request($headers); } } From 0f827b13c9ef3a09083c8cfd173c7094a2a36dad Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 14 Sep 2017 07:40:37 -0400 Subject: [PATCH 51/52] Remove Topic autoDelete option from WAMP When a Topic reaches 0 subscribers it will be removed New subscriptions to Topics that had the same name will create new Topics refs #185, #198 --- CHANGELOG.md | 1 + src/Ratchet/Wamp/Topic.php | 7 ------- src/Ratchet/Wamp/TopicManager.php | 2 +- tests/unit/Wamp/TopicManagerTest.php | 13 +++++-------- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b58053..672237d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ CHANGELOG * BC: The Session component implements HttpServerInterface instead of WsServerInterface * BC: PHP 5.3 no longer supported * BC: Update to newer version of react/socket dependency + * BC: WAMP topics reduced to 0 subscriptions are deleted, new subs to same name will result in new Topic instance * Significant performance enhancements * 0.3.6 (2017-01-06) diff --git a/src/Ratchet/Wamp/Topic.php b/src/Ratchet/Wamp/Topic.php index 7bde5e0..bca8f67 100644 --- a/src/Ratchet/Wamp/Topic.php +++ b/src/Ratchet/Wamp/Topic.php @@ -6,13 +6,6 @@ use Ratchet\ConnectionInterface; * A topic/channel containing connections that have subscribed to it */ class Topic implements \IteratorAggregate, \Countable { - /** - * If true the TopicManager will destroy this object if it's ever empty of connections - * @deprecated in v0.4 - * @type bool - */ - public $autoDelete = false; - private $id; private $subscribers; diff --git a/src/Ratchet/Wamp/TopicManager.php b/src/Ratchet/Wamp/TopicManager.php index 318b986..dd06ada 100644 --- a/src/Ratchet/Wamp/TopicManager.php +++ b/src/Ratchet/Wamp/TopicManager.php @@ -118,7 +118,7 @@ class TopicManager implements WsServerInterface, WampServerInterface { $this->topicLookup[$topic->getId()]->remove($conn); - if ($topic->autoDelete && 0 === $topic->count()) { + if (0 === $topic->count()) { unset($this->topicLookup[$topic->getId()]); } } diff --git a/tests/unit/Wamp/TopicManagerTest.php b/tests/unit/Wamp/TopicManagerTest.php index 8482877..b21b6bc 100644 --- a/tests/unit/Wamp/TopicManagerTest.php +++ b/tests/unit/Wamp/TopicManagerTest.php @@ -185,21 +185,18 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { } public static function topicConnExpectationProvider() { - return array( - array(true, 'onClose', 0) - , array(true, 'onUnsubscribe', 0) - , array(false, 'onClose', 1) - , array(false, 'onUnsubscribe', 1) - ); + return [ + [ 'onClose', 0] + , ['onUnsubscribe', 0] + ]; } /** * @dataProvider topicConnExpectationProvider */ - public function testTopicRetentionFromLeavingConnections($autoDelete, $methodCall, $expectation) { + public function testTopicRetentionFromLeavingConnections($methodCall, $expectation) { $topicName = 'checkTopic'; list($topic, $attribute) = $this->topicProvider($topicName); - $topic->autoDelete = $autoDelete; $this->mngr->onSubscribe($this->conn, $topicName); call_user_func_array(array($this->mngr, $methodCall), array($this->conn, $topicName)); From 680b2ae45c539b676e2c1a5ea2b59da52298c67f Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 14 Sep 2017 08:03:44 -0400 Subject: [PATCH 52/52] Temporarily remove TLS helper functions --- CHANGELOG.md | 3 +-- src/Ratchet/App.php | 7 +------ src/Ratchet/Server/IoServer.php | 6 +----- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 672237d..a2fc038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,11 @@ CHANGELOG --- -* 0.4 (2017-) +* 0.4 (2017-09-14) * BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object * Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface * Added heartbeat support via ping/pong in WsServer - * TLS now supported * BC: No longer support old (and insecure) Hixie76 and Hybi protocols * BC: No longer support disabling UTF-8 checks * BC: The Session component implements HttpServerInterface instead of WsServerInterface diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index b5d7fa6..c119d41 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -60,9 +60,8 @@ class App { * @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843 * @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. * @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you. - * @param array $sslContext An array of PHP stream context options in order to support SSL */ - public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null, array $sslContext = null) { + public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null) { if (extension_loaded('xdebug')) { trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING); } @@ -76,10 +75,6 @@ class App { $socket = new Reactor($address . ':' . $port, $loop); - if (is_array($sslContext)) { - $socket = new SecureReactor($socket, $loop, $sslContext); - } - $this->routes = new RouteCollection; $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index f64c960..b3fb7e0 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -52,15 +52,11 @@ class IoServer { * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received * @param int $port The port to server sockets on * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) - * @param array $sslContext An array of PHP stream context options in order to support SSL * @return IoServer */ - public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0', array $sslContext = null) { + public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { $loop = LoopFactory::create(); $socket = new Reactor($address . ':' . $port, $loop); - if (is_array($sslContext)) { - $socket = new SecureReactor($socket, $loop, $sslContext); - } return new static($component, $socket, $loop); }