diff --git a/.travis.yml b/.travis.yml index 6161d58..7604e9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: php php: - - 5.3.3 - 5.3 - 5.4 + - 5.5 before_script: - curl -s http://getcomposer.org/installer | php diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f2074..7aae881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ CHANGELOG --- +* 0.3.0 (2013-10-14) + + * Added the `App` class to help making Ratchet so easy to use it's silly + * BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks + * Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router + * BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer + * BF: Single sub-protocol selection to conform with RFC6455 + * BF: Sanity checks on WAMP protocol to prevent errors + * 0.2.8 (2013-09-19) * React 0.3 support @@ -85,4 +94,4 @@ CHANGELOG * 0.1 (2012-05-11) * First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList - * I/O now handled by React, making Ratchet fully asynchronous + * I/O now handled by React, making Ratchet fully asynchronous \ No newline at end of file diff --git a/Makefile b/Makefile index a1b8a5b..0595b04 100644 --- a/Makefile +++ b/Makefile @@ -8,23 +8,21 @@ cover: phpunit --coverage-text --coverage-html=reports/coverage abtests: - ulimit -n 2048 && php tests/AutobahnTestSuite/bin/fuzzingserver-libevent.php 8002 & - ulimit -n 2048 && php tests/AutobahnTestSuite/bin/fuzzingserver-stream.php 8001 & - ulimit -n 2048 && php tests/AutobahnTestSuite/bin/fuzzingserver-libev.php 8004 & - ulimit -n 2048 && php tests/AutobahnTestSuite/bin/fuzzingserver-libuv.php 8005 & - ulimit -n 2048 && php tests/AutobahnTestSuite/bin/fuzzingserver-noutf8.php 8003 & + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-libevent.php 8001 & + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-stream.php 8002 & + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-noutf8.php 8003 & wstest -m testeeserver -w ws://localhost:8000 & - wstest -m fuzzingclient -s tests/AutobahnTestSuite/fuzzingclient-all.json + wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json killall php wstest abtest: - ulimit -n 2048 && php tests/AutobahnTestSuite/bin/fuzzingserver-stream.php & - wstest -m fuzzingclient -s tests/AutobahnTestSuite/fuzzingclient-quick.json + ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-stream.php & + wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json killall php profile: - php -d 'xdebug.profiler_enable=1' tests/AutobahnTestSuite/bin/fuzzingserver-libevent.php & - wstest -m fuzzingclient -s tests/AutobahnTestSuite/fuzzingclient-profile.json + php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver-libevent.php & + wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json killall php apidocs: @@ -32,4 +30,5 @@ apidocs: -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 diff --git a/README.md b/README.md index 092a9dc..fa127b6 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,22 @@ [![Build Status](https://secure.travis-ci.org/cboden/Ratchet.png?branch=master)](http://travis-ci.org/cboden/Ratchet) A PHP 5.3 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. +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 - 27, Firefox 6 - 21, Safari 5.0.1 - 6, iOS 4.2 - 6 +* Tested on Chrome 13 - 30, Firefox 6 - 24, Safari 5.0.1 - 6, iOS 4.2 - 7 * 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. -To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80, which requires root access. +To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access. 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.3 (or higher) is required. If you have access, PHP 5.4 is *highly* recommended for its performance improvements. +PHP 5.3.9 (or higher) is required. If you have access, PHP 5.4 is *highly* recommended for its performance improvements. ### Documentation @@ -30,22 +30,21 @@ Need help? Have a question? Want to provide feedback? Write a message on the --- -###A quick server example +###A quick example ```php run(); + $app = new Ratchet\App('localhost', 8080); + $app->route('/chat', new MyChat); + $app->route('/echo', new Ratchet\Server\EchoServer, array(*)); + $app->run(); ``` - $ php chat.php \ No newline at end of file + $ php chat.php + +```javascript + // Then some JavaScript in the browser: + 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 diff --git a/composer.json b/composer.json index d66c685..12c95f0 100644 --- a/composer.json +++ b/composer.json @@ -20,14 +20,14 @@ } , "autoload": { "psr-0": { - "Ratchet": "src", - "Ratchet\\Tests": "tests" + "Ratchet": "src" } } , "require": { - "php": ">=5.3.3" - , "react/socket": ">=0.2.0,<0.4.0-dev" + "php": ">=5.3.9" + , "react/socket": "0.3.*" , "guzzle/http": ">=3.6.0,<3.8.0-dev" - , "symfony/http-foundation": "~2.1" + , "symfony/http-foundation": "~2.2" + , "symfony/routing": "~2.2" } } diff --git a/composer.lock b/composer.lock index 41322f5..ec801f4 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "a62a14a02670ed6aedd683b1cb2b414f", + "hash": "a45c5bcb9c18e390adc2a60ffd059a52", "packages": [ { "name": "evenement/evenement", @@ -47,17 +47,17 @@ }, { "name": "guzzle/common", - "version": "v3.7.3", + "version": "v3.7.4", "target-dir": "Guzzle/Common", "source": { "type": "git", "url": "https://github.com/guzzle/common.git", - "reference": "bf73c87375f60861f8c7ccc7b95878023ade5306" + "reference": "5126e268446c7e7df961b89128d71878e0652432" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/common/zipball/bf73c87375f60861f8c7ccc7b95878023ade5306", - "reference": "bf73c87375f60861f8c7ccc7b95878023ade5306", + "url": "https://api.github.com/repos/guzzle/common/zipball/5126e268446c7e7df961b89128d71878e0652432", + "reference": "5126e268446c7e7df961b89128d71878e0652432", "shasum": "" }, "require": { @@ -87,21 +87,21 @@ "event", "exception" ], - "time": "2013-09-08 21:09:18" + "time": "2013-10-02 20:47:00" }, { "name": "guzzle/http", - "version": "v3.7.3", + "version": "v3.7.4", "target-dir": "Guzzle/Http", "source": { "type": "git", "url": "https://github.com/guzzle/http.git", - "reference": "1034125dfd906b73119e535f03153a62fccb1989" + "reference": "3420035adcf312d62a2e64f3e6b3e3e590121786" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/http/zipball/1034125dfd906b73119e535f03153a62fccb1989", - "reference": "1034125dfd906b73119e535f03153a62fccb1989", + "url": "https://api.github.com/repos/guzzle/http/zipball/3420035adcf312d62a2e64f3e6b3e3e590121786", + "reference": "3420035adcf312d62a2e64f3e6b3e3e590121786", "shasum": "" }, "require": { @@ -144,11 +144,11 @@ "http", "http client" ], - "time": "2013-09-06 11:34:26" + "time": "2013-09-20 22:05:53" }, { "name": "guzzle/parser", - "version": "v3.7.3", + "version": "v3.7.4", "target-dir": "Guzzle/Parser", "source": { "type": "git", @@ -192,7 +192,7 @@ }, { "name": "guzzle/stream", - "version": "v3.7.3", + "version": "v3.7.4", "target-dir": "Guzzle/Stream", "source": { "type": "git", @@ -375,17 +375,17 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.3.4", + "version": "v2.3.6", "target-dir": "Symfony/Component/EventDispatcher", "source": { "type": "git", "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "41c9826457c65fa3cf746f214985b7ca9cba42f8" + "reference": "7fc72a7a346a1887d3968cc1ce5642a15cd182e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/41c9826457c65fa3cf746f214985b7ca9cba42f8", - "reference": "41c9826457c65fa3cf746f214985b7ca9cba42f8", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/7fc72a7a346a1887d3968cc1ce5642a15cd182e9", + "reference": "7fc72a7a346a1887d3968cc1ce5642a15cd182e9", "shasum": "" }, "require": { @@ -425,21 +425,21 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "http://symfony.com", - "time": "2013-07-21 12:12:18" + "time": "2013-09-19 09:45:20" }, { "name": "symfony/http-foundation", - "version": "v2.3.4", + "version": "v2.3.6", "target-dir": "Symfony/Component/HttpFoundation", "source": { "type": "git", "url": "https://github.com/symfony/HttpFoundation.git", - "reference": "fdf130fe65457aedbc4639a22f4ef9d3be5c002c" + "reference": "59e712338cd05463ebcb8da6422a01b1291871e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/fdf130fe65457aedbc4639a22f4ef9d3be5c002c", - "reference": "fdf130fe65457aedbc4639a22f4ef9d3be5c002c", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/59e712338cd05463ebcb8da6422a01b1291871e3", + "reference": "59e712338cd05463ebcb8da6422a01b1291871e3", "shasum": "" }, "require": { @@ -475,7 +475,65 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "http://symfony.com", - "time": "2013-08-26 05:49:51" + "time": "2013-09-29 19:41:41" + }, + { + "name": "symfony/routing", + "version": "v2.3.6", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "7d41463094752e87a0fae60316d236abecb8a034" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/7d41463094752e87a0fae60316d236abecb8a034", + "reference": "7d41463094752e87a0fae60316d236abecb8a034", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.2", + "symfony/yaml": "~2.0" + }, + "suggest": { + "doctrine/common": "", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com", + "time": "2013-09-29 19:41:41" } ], "packages-dev": [ @@ -489,7 +547,7 @@ ], "platform": { - "php": ">=5.3.3" + "php": ">=5.3.9" }, "platform-dev": [ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 78b3128..0cc5451 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ - - ./tests/Ratchet/ + + ./tests/unit/ + + + + + + ./tests/integration/ - - ./tests/ - ./vendor/ - + + ./src/ + \ No newline at end of file diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php new file mode 100644 index 0000000..cc44694 --- /dev/null +++ b/src/Ratchet/App.php @@ -0,0 +1,127 @@ +httpHost = $httpHost; + + $socket = new Reactor($loop); + $socket->listen($port, $address); + + $this->routes = new RouteCollection; + $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); + + $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'); + } else { + $flashSock->listen(8843); + } + } + + /** + * Add an endpiont/application to the server + * @param string $path The URI the client will connect to + * @param ComponentInterface $controller Your application to server for the route. If not specified, assumed to be for a WebSocket + * @param array $allowedOrigins An array of hosts allowed to connect (same host by default), [*] for any + * @param string $httpHost Override the $httpHost variable provided in the __construct + * @return ComponentInterface|WsServer + */ + public function route($path, ComponentInterface $controller, array $allowedOrigins = array(), $httpHost = null) { + if ($controller instanceof HttpServerInterface || $controller instanceof WsServer) { + $decorated = $controller; + } elseif ($controller instanceof WampServerInterface) { + $decorated = new WsServer(new WampServer($controller)); + } elseif ($controller instanceof MessageComponentInterface) { + $decorated = new WsServer($controller); + } else { + $decorated = $controller; + } + + $httpHost = $httpHost ?: $this->httpHost; + + $allowedOrigins = array_values($allowedOrigins); + if (0 === count($allowedOrigins)) { + $allowedOrigins[] = $httpHost; + } + if ('*' !== $allowedOrigins[0]) { + $decorated = new OriginCheck($decorated, $allowedOrigins); + } + + $this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array('Origin' => $this->httpHost), array(), $httpHost)); + + return $decorated; + } + + /** + * Run the server by entering the event loop + */ + public function run() { + $this->_server->run(); + } +} \ No newline at end of file diff --git a/src/Ratchet/ConnectionInterface.php b/src/Ratchet/ConnectionInterface.php index eba0fd9..5cf8e71 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.2.7'; +const VERSION = 'Ratchet/0.3'; /** * A proxy object representing a connection to the application diff --git a/src/Ratchet/WebSocket/Guzzle/Http/Message/RequestFactory.php b/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php similarity index 54% rename from src/Ratchet/WebSocket/Guzzle/Http/Message/RequestFactory.php rename to src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php index 64b6302..7457871 100644 --- a/src/Ratchet/WebSocket/Guzzle/Http/Message/RequestFactory.php +++ b/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php @@ -1,9 +1,26 @@ _httpServer = $component; + $this->_reqParser = new HttpRequestParser; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn) { + $conn->httpHeadersReceived = false; + } + + /** + * {@inheritdoc} + */ + public function onMessage(ConnectionInterface $from, $msg) { + if (true !== $from->httpHeadersReceived) { + try { + if (null === ($request = $this->_reqParser->onMessage($from, $msg))) { + return; + } + } catch (\OverflowException $oe) { + return $this->close($from, 413); + } + + $from->httpHeadersReceived = true; + + return $this->_httpServer->onOpen($from, $request); + } + + $this->_httpServer->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + public function onClose(ConnectionInterface $conn) { + if ($conn->httpHeadersReceived) { + $this->_httpServer->onClose($conn); + } + } + + /** + * {@inheritdoc} + */ + public function onError(ConnectionInterface $conn, \Exception $e) { + if ($conn->httpHeadersReceived) { + $this->_httpServer->onError($conn, $e); + } else { + $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 new file mode 100644 index 0000000..79b7d55 --- /dev/null +++ b/src/Ratchet/Http/HttpServerInterface.php @@ -0,0 +1,14 @@ +_component = $component; + $this->allowedOrigins += $allowed; + } + + /** + * {@inheritdoc} + */ + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + $header = (string)$request->getHeader('Origin'); + $origin = parse_url($header, PHP_URL_HOST) ?: $header; + + if (!in_array($origin, $this->allowedOrigins)) { + return $this->close($conn, 403); + } + + return $this->_component->onOpen($conn, $request); + } + + /** + * {@inheritdoc} + */ + function onMessage(ConnectionInterface $from, $msg) { + return $this->_component->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + function onClose(ConnectionInterface $conn) { + return $this->_component->onClose($conn); + } + + /** + * {@inheritdoc} + */ + 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 new file mode 100644 index 0000000..8e6611e --- /dev/null +++ b/src/Ratchet/Http/Router.php @@ -0,0 +1,92 @@ +_matcher = $matcher; + } + + /** + * {@inheritdoc} + * @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface + */ + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + if (null === $request) { + throw new \UnexpectedValueException('$request can not be null'); + } + + $context = $this->_matcher->getContext(); + $context->setMethod($request->getMethod()); + $context->setHost($request->getHost()); + + try { + $route = $this->_matcher->match($request->getPath()); + } catch (MethodNotAllowedException $nae) { + return $this->close($conn, 403); + } catch (ResourceNotFoundException $nfe) { + return $this->close($conn, 404); + } + + if (is_string($route['_controller']) && class_exists($route['_controller'])) { + $route['_controller'] = new $route['_controller']; + } + + if (!($route['_controller'] instanceof HttpServerInterface)) { + throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); + } + + $conn->controller = $route['_controller']; + $conn->controller->onOpen($conn, $request); + } + + /** + * {@inheritdoc} + */ + function onMessage(ConnectionInterface $from, $msg) { + $from->controller->onMessage($from, $msg); + } + + /** + * {@inheritdoc} + */ + function onClose(ConnectionInterface $conn) { + if (isset($conn->controller)) { + $conn->controller->onClose($conn); + } + } + + /** + * {@inheritdoc} + */ + function onError(ConnectionInterface $conn, \Exception $e) { + if (isset($conn->controller)) { + $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/tests/Ratchet/Tests/AbFuzzyServer.php b/src/Ratchet/Server/EchoServer.php similarity index 68% rename from tests/Ratchet/Tests/AbFuzzyServer.php rename to src/Ratchet/Server/EchoServer.php index 025cb4b..4011d66 100644 --- a/tests/Ratchet/Tests/AbFuzzyServer.php +++ b/src/Ratchet/Server/EchoServer.php @@ -1,9 +1,12 @@ getMessage() . "\n"; - $conn->close(); } } \ No newline at end of file diff --git a/src/Ratchet/Server/FlashPolicy.php b/src/Ratchet/Server/FlashPolicy.php index ec64c78..f0ee26f 100644 --- a/src/Ratchet/Server/FlashPolicy.php +++ b/src/Ratchet/Server/FlashPolicy.php @@ -108,6 +108,7 @@ class FlashPolicy implements MessageComponentInterface { } $from->send($this->_cache . "\0"); + $from->close(); } /** diff --git a/src/Ratchet/Session/SessionProvider.php b/src/Ratchet/Session/SessionProvider.php index 7203dda..9cd4dd3 100644 --- a/src/Ratchet/Session/SessionProvider.php +++ b/src/Ratchet/Session/SessionProvider.php @@ -42,7 +42,7 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { * @param \SessionHandlerInterface $handler * @param array $options * @param \Ratchet\Session\Serialize\HandlerInterface $serializer - * @throws \RuntimeExcpetion + * @throws \RuntimeException */ public function __construct(MessageComponentInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { $this->_app = $app; @@ -71,7 +71,7 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { * {@inheritdoc} */ function onOpen(ConnectionInterface $conn) { - if (null === ($id = $conn->WebSocket->request->getCookie(ini_get('session.name')))) { + if (!isset($conn->WebSocket) || null === ($id = $conn->WebSocket->request->getCookie(ini_get('session.name')))) { $saveHandler = $this->_null; $id = ''; } else { diff --git a/src/Ratchet/Wamp/ServerProtocol.php b/src/Ratchet/Wamp/ServerProtocol.php index 7900659..7842c82 100644 --- a/src/Ratchet/Wamp/ServerProtocol.php +++ b/src/Ratchet/Wamp/ServerProtocol.php @@ -6,7 +6,7 @@ use Ratchet\ConnectionInterface; /** * WebSocket Application Messaging Protocol - * + * * @link http://wamp.ws/spec * @link https://github.com/oberstet/AutobahnJS * @@ -89,6 +89,10 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface { throw new JsonException; } + if (!is_array($json) || $json !== array_values($json)) { + throw new \UnexpectedValueException("Invalid WAMP message format"); + } + switch ($json[0]) { case static::MSG_PREFIX: $from->WAMP->prefixes[$json[1]] = $json[2]; diff --git a/src/Ratchet/Wamp/WampConnection.php b/src/Ratchet/Wamp/WampConnection.php index b5eb04a..de13844 100644 --- a/src/Ratchet/Wamp/WampConnection.php +++ b/src/Ratchet/Wamp/WampConnection.php @@ -6,7 +6,7 @@ use Ratchet\Wamp\ServerProtocol as WAMP; /** * A ConnectionInterface object wrapper that is passed to your WAMP application - * representing a client. Methods on this Connection are therefore different. + * representing a client. Methods on this Connection are therefore different. * @property \stdClass $WAMP */ class WampConnection extends AbstractConnectionDecorator { @@ -96,7 +96,7 @@ class WampConnection extends AbstractConnectionDecorator { /** * {@inheritdoc} */ - public function close() { - $this->getConnection()->close(); + public function close($opt = null) { + $this->getConnection()->close($opt); } } \ No newline at end of file diff --git a/src/Ratchet/Wamp/WampServer.php b/src/Ratchet/Wamp/WampServer.php index d2bfdcf..9e7951f 100644 --- a/src/Ratchet/Wamp/WampServer.php +++ b/src/Ratchet/Wamp/WampServer.php @@ -37,7 +37,13 @@ class WampServer implements MessageComponentInterface, WsServerInterface { * {@inheritdoc} */ public function onMessage(ConnectionInterface $conn, $msg) { - $this->wampProtocol->onMessage($conn, $msg); + try { + $this->wampProtocol->onMessage($conn, $msg); + } catch (JsonException $je) { + $conn->close(1007); + } catch (\UnexpectedValueException $uve) { + $conn->close(1007); + } } /** diff --git a/src/Ratchet/WebSocket/Version/HyBi10.php b/src/Ratchet/WebSocket/Version/HyBi10.php index a53d338..70329c6 100644 --- a/src/Ratchet/WebSocket/Version/HyBi10.php +++ b/src/Ratchet/WebSocket/Version/HyBi10.php @@ -12,4 +12,4 @@ class HyBi10 extends RFC6455 { public function getVersionNumber() { return 6; } -} +} \ No newline at end of file diff --git a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php b/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php index 838a047..41057cc 100644 --- a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php +++ b/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php @@ -113,22 +113,6 @@ class HandshakeVerifier { return (16 === strlen(base64_decode((string)$val))); } - /** - * Verify Origin matches RFC6454 IF it is set - * Origin is an optional field - * @param string|null - * @return bool - * @todo Implement verification functionality - see section 4.2.1.7 - */ - public function verifyOrigin($val) { - if (null === $val) { - return true; - } - - // logic here - return true; - } - /** * Verify the version passed matches this RFC * @param string|int MUST equal 13|"13" diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index dbc8104..c76fd3f 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -2,9 +2,11 @@ namespace Ratchet\WebSocket; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; +use Ratchet\Http\HttpServerInterface; +use Guzzle\Http\Message\RequestInterface; +use Guzzle\Http\Message\Response; use Ratchet\WebSocket\Version; use Ratchet\WebSocket\Encoding\ToggleableValidator; -use Guzzle\Http\Message\Response; /** * The adapter to handle WebSocket requests/responses @@ -12,14 +14,7 @@ use Guzzle\Http\Message\Response; * @link http://ca.php.net/manual/en/ref.http.php * @link http://dev.w3.org/html5/websockets/ */ -class WsServer implements MessageComponentInterface { - /** - * Buffers incoming HTTP requests returning a Guzzle Request when coalesced - * @var HttpRequestParser - * @note May not expose this in the future, may do through facade methods - */ - public $reqParser; - +class WsServer implements HttpServerInterface { /** * Manage the various WebSocket versions to support * @var VersionManager @@ -31,7 +26,7 @@ class WsServer implements MessageComponentInterface { * Decorated component * @var \Ratchet\MessageComponentInterface */ - protected $_decorating; + public $component; /** * @var \SplObjectStorage @@ -39,9 +34,7 @@ class WsServer implements MessageComponentInterface { protected $connections; /** - * For now, array_push accepted subprotocols to this array - * @deprecated - * @temporary + * Holder of accepted protocols, implement through WampServerInterface */ protected $acceptedSubProtocols = array(); @@ -62,7 +55,6 @@ class WsServer implements MessageComponentInterface { * If you want to enable sub-protocols have your component implement WsServerInterface as well */ public function __construct(MessageComponentInterface $component) { - $this->reqParser = new HttpRequestParser; $this->versioner = new VersionManager; $this->validator = new ToggleableValidator; @@ -72,16 +64,23 @@ class WsServer implements MessageComponentInterface { ->enableVersion(new Version\Hixie76) ; - $this->_decorating = $component; + $this->component = $component; $this->connections = new \SplObjectStorage; } /** * {@inheritdoc} */ - public function onOpen(ConnectionInterface $conn) { - $conn->WebSocket = new \StdClass; + public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { + if (null === $request) { + throw new \UnexpectedValueException('$request can not be null'); + } + + $conn->WebSocket = new \StdClass; + $conn->WebSocket->request = $request; $conn->WebSocket->established = false; + + $this->attemptUpgrade($conn); } /** @@ -92,51 +91,46 @@ class WsServer implements MessageComponentInterface { return $from->WebSocket->version->onMessage($this->connections[$from], $msg); } - if (isset($from->WebSocket->request)) { - $from->WebSocket->request->getBody()->write($msg); + $this->attemptUpgrade($from, $msg); + } + + protected function attemptUpgrade(ConnectionInterface $conn, $data = '') { + if ('' !== $data) { + $conn->WebSocket->request->getBody()->write($data); } else { - try { - if (null === ($request = $this->reqParser->onMessage($from, $msg))) { - return; - } - } catch (\OverflowException $oe) { - return $this->close($from, 413); + if (!$this->versioner->isVersionEnabled($conn->WebSocket->request)) { + return $this->close($conn); } - if (!$this->versioner->isVersionEnabled($request)) { - return $this->close($from); - } - - $from->WebSocket->request = $request; - $from->WebSocket->version = $this->versioner->getVersion($request); + $conn->WebSocket->version = $this->versioner->getVersion($conn->WebSocket->request); } try { - $response = $from->WebSocket->version->handshake($from->WebSocket->request); + $response = $conn->WebSocket->version->handshake($conn->WebSocket->request); } catch (\UnderflowException $e) { return; } - if (null !== ($subHeader = $from->WebSocket->request->getHeader('Sec-WebSocket-Protocol'))) { + 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); - $from->send((string)$response); + $conn->send((string)$response); if (101 != $response->getStatusCode()) { - return $from->close(); + return $conn->close(); } - $upgraded = $from->WebSocket->version->upgradeConnection($from, $this->_decorating); + $upgraded = $conn->WebSocket->version->upgradeConnection($conn, $this->component); - $this->connections->attach($from, $upgraded); + $this->connections->attach($conn, $upgraded); $upgraded->WebSocket->established = true; - return $this->_decorating->onOpen($upgraded); + return $this->component->onOpen($upgraded); } /** @@ -147,7 +141,7 @@ class WsServer implements MessageComponentInterface { $decor = $this->connections[$conn]; $this->connections->detach($conn); - $this->_decorating->onClose($decor); + $this->component->onClose($decor); } } @@ -155,8 +149,8 @@ class WsServer implements MessageComponentInterface { * {@inheritdoc} */ public function onError(ConnectionInterface $conn, \Exception $e) { - if ($conn->WebSocket->established) { - $this->_decorating->onError($this->connections[$conn], $e); + if ($conn->WebSocket->established && $this->connections->contains($conn)) { + $this->component->onError($this->connections[$conn], $e); } else { $conn->close(); } @@ -190,8 +184,8 @@ class WsServer implements MessageComponentInterface { */ public function isSubProtocolSupported($name) { if (!$this->isSpGenerated) { - if ($this->_decorating instanceof WsServerInterface) { - $this->acceptedSubProtocols = array_flip($this->_decorating->getSubProtocols()); + if ($this->component instanceof WsServerInterface) { + $this->acceptedSubProtocols = array_flip($this->component->getSubProtocols()); } $this->isSpGenerated = true; @@ -205,26 +199,21 @@ class WsServer implements MessageComponentInterface { * @return string */ protected function getSubProtocolString(\Traversable $requested = null) { - if (null === $requested) { - return ''; - } - - $result = array(); - - foreach ($requested as $sub) { - if ($this->isSubProtocolSupported($sub)) { - $result[] = $sub; + if (null !== $requested) { + foreach ($requested as $sub) { + if ($this->isSubProtocolSupported($sub)) { + return $sub; + } } } - return implode(',', $result); + return ''; } /** * Close a connection with an HTTP response * @param \Ratchet\ConnectionInterface $conn * @param int $code HTTP status code - * @return void */ protected function close(ConnectionInterface $conn, $code = 400) { $response = new Response($code, array( diff --git a/tests/AutobahnTestSuite/bin/fuzzingserver-libuv.php b/tests/AutobahnTestSuite/bin/fuzzingserver-libuv.php deleted file mode 100644 index ccd0613..0000000 --- a/tests/AutobahnTestSuite/bin/fuzzingserver-libuv.php +++ /dev/null @@ -1,13 +0,0 @@ - 1 ? $argv[1] : 8000; - $sock->listen($port, '0.0.0.0'); - - $server = new Ratchet\Server\IoServer($app, $sock, $loop); - $server->run(); diff --git a/tests/AutobahnTestSuite/fuzzingclient-all.json b/tests/AutobahnTestSuite/fuzzingclient-all.json deleted file mode 100644 index 8ebe6d8..0000000 --- a/tests/AutobahnTestSuite/fuzzingclient-all.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "options": {"failByDrop": false} - , "outdir": "reports/ab" - - , "servers": [ - {"agent": "Ratchet/0.2.5 libevent", "url": "ws://localhost:8002", "options": {"version": 18}} - , {"agent": "Ratchet/0.2.5 libuv", "url": "ws://localhost:8005", "options": {"version": 18}} - , {"agent": "Ratchet/0.2.5 libev", "url": "ws://localhost:8004", "options": {"version": 18}} - , {"agent": "Ratchet/0.2.5 -utf8", "url": "ws://localhost:8003", "options": {"version": 18}} - , {"agent": "Ratchet/0.2.5 streams", "url": "ws://localhost:8001", "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-agent-cases": {} -} diff --git a/tests/Ratchet/Tests/Mock/MemorySessionHandler.php b/tests/Ratchet/Tests/Mock/MemorySessionHandler.php deleted file mode 100644 index 87e9953..0000000 --- a/tests/Ratchet/Tests/Mock/MemorySessionHandler.php +++ /dev/null @@ -1,39 +0,0 @@ -_sessions[$session_id])) { - unset($this->_sessions[$session_id]); - } - - return true; - } - - public function gc($maxlifetime) { - return true; - } - - public function open($save_path, $session_id) { - if (!isset($this->_sessions[$session_id])) { - $this->_sessions[$session_id] = ''; - } - - return true; - } - - public function read($session_id) { - return $this->_sessions[$session_id]; - } - - public function write($session_id, $session_data) { - $this->_sessions[$session_id] = $session_data; - - return true; - } -} \ No newline at end of file diff --git a/tests/Ratchet/Tests/Wamp/WampServerTest.php b/tests/Ratchet/Tests/Wamp/WampServerTest.php deleted file mode 100644 index 6a92d57..0000000 --- a/tests/Ratchet/Tests/Wamp/WampServerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -mock = $this->getMock('\\Ratchet\\Wamp\\WampServerInterface'); - $this->serv = new WampServer($this->mock); - $this->conn = $this->getMock('\\Ratchet\\ConnectionInterface'); - - $this->serv->onOpen($this->conn); - } - - public function isWampConn() { - return new \PHPUnit_Framework_Constraint_IsInstanceOf('\\Ratchet\\Wamp\\WampConnection'); - } - - public function testOpen() { - $this->mock->expects($this->once())->method('onOpen')->with($this->isWampConn()); - $this->serv->onOpen($this->getMock('\\Ratchet\\ConnectionInterface')); - } - - public function testOnClose() { - $this->mock->expects($this->once())->method('onClose')->with($this->isWampConn()); - $this->serv->onClose($this->conn); - } - - public function testOnError() { - $e = new \Exception('hurr hurr'); - $this->mock->expects($this->once())->method('onError')->with($this->isWampConn(), $e); - $this->serv->onError($this->conn, $e); - } - - public function testOnMessageToEvent() { - $published = 'Client published this message'; - - $this->mock->expects($this->once())->method('onPublish')->with( - $this->isWampConn() - , new \PHPUnit_Framework_Constraint_IsInstanceOf('\\Ratchet\\Wamp\\Topic') - , $published - , array() - , array() - ); - - $this->serv->onMessage($this->conn, json_encode(array(7, 'topic', $published))); - } - - public function testGetSubProtocols() { - // todo: could expand on this - $this->assertInternalType('array', $this->serv->getSubProtocols()); - } -} \ No newline at end of file diff --git a/tests/AutobahnTestSuite/bin/fuzzingserver-libev.php b/tests/autobahn/bin/fuzzingserver-libev.php similarity index 76% rename from tests/AutobahnTestSuite/bin/fuzzingserver-libev.php rename to tests/autobahn/bin/fuzzingserver-libev.php index ccd0613..b3dec26 100644 --- a/tests/AutobahnTestSuite/bin/fuzzingserver-libev.php +++ b/tests/autobahn/bin/fuzzingserver-libev.php @@ -4,7 +4,7 @@ $loop = new React\EventLoop\LibEvLoop; $sock = new React\Socket\Server($loop); - $app = new Ratchet\WebSocket\WsServer(new Ratchet\Tests\AbFuzzyServer); + $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); $port = $argc > 1 ? $argv[1] : 8000; $sock->listen($port, '0.0.0.0'); diff --git a/tests/AutobahnTestSuite/bin/fuzzingserver-libevent.php b/tests/autobahn/bin/fuzzingserver-libevent.php similarity index 76% rename from tests/AutobahnTestSuite/bin/fuzzingserver-libevent.php rename to tests/autobahn/bin/fuzzingserver-libevent.php index 166bd45..729e7d7 100644 --- a/tests/AutobahnTestSuite/bin/fuzzingserver-libevent.php +++ b/tests/autobahn/bin/fuzzingserver-libevent.php @@ -4,7 +4,7 @@ $loop = new React\EventLoop\LibEventLoop; $sock = new React\Socket\Server($loop); - $app = new Ratchet\WebSocket\WsServer(new Ratchet\Tests\AbFuzzyServer); + $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); $port = $argc > 1 ? $argv[1] : 8000; $sock->listen($port, '0.0.0.0'); diff --git a/tests/AutobahnTestSuite/bin/fuzzingserver-noutf8.php b/tests/autobahn/bin/fuzzingserver-noutf8.php similarity index 68% rename from tests/AutobahnTestSuite/bin/fuzzingserver-noutf8.php rename to tests/autobahn/bin/fuzzingserver-noutf8.php index 589cdd9..01205f5 100644 --- a/tests/AutobahnTestSuite/bin/fuzzingserver-noutf8.php +++ b/tests/autobahn/bin/fuzzingserver-noutf8.php @@ -4,8 +4,9 @@ $loop = new React\EventLoop\StreamSelectLoop; $sock = new React\Socket\Server($loop); - $app = new Ratchet\WebSocket\WsServer(new Ratchet\Tests\AbFuzzyServer); - $app->setEncodingChecks(false); + $web = new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer); + $app = new Ratchet\Http\HttpServer($web); + $web->setEncodingChecks(false); $port = $argc > 1 ? $argv[1] : 8000; $sock->listen($port, '0.0.0.0'); diff --git a/tests/AutobahnTestSuite/bin/fuzzingserver-stream.php b/tests/autobahn/bin/fuzzingserver-stream.php similarity index 76% rename from tests/AutobahnTestSuite/bin/fuzzingserver-stream.php rename to tests/autobahn/bin/fuzzingserver-stream.php index 4e3e797..ae3db8d 100644 --- a/tests/AutobahnTestSuite/bin/fuzzingserver-stream.php +++ b/tests/autobahn/bin/fuzzingserver-stream.php @@ -4,7 +4,7 @@ $loop = new React\EventLoop\StreamSelectLoop; $sock = new React\Socket\Server($loop); - $app = new Ratchet\WebSocket\WsServer(new Ratchet\Tests\AbFuzzyServer); + $app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); $port = $argc > 1 ? $argv[1] : 8000; $sock->listen($port, '0.0.0.0'); diff --git a/tests/autobahn/fuzzingclient-all.json b/tests/autobahn/fuzzingclient-all.json new file mode 100644 index 0000000..5dbd204 --- /dev/null +++ b/tests/autobahn/fuzzingclient-all.json @@ -0,0 +1,15 @@ +{ + "options": {"failByDrop": false} + , "outdir": "reports/ab" + + , "servers": [ + {"agent": "Ratchet/0.3 libevent", "url": "ws://localhost:8001", "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": "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-agent-cases": {} +} diff --git a/tests/AutobahnTestSuite/fuzzingclient-profile.json b/tests/autobahn/fuzzingclient-profile.json similarity index 100% rename from tests/AutobahnTestSuite/fuzzingclient-profile.json rename to tests/autobahn/fuzzingclient-profile.json diff --git a/tests/AutobahnTestSuite/fuzzingclient-quick.json b/tests/autobahn/fuzzingclient-quick.json similarity index 100% rename from tests/AutobahnTestSuite/fuzzingclient-quick.json rename to tests/autobahn/fuzzingclient-quick.json diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..9d21c77 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,5 @@ +add('Ratchet', __DIR__ . '/helpers'); + $loader->register(); diff --git a/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php b/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php new file mode 100644 index 0000000..2ab458e --- /dev/null +++ b/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php @@ -0,0 +1,50 @@ +_app = $this->getMock($this->getComponentClassString()); + $decorator = $this->getDecoratorClassString(); + $this->_serv = new $decorator($this->_app); + $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); + + $this->doOpen($this->_conn); + } + + protected function doOpen($conn) { + $this->_serv->onOpen($conn); + } + + public function isExpectedConnection() { + return new \PHPUnit_Framework_Constraint_IsInstanceOf($this->getConnectionClassString()); + } + + public function testOpen() { + $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); + $this->doOpen($this->getMock('\Ratchet\ConnectionInterface')); + } + + public function testOnClose() { + $this->_app->expects($this->once())->method('onClose')->with($this->isExpectedConnection()); + $this->_serv->onClose($this->_conn); + } + + public function testOnError() { + $e = new \Exception('Whoops!'); + $this->_app->expects($this->once())->method('onError')->with($this->isExpectedConnection(), $e); + $this->_serv->onError($this->_conn, $e); + } + + public function passthroughMessageTest($value) { + $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $value); + $this->_serv->onMessage($this->_conn, $value); + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/Mock/Component.php b/tests/helpers/Ratchet/Mock/Component.php similarity index 96% rename from tests/Ratchet/Tests/Mock/Component.php rename to tests/helpers/Ratchet/Mock/Component.php index 26427b4..d2ef27d 100644 --- a/tests/Ratchet/Tests/Mock/Component.php +++ b/tests/helpers/Ratchet/Mock/Component.php @@ -1,5 +1,5 @@ '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()); + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/AbstractConnectionDecoratorTest.php b/tests/unit/AbstractConnectionDecoratorTest.php similarity index 96% rename from tests/Ratchet/Tests/AbstractConnectionDecoratorTest.php rename to tests/unit/AbstractConnectionDecoratorTest.php index 707460c..905cce1 100644 --- a/tests/Ratchet/Tests/AbstractConnectionDecoratorTest.php +++ b/tests/unit/AbstractConnectionDecoratorTest.php @@ -1,7 +1,6 @@ mock = new Connection; + $this->mock = $this->getMock('\Ratchet\ConnectionInterface'); $this->l1 = new ConnectionDecorator($this->mock); $this->l2 = new ConnectionDecorator($this->l1); } diff --git a/tests/Ratchet/Tests/WebSocket/Guzzle/Http/Message/RequestFactoryTest.php b/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php similarity index 92% rename from tests/Ratchet/Tests/WebSocket/Guzzle/Http/Message/RequestFactoryTest.php rename to tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php index 01d5df0..8cada8c 100644 --- a/tests/Ratchet/Tests/WebSocket/Guzzle/Http/Message/RequestFactoryTest.php +++ b/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php @@ -1,9 +1,9 @@ getMock('\Ratchet\ConnectionInterface'); $this->parser->maxSize = 20; @@ -42,4 +41,11 @@ class HttpRequestParserTest extends \PHPUnit_Framework_TestCase { $this->parser->onMessage($conn, "Header-Is: Too Big"); } + + public function testReturnTypeIsRequest() { + $conn = $this->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); + } } \ No newline at end of file diff --git a/tests/unit/Http/HttpServerTest.php b/tests/unit/Http/HttpServerTest.php new file mode 100644 index 0000000..2c069f1 --- /dev/null +++ b/tests/unit/Http/HttpServerTest.php @@ -0,0 +1,64 @@ +_conn->httpHeadersReceived = true; + } + + public function getConnectionClassString() { + return '\Ratchet\ConnectionInterface'; + } + + public function getDecoratorClassString() { + return '\Ratchet\Http\HttpServer'; + } + + public function getComponentClassString() { + return '\Ratchet\Http\HttpServerInterface'; + } + + public function testOpen() { + $headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"; + + $this->_conn->httpHeadersReceived = false; + $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); + $this->_serv->onMessage($this->_conn, $headers); + } + + public function testOnMessageAfterHeaders() { + $headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"; + $this->_conn->httpHeadersReceived = false; + $this->_serv->onMessage($this->_conn, $headers); + + $message = "Hello World!"; + $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message); + $this->_serv->onMessage($this->_conn, $message); + } + + public function testBufferOverflow() { + $this->_conn->expects($this->once())->method('close'); + $this->_conn->httpHeadersReceived = false; + + $this->_serv->onMessage($this->_conn, str_repeat('a', 5000)); + } + + public function testCloseIfNotEstablished() { + $this->_conn->httpHeadersReceived = false; + $this->_conn->expects($this->once())->method('close'); + $this->_serv->onError($this->_conn, new \Exception('Whoops!')); + } + + public function testBufferHeaders() { + $this->_conn->httpHeadersReceived = false; + $this->_app->expects($this->never())->method('onOpen'); + $this->_app->expects($this->never())->method('onMessage'); + + $this->_serv->onMessage($this->_conn, "GET / HTTP/1.1"); + } +} \ No newline at end of file diff --git a/tests/unit/Http/OriginCheckTest.php b/tests/unit/Http/OriginCheckTest.php new file mode 100644 index 0000000..34db439 --- /dev/null +++ b/tests/unit/Http/OriginCheckTest.php @@ -0,0 +1,46 @@ +_reqStub = $this->getMock('Guzzle\Http\Message\RequestInterface'); + $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue('localhost')); + + parent::setUp(); + + $this->_serv->allowedOrigins[] = 'localhost'; + } + + protected function doOpen($conn) { + $this->_serv->onOpen($conn, $this->_reqStub); + } + + public function getConnectionClassString() { + return '\Ratchet\ConnectionInterface'; + } + + public function getDecoratorClassString() { + return '\Ratchet\Http\OriginCheck'; + } + + public function getComponentClassString() { + return '\Ratchet\Http\HttpServerInterface'; + } + + public function testCloseOnNonMatchingOrigin() { + $this->_serv->allowedOrigins = array('socketo.me'); + $this->_conn->expects($this->once())->method('close'); + + $this->_serv->onOpen($this->_conn, $this->_reqStub); + } + + public function testOnMessage() { + $this->passthroughMessageTest('Hello World!'); + } +} diff --git a/tests/unit/Http/RouterTest.php b/tests/unit/Http/RouterTest.php new file mode 100644 index 0000000..8656dd3 --- /dev/null +++ b/tests/unit/Http/RouterTest.php @@ -0,0 +1,88 @@ +_conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_req = $this->getMock('\Guzzle\Http\Message\RequestInterface'); + $this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + $this->_matcher + ->expects($this->any()) + ->method('getContext') + ->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')); + } + + public function testFourOhFour() { + $this->_conn->expects($this->once())->method('close'); + + $nope = new ResourceNotFoundException; + $this->_matcher->expects($this->any())->method('match')->will($this->throwException($nope)); + + $this->_router->onOpen($this->_conn, $this->_req); + } + + public function testNullRequest() { + $this->setExpectedException('\UnexpectedValueException'); + $this->_router->onOpen($this->_conn); + } + + public function testControllerIsMessageComponentInterface() { + $this->setExpectedException('\UnexpectedValueException'); + $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => new \StdClass))); + $this->_router->onOpen($this->_conn, $this->_req); + } + + public function testControllerOnOpen() { + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); + $this->_router->onOpen($this->_conn, $this->_req); + + $expectedConn = new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\ConnectionInterface'); + $controller->expects($this->once())->method('onOpen')->with($expectedConn, $this->_req); + + $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); + $this->_router->onOpen($this->_conn, $this->_req); + } + + public function testControllerOnMessageBubbles() { + $message = "The greatest trick the Devil ever pulled was convincing the world he didn't exist"; + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $controller->expects($this->once())->method('onMessage')->with($this->_conn, $message); + + $this->_conn->controller = $controller; + + $this->_router->onMessage($this->_conn, $message); + } + + public function testControllerOnCloseBubbles() { + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $controller->expects($this->once())->method('onClose')->with($this->_conn); + + $this->_conn->controller = $controller; + + $this->_router->onClose($this->_conn); + } + + public function testControllerOnErrorBubbles() { + $e= new \Exception('One cannot be betrayed if one has no exceptions'); + $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); + $controller->expects($this->once())->method('onError')->with($this->_conn, $e); + + $this->_conn->controller = $controller; + + $this->_router->onError($this->_conn, $e); + } +} diff --git a/tests/unit/Server/EchoServerTest.php b/tests/unit/Server/EchoServerTest.php new file mode 100644 index 0000000..47fb0e2 --- /dev/null +++ b/tests/unit/Server/EchoServerTest.php @@ -0,0 +1,26 @@ +_conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_comp = new EchoServer; + } + + public function testMessageEchod() { + $message = 'Tillsonburg, my back still aches when I hear that word.'; + $this->_conn->expects($this->once())->method('send')->with($message); + $this->_comp->onMessage($this->_conn, $message); + } + + public function testErrorClosesConnection() { + ob_start(); + $this->_conn->expects($this->once())->method('close'); + $this->_comp->onError($this->_conn, new \Exception); + ob_end_clean(); + } +} diff --git a/tests/Ratchet/Tests/Server/FlashPolicyComponentTest.php b/tests/unit/Server/FlashPolicyComponentTest.php similarity index 90% rename from tests/Ratchet/Tests/Server/FlashPolicyComponentTest.php rename to tests/unit/Server/FlashPolicyComponentTest.php index 15fb45d..67f88a7 100644 --- a/tests/Ratchet/Tests/Server/FlashPolicyComponentTest.php +++ b/tests/unit/Server/FlashPolicyComponentTest.php @@ -1,5 +1,5 @@ _policy->onMessage($conn, ' '); } + + public function testOnOpenExists() { + $this->assertTrue(method_exists($this->_policy, 'onOpen')); + $conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_policy->onOpen($conn); + } + + public function testOnCloseExists() { + $this->assertTrue(method_exists($this->_policy, 'onClose')); + $conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_policy->onClose($conn); + } } \ No newline at end of file diff --git a/tests/Ratchet/Tests/Server/IoConnectionTest.php b/tests/unit/Server/IoConnectionTest.php similarity index 94% rename from tests/Ratchet/Tests/Server/IoConnectionTest.php rename to tests/unit/Server/IoConnectionTest.php index d2ff568..0cb7fb4 100644 --- a/tests/Ratchet/Tests/Server/IoConnectionTest.php +++ b/tests/unit/Server/IoConnectionTest.php @@ -1,5 +1,5 @@ markTestSkipped('Dependency of Symfony HttpFoundation failed'); } + + parent::setUp(); + $this->_serv = new SessionProvider($this->_app, new NullSessionHandler); + } + + public function tearDown() { + ini_set('session.serialize_handler', 'php'); + } + + public function getConnectionClassString() { + return '\Ratchet\ConnectionInterface'; + } + + public function getDecoratorClassString() { + return '\Ratchet\NullComponent'; + } + + public function getComponentClassString() { + return '\Ratchet\MessageComponentInterface'; } public function classCaseProvider() { @@ -33,7 +53,7 @@ class SessionProviderTest extends \PHPUnit_Framework_TestCase { $method = $ref->getMethod('toClassCase'); $method->setAccessible(true); - $component = new SessionProvider($this->getMock('Ratchet\\MessageComponentInterface'), new MemorySessionHandler); + $component = new SessionProvider($this->getMock('Ratchet\\MessageComponentInterface'), $this->getMock('\SessionHandlerInterface')); $this->assertEquals($out, $method->invokeArgs($component, array($in))); } @@ -74,9 +94,9 @@ class SessionProviderTest extends \PHPUnit_Framework_TestCase { } protected function newConn() { - $conn = $this->getMock('Ratchet\\ConnectionInterface'); + $conn = $this->getMock('Ratchet\ConnectionInterface'); - $headers = $this->getMock('Guzzle\\Http\\Message\\Request', array('getCookie'), array('POST', '/', array())); + $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; @@ -85,46 +105,10 @@ class SessionProviderTest extends \PHPUnit_Framework_TestCase { return $conn; } - public function testOnOpenBubbles() { - $conn = $this->newConn(); - $mock = $this->getMock('Ratchet\\MessageComponentInterface'); - $comp = new SessionProvider($mock, new NullSessionHandler); - - $mock->expects($this->once())->method('onOpen')->with($conn); - $comp->onOpen($conn); - } - - protected function getOpenConn() { - $conn = $this->newConn(); - $mock = $this->getMock('Ratchet\\MessageComponentInterface'); - $prov = new SessionProvider($mock, new NullSessionHandler); - - $prov->onOpen($conn); - - return array($conn, $mock, $prov); - } - - public function testOnMessageBubbles() { - list($conn, $mock, $prov) = $this->getOpenConn(); - $msg = 'No sessions here'; - - $mock->expects($this->once())->method('onMessage')->with($conn, $msg); - $prov->onMessage($conn, $msg); - } - - public function testOnCloseBubbles() { - list($conn, $mock, $prov) = $this->getOpenConn(); - - $mock->expects($this->once())->method('onClose')->with($conn); - $prov->onClose($conn); - } - - public function testOnErrorBubbles() { - list($conn, $mock, $prov) = $this->getOpenConn(); - $e = new \Exception('I made a boo boo'); - - $mock->expects($this->once())->method('onError')->with($conn, $e); - $prov->onError($conn, $e); + public function testOnMessageDecorator() { + $message = "Database calls are usually blocking :("; + $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message); + $this->_serv->onMessage($this->_conn, $message); } public function testGetSubProtocolsReturnsArray() { @@ -135,10 +119,20 @@ class SessionProviderTest extends \PHPUnit_Framework_TestCase { } public function testGetSubProtocolsGetFromApp() { - $mock = $this->getMock('Ratchet\\Tests\\WebSocket\\Stub\\WsMessageComponentInterface'); + $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(); + } + + ini_set('session.serialize_handler', 'wddx'); + $this->setExpectedException('\RuntimeException'); + new SessionProvider($this->getMock('\Ratchet\MessageComponentInterface'), $this->getMock('\SessionHandlerInterface')); + } } \ No newline at end of file diff --git a/tests/Ratchet/Tests/Wamp/ServerProtocolTest.php b/tests/unit/Wamp/ServerProtocolTest.php similarity index 91% rename from tests/Ratchet/Tests/Wamp/ServerProtocolTest.php rename to tests/unit/Wamp/ServerProtocolTest.php index cf5d803..b4f57a4 100644 --- a/tests/Ratchet/Tests/Wamp/ServerProtocolTest.php +++ b/tests/unit/Wamp/ServerProtocolTest.php @@ -1,9 +1,9 @@ setExpectedException('\\Ratchet\\Wamp\\Exception'); + $this->setExpectedException('\Ratchet\Wamp\Exception'); $conn = $this->newConn(); $this->_comp->onOpen($conn); @@ -247,4 +247,23 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { $this->assertContains('wamp', $wamp->getSubProtocols()); } + + public function badFormatProvider() { + return array( + array(json_encode(true)) + , array('{"valid":"json", "invalid": "message"}') + , array('{"0": "fail", "hello": "world"}') + ); + } + + /** + * @dataProvider badFormatProvider + */ + public function testValidJsonButInvalidProtocol($message) { + $this->setExpectedException('\UnexpectedValueException'); + + $conn = $this->newConn(); + $this->_comp->onOpen($conn); + $this->_comp->onMessage($conn, $message); + } } \ No newline at end of file diff --git a/tests/Ratchet/Tests/Wamp/TopicManagerTest.php b/tests/unit/Wamp/TopicManagerTest.php similarity index 83% rename from tests/Ratchet/Tests/Wamp/TopicManagerTest.php rename to tests/unit/Wamp/TopicManagerTest.php index 8388550..e096a28 100644 --- a/tests/Ratchet/Tests/Wamp/TopicManagerTest.php +++ b/tests/unit/Wamp/TopicManagerTest.php @@ -1,5 +1,5 @@ conn = $this->getMock('Ratchet\\ConnectionInterface'); - $this->mock = $this->getMock('Ratchet\\Wamp\\WampServerInterface'); + $this->conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->mock = $this->getMock('\Ratchet\Wamp\WampServerInterface'); $this->mngr = new TopicManager($this->mock); $this->conn->WAMP = new \StdClass; @@ -20,19 +28,19 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { } public function testGetTopicReturnsTopicObject() { - $class = new \ReflectionClass('Ratchet\\Wamp\\TopicManager'); + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $method = $class->getMethod('getTopic'); $method->setAccessible(true); $topic = $method->invokeArgs($this->mngr, array('The Topic')); - $this->assertInstanceOf('Ratchet\\Wamp\\Topic', $topic); + $this->assertInstanceOf('Ratchet\Wamp\Topic', $topic); } public function testGetTopicCreatesTopicWithSameName() { $name = 'The Topic'; - $class = new \ReflectionClass('Ratchet\\Wamp\\TopicManager'); + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $method = $class->getMethod('getTopic'); $method->setAccessible(true); @@ -42,7 +50,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { } public function testGetTopicReturnsSameObject() { - $class = new \ReflectionClass('Ratchet\\Wamp\\TopicManager'); + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $method = $class->getMethod('getTopic'); $method->setAccessible(true); @@ -63,7 +71,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { $this->mock->expects($this->once())->method('onCall')->with( $this->conn , $id - , $this->isInstanceOf('Ratchet\\Wamp\\Topic') + , $this->isInstanceOf('Ratchet\Wamp\Topic') , array() ); @@ -72,7 +80,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { public function testOnSubscribeCreatesTopicObject() { $this->mock->expects($this->once())->method('onSubscribe')->with( - $this->conn, $this->isInstanceOf('Ratchet\\Wamp\\Topic') + $this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic') ); $this->mngr->onSubscribe($this->conn, 'new topic'); @@ -81,7 +89,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { public function testTopicIsInConnectionOnSubscribe() { $name = 'New Topic'; - $class = new \ReflectionClass('Ratchet\\Wamp\\TopicManager'); + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $method = $class->getMethod('getTopic'); $method->setAccessible(true); @@ -102,7 +110,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { public function testUnsubscribeEvent() { $name = 'in and out'; $this->mock->expects($this->once())->method('onUnsubscribe')->with( - $this->conn, $this->isInstanceOf('Ratchet\\Wamp\\Topic') + $this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic') ); $this->mngr->onSubscribe($this->conn, $name); @@ -121,7 +129,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { public function testUnsubscribeRemovesTopicFromConnection() { $name = 'Bye Bye Topic'; - $class = new \ReflectionClass('Ratchet\\Wamp\\TopicManager'); + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $method = $class->getMethod('getTopic'); $method->setAccessible(true); @@ -138,7 +146,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { $this->mock->expects($this->once())->method('onPublish')->with( $this->conn - , $this->isInstanceOf('Ratchet\\Wamp\\Topic') + , $this->isInstanceOf('Ratchet\Wamp\Topic') , $msg , $this->isType('array') , $this->isType('array') @@ -155,7 +163,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { public function testConnIsRemovedFromTopicOnClose() { $name = 'State testing'; - $class = new \ReflectionClass('Ratchet\\Wamp\\TopicManager'); + $class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $method = $class->getMethod('getTopic'); $method->setAccessible(true); @@ -180,7 +188,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { public function testGetSubProtocolsBubbles() { $subs = array('hello', 'world'); - $app = $this->getMock('Ratchet\\Tests\\Wamp\\Stub\\WsWampServerInterface'); + $app = $this->getMock('Ratchet\Wamp\Stub\WsWampServerInterface'); $app->expects($this->once())->method('getSubProtocols')->will($this->returnValue($subs)); $mngr = new TopicManager($app); diff --git a/tests/Ratchet/Tests/Wamp/TopicTest.php b/tests/unit/Wamp/TopicTest.php similarity index 98% rename from tests/Ratchet/Tests/Wamp/TopicTest.php rename to tests/unit/Wamp/TopicTest.php index d5358d1..0a2102b 100644 --- a/tests/Ratchet/Tests/Wamp/TopicTest.php +++ b/tests/unit/Wamp/TopicTest.php @@ -1,5 +1,5 @@ _app->expects($this->once())->method('onPublish')->with( + $this->isExpectedConnection() + , new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\Wamp\Topic') + , $published + , array() + , array() + ); + + $this->_serv->onMessage($this->_conn, json_encode(array(7, 'topic', $published))); + } + + public function testGetSubProtocols() { + // todo: could expand on this + $this->assertInternalType('array', $this->_serv->getSubProtocols()); + } + + public function testConnectionClosesOnInvalidJson() { + $this->_conn->expects($this->once())->method('close'); + $this->_serv->onMessage($this->_conn, 'invalid json'); + } + + public function testConnectionClosesOnProtocolError() { + $this->_conn->expects($this->once())->method('close'); + $this->_serv->onMessage($this->_conn, json_encode(array('valid' => 'json', 'invalid' => 'protocol'))); + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/WebSocket/Version/Hixie76Test.php b/tests/unit/WebSocket/Version/Hixie76Test.php similarity index 81% rename from tests/Ratchet/Tests/WebSocket/Version/Hixie76Test.php rename to tests/unit/WebSocket/Version/Hixie76Test.php index d31ce64..07d863b 100644 --- a/tests/Ratchet/Tests/WebSocket/Version/Hixie76Test.php +++ b/tests/unit/WebSocket/Version/Hixie76Test.php @@ -1,6 +1,7 @@ _crlf}"; $headers .= "Connection: Upgrade{$this->_crlf}"; - $headers .= "Host: home.chrisboden.ca{$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}"; @@ -55,12 +56,11 @@ class Hixie76Test extends \PHPUnit_Framework_TestCase { public function testNoUpgradeBeforeBody() { $headers = $this->headerProvider(); - $body = base64_decode($this->_body); - $mockConn = $this->getMock('\\Ratchet\\ConnectionInterface'); - $mockApp = $this->getMock('\\Ratchet\\MessageComponentInterface'); + $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); + $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - $server = new WsServer($mockApp); + $server = new HttpServer(new WsServer($mockApp)); $server->onOpen($mockConn); $mockApp->expects($this->exactly(0))->method('onOpen'); $server->onMessage($mockConn, $headers); @@ -70,10 +70,10 @@ class Hixie76Test extends \PHPUnit_Framework_TestCase { $headers = $this->headerProvider(); $body = base64_decode($this->_body); - $mockConn = $this->getMock('\\Ratchet\\ConnectionInterface'); - $mockApp = $this->getMock('\\Ratchet\\MessageComponentInterface'); + $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); + $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - $server = new WsServer($mockApp); + $server = new HttpServer(new WsServer($mockApp)); $server->onOpen($mockConn); $server->onMessage($mockConn, $headers); diff --git a/tests/Ratchet/Tests/WebSocket/Version/HyBi10Test.php b/tests/unit/WebSocket/Version/HyBi10Test.php similarity index 98% rename from tests/Ratchet/Tests/WebSocket/Version/HyBi10Test.php rename to tests/unit/WebSocket/Version/HyBi10Test.php index 11ebaa2..74044df 100644 --- a/tests/Ratchet/Tests/WebSocket/Version/HyBi10Test.php +++ b/tests/unit/WebSocket/Version/HyBi10Test.php @@ -1,5 +1,5 @@