Merge branch 'refs/heads/0.3'

This commit is contained in:
Chris Boden 2013-10-14 10:38:12 -04:00
commit d756e0b507
71 changed files with 1208 additions and 388 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -8,17 +8,17 @@ Build up your application through simple interfaces and re-use your application
##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
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
// Make sure composer dependencies have been installed
require __DIR__ . '/vendor/autoload.php';
/**
* chat.php
* Send any incoming messages to all connected clients (except sender)
*/
class Chat implements MessageComponentInterface {
class MyChat implements MessageComponentInterface {
protected $clients;
public function __construct() {
@ -74,8 +73,17 @@ class Chat implements MessageComponentInterface {
}
// Run the server application through the WebSocket protocol on port 8080
$server = IoServer::factory(new WsServer(new Chat), 8080);
$server->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
```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!');
```

View File

@ -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"
}
}

106
composer.lock generated
View File

@ -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": [

View File

@ -2,7 +2,7 @@
<phpunit
forceCoversAnnotation="true"
mapTestClassNameToCoveredClassName="true"
bootstrap="vendor/autoload.php"
bootstrap="tests/bootstrap.php"
colors="true"
backupGlobals="false"
backupStaticAttributes="false"
@ -11,15 +11,20 @@
>
<testsuites>
<testsuite name="Ratchet Test Suite">
<directory>./tests/Ratchet/</directory>
<testsuite name="unit">
<directory>./tests/unit/</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="integration">
<directory>./tests/integration/</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>./tests/</directory>
<directory>./vendor/</directory>
</blacklist>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

127
src/Ratchet/App.php Normal file
View File

@ -0,0 +1,127 @@
<?php
namespace Ratchet;
use React\EventLoop\LoopInterface;
use React\EventLoop\Factory as LoopFactory;
use React\Socket\Server as Reactor;
use Ratchet\Http\HttpServerInterface;
use Ratchet\Http\OriginCheck;
use Ratchet\Wamp\WampServerInterface;
use Ratchet\Server\IoServer;
use Ratchet\Server\FlashPolicy;
use Ratchet\Http\HttpServer;
use Ratchet\Http\Router;
use Ratchet\WebSocket\WsServer;
use Ratchet\Wamp\WampServer;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;
/**
* An opinionated facade class to quickly and easily create a WebSocket server.
* A few configuration assumptions are made and some best-practice security conventions are applied by default.
*/
class App {
/**
* @var \Symfony\Component\Routing\RouteCollection
*/
public $routes;
/**
* @var \Ratchet\Server\IoServer
*/
public $flashServer;
/**
* @var \Ratchet\Server\IoServer
*/
protected $_server;
/**
* The Host passed in construct used for same origin policy
* @var string
*/
protected $httpHost;
/**
* @var int
*/
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.
*/
public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null) {
if (extension_loaded('xdebug')) {
echo "XDebug extension detected. Remember to disable this if performance testing or going live!\n";
}
if (null === $loop) {
$loop = LoopFactory::create();
}
$this->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();
}
}

View File

@ -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

View File

@ -1,9 +1,26 @@
<?php
namespace Ratchet\WebSocket\Guzzle\Http\Message;
namespace Ratchet\Http\Guzzle\Http\Message;
use Guzzle\Http\Message\RequestFactory as GuzzleRequestFactory;
use Guzzle\Http\EntityBody;
class RequestFactory extends GuzzleRequestFactory {
protected static $ratchetInstance;
/**
* {@inheritdoc}
*/
public static function getInstance()
{
// @codeCoverageIgnoreStart
if (!static::$ratchetInstance) {
static::$ratchetInstance = new static();
}
// @codeCoverageIgnoreEnd
return static::$ratchetInstance;
}
/**
* {@inheritdoc}
*/

View File

@ -1,8 +1,8 @@
<?php
namespace Ratchet\WebSocket;
namespace Ratchet\Http;
use Ratchet\MessageInterface;
use Ratchet\ConnectionInterface;
use Ratchet\WebSocket\Guzzle\Http\Message\RequestFactory;
use Ratchet\Http\Guzzle\Http\Message\RequestFactory;
/**
* This class receives streaming data from a client request

View File

@ -0,0 +1,90 @@
<?php
namespace Ratchet\Http;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Guzzle\Http\Message\Response;
class HttpServer 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
*/
protected $_reqParser;
/**
* @var \Ratchet\Http\HttpServerInterface
*/
protected $_httpServer;
/**
* @param HttpServerInterface
*/
public function __construct(HttpServerInterface $component) {
$this->_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();
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Ratchet\Http;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Guzzle\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!!!
* @throws \UnexpectedValueException if a RequestInterface is not passed
*/
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null);
}

