Merge pull request #91 from cboden/http

HTTP and Routing
Fixes #89
Fixes #15
This commit is contained in:
Chris Boden 2013-05-08 16:11:35 -07:00
commit 0dba9fc50e
36 changed files with 761 additions and 272 deletions

View File

@ -1,7 +1,6 @@
language: php
php:
- 5.3.3
- 5.3
- 5.4

View File

@ -8,6 +8,12 @@ CHANGELOG
---
* 0.3.0 (2013-xx-xx)
* BC: Require hostname and do Origin HTTP header check against it by default, helping prevent CSRF attacks
* Added HTTP Router component to allowing for a single Ratchet server to handle multiple apps
* BC: Decoupled HTTP from WebSocket component
* 0.2.5 (2013-04-01)
* Fixed Hixie-76 handshake bug

View File

@ -32,4 +32,5 @@ apidocs:
-s vendor/react \
-s vendor/guzzle \
-s vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session \
-s vendor/symfony/http-foundation/Symfony/Component/Routing \
-s vendor/evenement/evenement/src/Evenement

View File

@ -38,8 +38,9 @@ Need help? Have a question? Want to provide feedback? Write a message on the
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Http\RoutedHttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\Tests\AbFuzzyServer;
require __DIR__ . '/vendor/autoload.php';
@ -76,7 +77,11 @@ class Chat implements MessageComponentInterface {
}
// Run the server application through the WebSocket protocol on port 8080
$server = IoServer::factory(new WsServer(new Chat), 8080);
$router = new RoutedHttpServer;
$router->addRoute('/echo', new AbFuzzyServer);
$router->addRoute('/chat', new Chat);
$server = IoServer::factory($router, 8000);
$server->run();
```

View File

@ -25,9 +25,10 @@
}
}
, "require": {
"php": ">=5.3.3"
, "react/socket": "0.2.*"
"php": ">=5.3.9"
, "react/socket": "~0.2"
, "guzzle/http": "~3.0"
, "symfony/http-foundation": "~2.1"
, "symfony/http-foundation": "~2.2"
, "symfony/routing": "~2.2"
}
}

185
composer.lock generated
View File

@ -1,5 +1,9 @@
{
"hash": "9ccce99ef687cb79dad8a4c581f38cc5",
"_readme": [
"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": "6006ea344879ef78bf1a490e630a74fc",
"packages": [
{
"name": "evenement/evenement",
@ -42,17 +46,17 @@
},
{
"name": "guzzle/common",
"version": "v3.3.0",
"version": "v3.4.1",
"target-dir": "Guzzle/Common",
"source": {
"type": "git",
"url": "https://github.com/guzzle/common.git",
"reference": "v3.3.0"
"reference": "v3.4.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/common/zipball/v3.3.0",
"reference": "v3.3.0",
"url": "https://api.github.com/repos/guzzle/common/zipball/v3.4.1",
"reference": "v3.4.1",
"shasum": ""
},
"require": {
@ -62,7 +66,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "3.3-dev"
}
},
"autoload": {
@ -82,21 +86,21 @@
"event",
"exception"
],
"time": "2013-03-04 00:41:45"
"time": "2013-04-16 20:56:26"
},
{
"name": "guzzle/http",
"version": "v3.3.0",
"version": "v3.4.1",
"target-dir": "Guzzle/Http",
"source": {
"type": "git",
"url": "https://github.com/guzzle/http.git",
"reference": "v3.3.0"
"reference": "v3.4.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/http/zipball/v3.3.0",
"reference": "v3.3.0",
"url": "https://api.github.com/repos/guzzle/http/zipball/v3.4.1",
"reference": "v3.4.1",
"shasum": ""
},
"require": {
@ -111,7 +115,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "3.3-dev"
}
},
"autoload": {
@ -139,21 +143,21 @@
"http",
"http client"
],
"time": "2013-03-03 21:40:51"
"time": "2013-04-16 20:27:11"
},
{
"name": "guzzle/parser",
"version": "v3.3.0",
"version": "v3.4.1",
"target-dir": "Guzzle/Parser",
"source": {
"type": "git",
"url": "https://github.com/guzzle/parser.git",
"reference": "v3.3.0"
"reference": "v3.4.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/parser/zipball/v3.3.0",
"reference": "v3.3.0",
"url": "https://api.github.com/repos/guzzle/parser/zipball/v3.4.1",
"reference": "v3.4.1",
"shasum": ""
},
"require": {
@ -162,7 +166,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "3.3-dev"
}
},
"autoload": {
@ -183,31 +187,34 @@
"message",
"url"
],
"time": "2013-01-12 21:43:21"
"time": "2013-03-07 22:13:59"
},
{
"name": "guzzle/stream",
"version": "v3.3.0",
"version": "v3.4.1",
"target-dir": "Guzzle/Stream",
"source": {
"type": "git",
"url": "https://github.com/guzzle/stream.git",
"reference": "v3.3.0"
"reference": "v3.4.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/stream/zipball/v3.3.0",
"reference": "v3.3.0",
"url": "https://api.github.com/repos/guzzle/stream/zipball/v3.4.1",
"reference": "v3.4.1",
"shasum": ""
},
"require": {
"guzzle/common": "self.version",
"php": ">=5.3.2"
},
"suggest": {
"guzzle/http": "To convert Guzzle request objects to PHP streams"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "3.3-dev"
}
},
"autoload": {
@ -233,21 +240,21 @@
"component",
"stream"
],
"time": "2013-03-03 03:07:02"
"time": "2013-04-06 18:28:51"
},
{
"name": "react/event-loop",
"version": "v0.2.7",
"version": "v0.3.1",
"target-dir": "React/EventLoop",
"source": {
"type": "git",
"url": "https://github.com/reactphp/event-loop",
"reference": "v0.2.7"
"url": "https://github.com/reactphp/event-loop.git",
"reference": "v0.3.1"
},
"dist": {
"type": "zip",
"url": "https://github.com/reactphp/event-loop/archive/v0.2.7.zip",
"reference": "v0.2.7",
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/v0.3.1",
"reference": "v0.3.1",
"shasum": ""
},
"require": {
@ -260,7 +267,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.2-dev"
"dev-master": "0.3-dev"
}
},
"autoload": {
@ -276,33 +283,33 @@
"keywords": [
"event-loop"
],
"time": "2013-01-05 11:41:26"
"time": "2013-01-14 23:11:47"
},
{
"name": "react/socket",
"version": "v0.2.7",
"version": "v0.3.1",
"target-dir": "React/Socket",
"source": {
"type": "git",
"url": "https://github.com/reactphp/socket",
"reference": "v0.2.7"
"url": "https://github.com/reactphp/socket.git",
"reference": "v0.3.1"
},
"dist": {
"type": "zip",
"url": "https://github.com/reactphp/socket/archive/v0.2.7.zip",
"reference": "v0.2.7",
"url": "https://api.github.com/repos/reactphp/socket/zipball/v0.3.1",
"reference": "v0.3.1",
"shasum": ""
},
"require": {
"evenement/evenement": "1.0.*",
"php": ">=5.3.3",
"react/event-loop": "0.2.*",
"react/stream": "0.2.*"
"react/event-loop": "0.3.*",
"react/stream": "0.3.*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.2-dev"
"dev-master": "0.3-dev"
}
},
"autoload": {
@ -318,21 +325,21 @@
"keywords": [
"Socket"
],
"time": "2012-12-14 00:58:14"
"time": "2013-04-20 14:53:10"
},
{
"name": "react/stream",
"version": "v0.2.7",
"version": "v0.3.1",
"target-dir": "React/Stream",
"source": {
"type": "git",
"url": "https://github.com/reactphp/stream",
"reference": "v0.2.7"
"url": "https://github.com/reactphp/stream.git",
"reference": "v0.3.1"
},
"dist": {
"type": "zip",
"url": "https://github.com/reactphp/stream/archive/v0.2.7.zip",
"reference": "v0.2.7",
"url": "https://api.github.com/repos/reactphp/stream/zipball/v0.3.1",
"reference": "v0.3.1",
"shasum": ""
},
"require": {
@ -340,13 +347,13 @@
"php": ">=5.3.3"
},
"suggest": {
"react/event-loop": "0.2.*",
"react/promise": "1.0.*"
"react/event-loop": "0.3.*",
"react/promise": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.2-dev"
"dev-master": "0.3-dev"
}
},
"autoload": {
@ -363,21 +370,21 @@
"pipe",
"stream"
],
"time": "2012-12-14 00:58:14"
"time": "2013-04-21 13:25:49"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.2.0",
"version": "v2.2.1",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
"reference": "v2.2.0-RC3"
"reference": "v2.2.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.2.0-RC3",
"reference": "v2.2.0-RC3",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.2.1",
"reference": "v2.2.1",
"shasum": ""
},
"require": {
@ -421,17 +428,17 @@
},
{
"name": "symfony/http-foundation",
"version": "v2.2.0",
"version": "v2.2.1",
"target-dir": "Symfony/Component/HttpFoundation",
"source": {
"type": "git",
"url": "https://github.com/symfony/HttpFoundation.git",
"reference": "v2.2.0"
"reference": "v2.2.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.2.0",
"reference": "v2.2.0",
"url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.2.1",
"reference": "v2.2.1",
"shasum": ""
},
"require": {
@ -467,7 +474,65 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "http://symfony.com",
"time": "2013-02-26 09:42:13"
"time": "2013-04-06 10:15:43"
},
{
"name": "symfony/routing",
"version": "v2.2.1",
"target-dir": "Symfony/Component/Routing",
"source": {
"type": "git",
"url": "https://github.com/symfony/Routing.git",
"reference": "v2.2.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Routing/zipball/v2.2.1",
"reference": "v2.2.1",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"doctrine/common": ">=2.2,<3.0",
"psr/log": ">=1.0,<2.0",
"symfony/config": ">=2.2,<2.3-dev",
"symfony/yaml": ">=2.0,<3.0"
},
"suggest": {
"doctrine/common": "~2.2",
"symfony/config": "2.2.*",
"symfony/yaml": "2.2.*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-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-03-23 12:03:22"
}
],
"packages-dev": [
@ -481,7 +546,7 @@
],
"platform": {
"php": ">=5.3.3"
"php": ">=5.3.9"
},
"platform-dev": [

View File

@ -5,7 +5,7 @@ namespace Ratchet;
* The version of Ratchet being used
* @var string
*/
const VERSION = 'Ratchet/0.2.5';
const VERSION = 'Ratchet/0.3-beta';
/**
* A proxy object representing a connection to the application

View File

@ -1,5 +1,5 @@
<?php
namespace Ratchet\WebSocket\Guzzle\Http\Message;
namespace Ratchet\Http\Guzzle\Http\Message;
use Guzzle\Http\Message\RequestFactory as GuzzleRequestFactory;
use Guzzle\Http\EntityBody;

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,97 @@
<?php
namespace Ratchet\Http;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Guzzle\Http\Message\Response;
/**
* @todo Detect HTTP proxy header IP, re-map remoteAddress
*/
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;
if (isset($from->remoteAddress) && '127.0.0.1' == $from->remoteAddress && $request->hasHeader('X-Forwarded-For')) {
$from->remoteAddress = $request->getHeader('X-Forwarded-For', 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,65 @@
<?php
namespace Ratchet\Http;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\WebSocket\WsServer;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;
/**
*/
class RoutedHttpServer implements MessageComponentInterface {
protected $_routes;
protected $_server;
public function __construct(RouteCollection $routes = null) {
if (null == $routes) {
$routes = new RouteCollection;
}
$this->_routes = $routes;
$this->_server = new HttpServer(new Router(new UrlMatcher($routes, new RequestContext)));
}
public function addRoute($path, MessageComponentInterface $controller) {
$this->_routes->add(uniqid(), new Route($path, array(
'_controller' => new WsServer($controller)
)));
}
public function addHttpRoute($path, HttpServerInterface $controller) {
$this->_routes->add(uniqid(), new Route($path, array(
'_controller' => $controller
)));
}
/**
* {@inheritdoc}
*/
function onOpen(ConnectionInterface $conn) {
$this->_server->onOpen($conn);
}
/**
* {@inheritdoc}
*/
function onMessage(ConnectionInterface $from, $msg) {
$this->_server->onMessage($from, $msg);
}
/**
* {@inheritdoc}
*/
function onClose(ConnectionInterface $conn) {
$this->_server->onClose($conn);
}
/**
* {@inheritdoc}
*/
function onError(ConnectionInterface $conn, \Exception $e) {
$this->_server->onError($conn, $e);
}
}

View File

@ -0,0 +1,84 @@
<?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');
}
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) {
$conn->controller->onClose($conn);
}
/**
* {@inheritdoc}
*/
function onError(ConnectionInterface $conn, \Exception $e) {
$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

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

@ -4,7 +4,8 @@ use Guzzle\Http\Message\RequestInterface;
class HyBi10 extends RFC6455 {
public function isProtocol(RequestInterface $request) {
$version = (int)$request->getHeader('Sec-WebSocket-Version', -1);
$version = $request->hasHeader('Sec-WebSocket-Version') ? (int)$request->getHeader('Sec-WebSocket-Version', true) : -1;
return ($version >= 6 && $version < 13);
}

View File

@ -50,7 +50,7 @@ class RFC6455 implements VersionInterface {
* {@inheritdoc}
*/
public function isProtocol(RequestInterface $request) {
$version = (int)$request->getHeader('Sec-WebSocket-Version', -1);
$version = $request->hasHeader('Sec-WebSocket-Version') ? (int)$request->getHeader('Sec-WebSocket-Version', true) : -1;
return ($this->getVersionNumber() === $version);
}
@ -73,7 +73,7 @@ class RFC6455 implements VersionInterface {
return new Response(101, array(
'Upgrade' => 'websocket'
, 'Connection' => 'Upgrade'
, 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key'))
, 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key', true))
));
}

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,50 +91,44 @@ 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;
}
// This needs to be refactored later on, incorporated with routing
if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($from->WebSocket->request->getTokenizedHeader('Sec-WebSocket-Protocol', ',')))) {
if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($conn->WebSocket->request->getTokenizedHeader('Sec-WebSocket-Protocol', ',')))) {
$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);
}
/**
@ -146,7 +139,7 @@ class WsServer implements MessageComponentInterface {
$decor = $this->connections[$conn];
$this->connections->detach($conn);
$this->_decorating->onClose($decor);
$this->component->onClose($decor);
}
}
@ -155,7 +148,7 @@ class WsServer implements MessageComponentInterface {
*/
public function onError(ConnectionInterface $conn, \Exception $e) {
if ($conn->WebSocket->established) {
$this->_decorating->onError($this->connections[$conn], $e);
$this->component->onError($this->connections[$conn], $e);
} else {
$conn->close();
}
@ -189,8 +182,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;
@ -223,7 +216,6 @@ class WsServer implements MessageComponentInterface {
* 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

@ -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\Tests\AbFuzzyServer));
$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\Tests\AbFuzzyServer));
$port = $argc > 1 ? $argv[1] : 8000;
$sock->listen($port, '0.0.0.0');

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\Tests\AbFuzzyServer));
$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\Tests\AbFuzzyServer);
$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\Tests\AbFuzzyServer));
$port = $argc > 1 ? $argv[1] : 8000;
$sock->listen($port, '0.0.0.0');

View File

@ -1,7 +1,6 @@
<?php
namespace Ratchet\Tests;
use Ratchet\Tests\Mock\ConnectionDecorator;
use Ratchet\Tests\Mock\Connection;
/**
* @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

@ -0,0 +1,41 @@
<?php
namespace Ratchet\Tests;
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->_serv->onOpen($this->_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->_serv->onOpen($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);
}
}

View File

@ -1,9 +1,9 @@
<?php
namespace Ratchet\Tests\WebSocket\Guzzle\Http\Message;
use Ratchet\WebSocket\Guzzle\Http\Message\RequestFactory;
namespace Ratchet\Tests\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\Tests\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\Tests\Http;
use Ratchet\Tests\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,84 @@
<?php
namespace Ratchet\Tests\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->_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

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

@ -0,0 +1,28 @@
<?php
namespace Ratchet\Tests\Mock;
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

@ -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,6 @@
<?php
namespace Ratchet\Tests\Session;
use Ratchet\Tests\AbstractMessageComponentTestCase;
use Ratchet\Session\SessionProvider;
use Ratchet\Tests\Mock\MemorySessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
@ -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\Tests\Mock\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)));
}
@ -73,9 +93,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;
@ -84,46 +104,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() {
@ -140,4 +124,14 @@ class SessionProviderTest extends \PHPUnit_Framework_TestCase {
$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

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

View File

@ -1,59 +1,44 @@
<?php
namespace Ratchet\Tests\Wamp;
use Ratchet\Wamp\WampServer;
use Ratchet\Tests\AbstractMessageComponentTestCase;
/**
* @covers Ratchet\Wamp\WampServer
*/
class WampServerTest extends \PHPUnit_Framework_TestCase {
class WampServerTest extends AbstractMessageComponentTestCase {
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 getConnectionClassString() {
return '\Ratchet\Wamp\WampConnection';
}
public function isWampConn() {
return new \PHPUnit_Framework_Constraint_IsInstanceOf('\\Ratchet\\Wamp\\WampConnection');
public function getDecoratorClassString() {
return 'Ratchet\Wamp\WampServer';
}
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 getComponentClassString() {
return '\Ratchet\Wamp\WampServerInterface';
}
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')
$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)));
$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());
$this->assertInternalType('array', $this->_serv->getSubProtocols());
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace Ratchet\Tests\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);