[Http] ROUTING BABY
Decoupled routing from HTTP Added Router implement HttpServerInterface Fully functional Symfony routes in application! As a result, this drastically decreased backwards compatibility breaks while introducing new functionality
This commit is contained in:
parent
c24cdf379e
commit
4df71c3a35
@ -10,8 +10,8 @@ CHANGELOG
|
|||||||
|
|
||||||
* 0.3.0 (2013-xx-xx)
|
* 0.3.0 (2013-xx-xx)
|
||||||
|
|
||||||
* BC: Requre hostname and do Origin HTTP header check against it, disabling CORS by default for security reasons
|
* BC: Require hostname and do Origin HTTP header check against it by default, helping prevent CSRF attacks
|
||||||
* BC: Added Routing to HTTP allowing for a single Ratchet server to handle multiple apps
|
* Added HTTP Router component to allowing for a single Ratchet server to handle multiple apps
|
||||||
* BC: Decoupled HTTP from WebSocket component
|
* BC: Decoupled HTTP from WebSocket component
|
||||||
|
|
||||||
* 0.2.5 (2013-04-01)
|
* 0.2.5 (2013-04-01)
|
||||||
|
13
README.md
13
README.md
@ -38,9 +38,15 @@ Need help? Have a question? Want to provide feedback? Write a message on the
|
|||||||
<?php
|
<?php
|
||||||
use Ratchet\MessageComponentInterface;
|
use Ratchet\MessageComponentInterface;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
use Ratchet\Server\IoServer;
|
use Ratchet\Server\IoServer;
|
||||||
use Ratchet\Http\HttpServer;
|
use Ratchet\Http\HttpServer;
|
||||||
|
use Ratchet\Http\Router;
|
||||||
use Ratchet\WebSocket\WsServer;
|
use Ratchet\WebSocket\WsServer;
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
use Symfony\Component\Routing\RequestContext;
|
||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
@ -77,7 +83,12 @@ class Chat implements MessageComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the server application through the WebSocket protocol on port 8080
|
// Run the server application through the WebSocket protocol on port 8080
|
||||||
$server = IoServer::factory(new HttpServer(new WsServer(new Chat)), 8080);
|
// This will be made easier soon, routing currently in development
|
||||||
|
$routes->add('chatRoute', new Route('/chat', array(
|
||||||
|
'_controller' => new WsServer(new Chat)
|
||||||
|
)));
|
||||||
|
|
||||||
|
$server = IoServer::factory(new HttpServer(new Router(new UrlMatcher($routes, new RequestContext))), 8080);
|
||||||
$server->run();
|
$server->run();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -19,61 +19,30 @@ class HttpServer implements MessageComponentInterface {
|
|||||||
protected $_reqParser;
|
protected $_reqParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Symfony\Component\Routing\RouteCollection A collection with \Ratchet\MessageComponentInterface controllers
|
* @var \Ratchet\Http\HttpServerInterface
|
||||||
*/
|
*/
|
||||||
protected $_routes;
|
protected $_httpServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $host
|
* @param HttpServerInterface
|
||||||
* @param RouteCollection $collection
|
|
||||||
* @throws \UnexpectedValueException If a Route Controller does not map to a \Ratchet\MessageComponentInterface
|
|
||||||
*/
|
*/
|
||||||
public function __construct($host, RouteCollection $collection = null) {
|
public function __construct(HttpServerInterface $server) {
|
||||||
if (null === $collection) {
|
$this->_httpServer = $server;
|
||||||
$collection = new RouteCollection;
|
$this->_reqParser = new HttpRequestParser;
|
||||||
} else {
|
|
||||||
foreach ($collection as $routeName => $route) {
|
|
||||||
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\MessageComponentInterface');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$collection->setHost($host);
|
|
||||||
|
|
||||||
$this->_routes = $collection;
|
|
||||||
$this->_reqParser = new HttpRequestParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string
|
|
||||||
* @param string
|
|
||||||
* @param Ratchet\Http\HttpServerInterface
|
|
||||||
* @param array
|
|
||||||
*/
|
|
||||||
public function addRoute($name, $path, HttpServerInterface $controller) {
|
|
||||||
$this->_routes->add($name, new Route($path, array(
|
|
||||||
'_controller' => $controller
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @{inheritdoc}
|
* @{inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function onOpen(ConnectionInterface $conn) {
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
$conn->Http = new \StdClass;
|
$conn->httpHeadersReceived = false;
|
||||||
$conn->Http->headers = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @{inheritdoc}
|
* @{inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function onMessage(ConnectionInterface $from, $msg) {
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
if (true !== $from->Http->headers) {
|
if (true !== $from->httpHeadersReceived) {
|
||||||
try {
|
try {
|
||||||
if (null === ($request = $this->_reqParser->onMessage($from, $msg))) {
|
if (null === ($request = $this->_reqParser->onMessage($from, $msg))) {
|
||||||
return;
|
return;
|
||||||
@ -82,32 +51,20 @@ class HttpServer implements MessageComponentInterface {
|
|||||||
return $this->close($from, 413);
|
return $this->close($from, 413);
|
||||||
}
|
}
|
||||||
|
|
||||||
$context = new RequestContext($request->getUrl(), $request->getMethod(), $request->getHost(), $request->getScheme(), $request->getPort());
|
$from->httpHeadersReceived = true;
|
||||||
$matcher = new UrlMatcher($this->_routes, $context);
|
|
||||||
|
|
||||||
try {
|
return $this->_httpServer->onOpen($from, $request);
|
||||||
$route = $matcher->match($request->getPath());
|
|
||||||
} catch (MethodNotAllowedException $nae) {
|
|
||||||
return $this->close($from, 403);
|
|
||||||
} catch (ResourceNotFoundException $nfe) {
|
|
||||||
return $this->close($from, 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$from->Http->headers = true;
|
|
||||||
$from->Http->controller = $route['_controller'];
|
|
||||||
|
|
||||||
return $from->Http->controller->onOpen($from, $request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$from->Http->controller->onMessage($from, $msg);
|
$this->_httpServer->onMessage($from, $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @{inheritdoc}
|
* @{inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function onClose(ConnectionInterface $conn) {
|
public function onClose(ConnectionInterface $conn) {
|
||||||
if ($conn->Http->headers) {
|
if ($conn->httpHeadersReceived) {
|
||||||
$conn->Http->controller->onClose($conn);
|
$this->_httpServer->onClose($conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,10 +72,10 @@ class HttpServer implements MessageComponentInterface {
|
|||||||
* @{inheritdoc}
|
* @{inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function onError(ConnectionInterface $conn, \Exception $e) {
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
if ($conn->Http->headers) {
|
if ($conn->httpHeadersReceived) {
|
||||||
$conn->Http->controller->onError($conn, $e);
|
$this->_httpServer->onError($conn, $e);
|
||||||
} else {
|
} else {
|
||||||
$conn->close();
|
$this->close($conn, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,7 @@ use Guzzle\Http\Message\RequestInterface;
|
|||||||
interface HttpServerInterface extends MessageComponentInterface {
|
interface HttpServerInterface extends MessageComponentInterface {
|
||||||
/**
|
/**
|
||||||
* @param \Ratchet\ConnectionInterface $conn
|
* @param \Ratchet\ConnectionInterface $conn
|
||||||
* @param \Guzzle\Http\Message\RequestInterface $headers null is default because PHP won't let me overload; don't pass null!!!
|
* @param \Guzzle\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!!
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function onOpen(ConnectionInterface $conn, RequestInterface $headers = null);
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null);
|
||||||
}
|
}
|
||||||
|
61
src/Ratchet/Http/Router.php
Normal file
61
src/Ratchet/Http/Router.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Http;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Guzzle\Http\Message\RequestInterface;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
|
try {
|
||||||
|
$route = $this->_matcher->match($request->getPath());
|
||||||
|
} catch (MethodNotAllowedException $nae) {
|
||||||
|
return $this->close($from, 403);
|
||||||
|
} catch (ResourceNotFoundException $nfe) {
|
||||||
|
return $this->close($from, 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\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);
|
||||||
|
}
|
||||||
|
}
|
@ -73,9 +73,9 @@ class WsServer implements HttpServerInterface {
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function onOpen(ConnectionInterface $conn, RequestInterface $headers = null) {
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||||
$conn->WebSocket = new \StdClass;
|
$conn->WebSocket = new \StdClass;
|
||||||
$conn->WebSocket->request = $headers;
|
$conn->WebSocket->request = $request;
|
||||||
$conn->WebSocket->established = false;
|
$conn->WebSocket->established = false;
|
||||||
|
|
||||||
$this->attemptUpgrade($conn);
|
$this->attemptUpgrade($conn);
|
||||||
|
Loading…
Reference in New Issue
Block a user