View File

@ -0,0 +1,79 @@
<?php
namespace Ratchet\Http;
use Guzzle\Http\Message\RequestInterface;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
use Guzzle\Http\Message\Response;
/**
* A middleware to ensure JavaScript clients connecting are from the expected domain.
* This protects other websites from open WebSocket connections to your application.
* Note: This can be spoofed from non-web browser clients
*/
class OriginCheck implements HttpServerInterface {
/**
* @var \Ratchet\MessageComponentInterface
*/
protected $_component;
public $allowedOrigins = array();
/**
* @param MessageComponentInterface $component Component/Application to decorate
* @param array $allowed An array of allowed domains that are allowed to connect from
*/
public function __construct(MessageComponentInterface $component, array $allowed = array()) {
$this->_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();
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace Ratchet\Http;
use Ratchet\ConnectionInterface;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
class Router implements HttpServerInterface {
/**
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
*/
protected $_matcher;
public function __construct(UrlMatcherInterface $matcher) {
$this->_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();
}
}

View File

@ -1,9 +1,12 @@
<?php
namespace Ratchet\Tests;
namespace Ratchet\Server;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class AbFuzzyServer implements MessageComponentInterface {
/**
* A simple Ratchet application that will reply to all messages with the message it received
*/
class EchoServer implements MessageComponentInterface {
public function onOpen(ConnectionInterface $conn) {
}
@ -15,8 +18,6 @@ class AbFuzzyServer implements MessageComponentInterface {
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo $e->getMessage() . "\n";
$conn->close();
}
}

View File

@ -108,6 +108,7 @@ class FlashPolicy implements MessageComponentInterface {
}
$from->send($this->_cache . "\0");
$from->close();
}
/**

View File

@ -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 {

View File

@ -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];

View File

@ -96,7 +96,7 @@ class WampConnection extends AbstractConnectionDecorator {
/**
* {@inheritdoc}
*/
public function close() {
$this->getConnection()->close();
public function close($opt = null) {
$this->getConnection()->close($opt);
}
}

View File

@ -37,7 +37,13 @@ class WampServer implements MessageComponentInterface, WsServerInterface {
* {@inheritdoc}
*/
public function onMessage(ConnectionInterface $conn, $msg) {
try {
$this->wampProtocol->onMessage($conn, $msg);
} catch (JsonException $je) {
$conn->close(1007);
} catch (\UnexpectedValueException $uve) {
$conn->close(1007);
}
}
/**

View File

@ -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"

View File

@ -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) {
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();
if (null !== $requested) {
foreach ($requested as $sub) {
if ($this->isSubProtocolSupported($sub)) {
$result[] = $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(

View File

@ -1,13 +0,0 @@
<?php
require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php';
$loop = new React\EventLoop\LibEvLoop;
$sock = new React\Socket\Server($loop);
$app = new Ratchet\WebSocket\WsServer(new Ratchet\Tests\AbFuzzyServer);
$port = $argc > 1 ? $argv[1] : 8000;
$sock->listen($port, '0.0.0.0');
$server = new Ratchet\Server\IoServer($app, $sock, $loop);
$server->run();

View File

@ -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": {}
}

View File

@ -1,39 +0,0 @@
<?php
namespace Ratchet\Tests\Mock;
class MemorySessionHandler implements \SessionHandlerInterface {
protected $_sessions = array();
public function close() {
}
public function destroy($session_id) {
if (isset($this->_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;
}
}

View File

@ -1,59 +0,0 @@
<?php
namespace Ratchet\Tests\Wamp;
use Ratchet\Wamp\WampServer;
/**
* @covers Ratchet\Wamp\WampServer
*/
class WampServerTest extends \PHPUnit_Framework_TestCase {
private $serv;
private $mock;
private $conn;
public function setUp() {
$this->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());
}
}

View File

@ -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');

View File

@ -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');

View File

@ -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');

View File

@ -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');

View File

@ -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": {}
}

5
tests/bootstrap.php Normal file
View File

@ -0,0 +1,5 @@
<?php
$loader = require __DIR__ . '/../vendor/autoload.php';
$loader->add('Ratchet', __DIR__ . '/helpers');
$loader->register();

View File

@ -0,0 +1,50 @@
<?php
namespace Ratchet;
abstract class AbstractMessageComponentTestCase extends \PHPUnit_Framework_TestCase {
protected $_app;
protected $_serv;
protected $_conn;
abstract public function getConnectionClassString();
abstract public function getDecoratorClassString();
abstract public function getComponentClassString();
public function setUp() {
$this->_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);
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Mock;
namespace Ratchet\Mock;
use Ratchet\MessageComponentInterface;
use Ratchet\WebSocket\WsServerInterface;
use Ratchet\ConnectionInterface;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Mock;
namespace Ratchet\Mock;
use Ratchet\ConnectionInterface;
class Connection implements ConnectionInterface {

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Mock;
namespace Ratchet\Mock;
use Ratchet\AbstractConnectionDecorator;
class ConnectionDecorator extends AbstractConnectionDecorator {

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Mock;
namespace Ratchet\Mock;
use Ratchet\Wamp\WampServerInterface;
use Ratchet\WebSocket\WsServerInterface;
use Ratchet\ConnectionInterface;

View File

@ -0,0 +1,28 @@
<?php
namespace Ratchet;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
use Ratchet\WebSocket\WsServerInterface;
use Ratchet\Wamp\WampServerInterface;
class NullComponent implements MessageComponentInterface, WsServerInterface, WampServerInterface {
public function onOpen(ConnectionInterface $conn) {}
public function onMessage(ConnectionInterface $conn, $msg) {}
public function onClose(ConnectionInterface $conn) {}
public function onError(ConnectionInterface $conn, \Exception $e) {}
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {}
public function onSubscribe(ConnectionInterface $conn, $topic) {}
public function onUnSubscribe(ConnectionInterface $conn, $topic) {}
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude = array(), array $eligible = array()) {}
public function getSubProtocols() {
return array();
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Wamp\Stub;
namespace Ratchet\Wamp\Stub;
use Ratchet\WebSocket\WsServerInterface;
use Ratchet\Wamp\WampServerInterface;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket\Stub;
namespace Ratchet\WebSocket\Stub;
use Ratchet\MessageComponentInterface;
use Ratchet\WebSocket\WsServerInterface;

View File

@ -0,0 +1,53 @@
<?php
use Guzzle\Http\Message\Request;
class GuzzleTest extends \PHPUnit_Framework_TestCase {
protected $_request;
protected $_headers = array(
'Upgrade' => '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());
}
}

View File

@ -1,7 +1,6 @@
<?php
namespace Ratchet\Tests;
use Ratchet\Tests\Mock\ConnectionDecorator;
use Ratchet\Tests\Mock\Connection;
namespace Ratchet;
use Ratchet\Mock\ConnectionDecorator;
/**
* @covers Ratchet\AbstractConnectionDecorator
@ -13,7 +12,7 @@ class AbstractConnectionDecoratorTest extends \PHPUnit_Framework_TestCase {
protected $l2;
public function setUp() {
$this->mock = new Connection;
$this->mock = $this->getMock('\Ratchet\ConnectionInterface');
$this->l1 = new ConnectionDecorator($this->mock);
$this->l2 = new ConnectionDecorator($this->l1);
}

View File

@ -1,9 +1,9 @@
<?php
namespace Ratchet\Tests\WebSocket\Guzzle\Http\Message;
use Ratchet\WebSocket\Guzzle\Http\Message\RequestFactory;
namespace Ratchet\Http\Guzzle\Http\Message;
use Ratchet\Http\Guzzle\Http\Message\RequestFactory;
/**
* @covers Ratchet\WebSocket\Guzzle\Http\Message\RequestFactory
* @covers Ratchet\Http\Guzzle\Http\Message\RequestFactory
*/
class RequestFactoryTest extends \PHPUnit_Framework_TestCase {
protected $factory;

View File

@ -1,10 +1,9 @@
<?php
namespace Ratchet\Tests\WebSocket;
use Ratchet\WebSocket\HttpRequestParser;
use Ratchet\Tests\Mock\Connection as ConnectionStub;
namespace Ratchet\Http;
use Ratchet\Http\HttpRequestParser;
/**
* @covers Ratchet\WebSocket\HttpRequestParser
* @covers Ratchet\Http\HttpRequestParser
*/
class HttpRequestParserTest extends \PHPUnit_Framework_TestCase {
protected $parser;
@ -32,7 +31,7 @@ class HttpRequestParserTest extends \PHPUnit_Framework_TestCase {
}
public function testBufferOverflowResponse() {
$conn = new ConnectionStub;
$conn = $this->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);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace Ratchet\Http;
use Ratchet\AbstractMessageComponentTestCase;
/**
* @covers Ratchet\Http\HttpServer
*/
class HttpServerTest extends AbstractMessageComponentTestCase {
public function setUp() {
parent::setUp();
$this->_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");
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Ratchet\Http;
use Ratchet\AbstractMessageComponentTestCase;
/**
* @covers Ratchet\Http\OriginCheck
*/
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'));
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!');
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Ratchet\Http;
use Ratchet\Http\Router;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* @covers Ratchet\Http\Router
*/
class RouterTest extends \PHPUnit_Framework_TestCase {
protected $_router;
protected $_matcher;
protected $_conn;
protected $_req;
public function setUp() {
$this->_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);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Ratchet\Server;
use Ratchet\Server\EchoServer;
class EchoServerTest extends \PHPUnit_Framework_TestCase {
protected $_conn;
protected $_comp;
public function setUp() {
$this->_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();
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Application\Server;
namespace Ratchet\Application\Server;
use Ratchet\Server\FlashPolicy;
/**
@ -137,4 +137,16 @@ class FlashPolicyTest extends \PHPUnit_Framework_TestCase {
$this->_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);
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Application\Server;
namespace Ratchet\Application\Server;
use Ratchet\Server\IoConnection;
/**

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Server;
namespace Ratchet\Server;
use Ratchet\Server\IoServer;
use React\EventLoop\StreamSelectLoop;
use React\Socket\Server;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Server;
namespace Ratchet\Server;
use Ratchet\Server\IpBlackList;
/**

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Session\Serialize;
namespace Ratchet\Session\Serialize;
use Ratchet\Session\Serialize\PhpHandler;
/**

View File

@ -1,7 +1,8 @@
<?php
namespace Ratchet\Tests\Session;
namespace Ratchet\Session;
use Ratchet\AbstractMessageComponentTestCase;
use Ratchet\Session\SessionProvider;
use Ratchet\Tests\Mock\MemorySessionHandler;
use Ratchet\Mock\MemorySessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
use Guzzle\Http\Message\Request;
@ -11,11 +12,30 @@ use Guzzle\Http\Message\Request;
* @covers Ratchet\Session\Storage\VirtualSessionStorage
* @covers Ratchet\Session\Storage\Proxy\VirtualProxy
*/
class SessionProviderTest extends \PHPUnit_Framework_TestCase {
class SessionProviderTest extends AbstractMessageComponentTestCase {
public function setUp() {
if (!class_exists('Symfony\\Component\\HttpFoundation\\Session\\Session')) {
if (!class_exists('Symfony\Component\HttpFoundation\Session\Session')) {
return $this->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'));
}
}

View File

@ -1,9 +1,9 @@
<?php
namespace Ratchet\Tests\Wamp;
namespace Ratchet\Wamp;
use Ratchet\Wamp\ServerProtocol;
use Ratchet\Wamp\WampConnection;
use Ratchet\Tests\Mock\Connection;
use Ratchet\Tests\Mock\WampComponent as TestComponent;
use Ratchet\Mock\Connection;
use Ratchet\Mock\WampComponent as TestComponent;
/**
* @covers Ratchet\Wamp\ServerProtocol
@ -38,7 +38,7 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase {
* @dataProvider invalidMessageProvider
*/
public function testInvalidMessages($type) {
$this->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);
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Wamp;
namespace Ratchet\Wamp;
use Ratchet\Wamp\TopicManager;
/**
@ -7,12 +7,20 @@ use Ratchet\Wamp\TopicManager;
*/
class TopicManagerTest extends \PHPUnit_Framework_TestCase {
private $mock;
/**
* @var \Ratchet\Wamp\TopicManager
*/
private $mngr;
/**
* @var \Ratchet\ConnectionInterface
*/
private $conn;
public function setUp() {
$this->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);

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Wamp;
namespace Ratchet\Wamp;
use Ratchet\Wamp\Topic;
use Ratchet\Wamp\WampConnection;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\Wamp;
namespace Ratchet\Wamp;
use Ratchet\Wamp\WampConnection;
/**

View File

@ -0,0 +1,50 @@
<?php
namespace Ratchet\Wamp;
use Ratchet\Wamp\WampServer;
use Ratchet\AbstractMessageComponentTestCase;
/**
* @covers Ratchet\Wamp\WampServer
*/
class WampServerTest extends AbstractMessageComponentTestCase {
public function getConnectionClassString() {
return '\Ratchet\Wamp\WampConnection';
}
public function getDecoratorClassString() {
return 'Ratchet\Wamp\WampServer';
}
public function getComponentClassString() {
return '\Ratchet\Wamp\WampServerInterface';
}
public function testOnMessageToEvent() {
$published = 'Client published this message';
$this->_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')));
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace Ratchet\Tests\WebSocket\Version;
namespace Ratchet\WebSocket\Version;
use Ratchet\WebSocket\Version\Hixie76;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
/**
@ -43,7 +44,7 @@ class Hixie76Test extends \PHPUnit_Framework_TestCase {
$headers = "GET / HTTP/1.1";
$headers .= "Upgrade: WebSocket{$this->_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);

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket\Version;
namespace Ratchet\WebSocket\Version;
use Ratchet\WebSocket\Version\HyBi10;
use Ratchet\WebSocket\Version\RFC6455\Frame;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket\Version\RFC6455;
namespace Ratchet\WebSocket\Version\RFC6455;
use Ratchet\WebSocket\Version\RFC6455\Frame;
/**

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket\Version\RFC6455;
namespace Ratchet\WebSocket\Version\RFC6455;
use Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier;
/**

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket\Version\RFC6455\Message;
namespace Ratchet\WebSocket\Version\RFC6455\Message;
use Ratchet\WebSocket\Version\RFC6455\Message;
use Ratchet\WebSocket\Version\RFC6455\Frame;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket\Version;
namespace Ratchet\WebSocket\Version;
use Ratchet\WebSocket\Version\RFC6455;
use Ratchet\WebSocket\Version\RFC6455\Frame;
use Guzzle\Http\Message\RequestFactory;

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\Tests\WebSocket;
namespace Ratchet\WebSocket;
use Ratchet\WebSocket\VersionManager;
use Ratchet\WebSocket\Version\RFC6455;
use Ratchet\WebSocket\Version\HyBi10;

View File

@ -1,7 +1,7 @@
<?php
namespace Ratchet\Tests\WebSocket;
namespace Ratchet\WebSocket;
use Ratchet\WebSocket\WsServer;
use Ratchet\Tests\Mock\Component as MockComponent;
use Ratchet\Mock\Component as MockComponent;
/**
* @covers Ratchet\WebSocket\WsServer
@ -27,10 +27,11 @@ class WsServerTest extends \PHPUnit_Framework_TestCase {
public function protocolProvider() {
return array(
array('hello,world', array('hello', 'world'), array('hello', 'world'))
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'))
);
}