[WebSocket] [WAMP] Sub-Protocols
Updated how Ratchet handles WebSocket sub-protocols Broke out WsServerInterface to not extend MessageInterface; Components will instead use Interface segregation principle WAMP is now able to work without the developer having to manually enable the WAMP sub-protocol
This commit is contained in:
parent
ae826aa24f
commit
f729be2ef3
@ -2,6 +2,7 @@
|
|||||||
namespace Ratchet\Session;
|
namespace Ratchet\Session;
|
||||||
use Ratchet\MessageComponentInterface;
|
use Ratchet\MessageComponentInterface;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
use Ratchet\Session\Storage\VirtualSessionStorage;
|
use Ratchet\Session\Storage\VirtualSessionStorage;
|
||||||
use Ratchet\Session\Serialize\HandlerInterface;
|
use Ratchet\Session\Serialize\HandlerInterface;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
@ -13,7 +14,7 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
|
|||||||
* Your website must also use Symfony HttpFoundation Sessions to read your sites session data
|
* Your website must also use Symfony HttpFoundation Sessions to read your sites session data
|
||||||
* If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer)
|
* If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer)
|
||||||
*/
|
*/
|
||||||
class SessionProvider implements MessageComponentInterface {
|
class SessionProvider implements MessageComponentInterface, WsServerInterface {
|
||||||
/**
|
/**
|
||||||
* @var Ratchet\MessageComponentInterface
|
* @var Ratchet\MessageComponentInterface
|
||||||
*/
|
*/
|
||||||
@ -109,6 +110,17 @@ class SessionProvider implements MessageComponentInterface {
|
|||||||
return $this->_app->onError($conn, $e);
|
return $this->_app->onError($conn, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSubProtocols() {
|
||||||
|
if ($this->_app instanceof WsServerInterface) {
|
||||||
|
return $this->_app->getSubProtocols();
|
||||||
|
} else {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set all the php session. ini options
|
* Set all the php session. ini options
|
||||||
* © Symfony
|
* © Symfony
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Ratchet\Wamp;
|
namespace Ratchet\Wamp;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
use Ratchet\WebSocket\WsServerInterface;
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ use Ratchet\ConnectionInterface;
|
|||||||
* | EVENT | 8 | Server-to-Client |
|
* | EVENT | 8 | Server-to-Client |
|
||||||
* +--------------+----+------------------+
|
* +--------------+----+------------------+
|
||||||
*/
|
*/
|
||||||
class WampServer implements WsServerInterface {
|
class WampServer implements MessageComponentInterface, WsServerInterface {
|
||||||
const MSG_WELCOME = 0;
|
const MSG_WELCOME = 0;
|
||||||
const MSG_PREFIX = 1;
|
const MSG_PREFIX = 1;
|
||||||
const MSG_CALL = 2;
|
const MSG_CALL = 2;
|
||||||
@ -55,8 +56,15 @@ class WampServer implements WsServerInterface {
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getSubProtocol() {
|
public function getSubProtocols() {
|
||||||
return 'wamp';
|
if ($this->_decorating instanceof WsServerInterface) {
|
||||||
|
$subs = $this->_decorating->getSubProtocols();
|
||||||
|
$subs[] = 'wamp';
|
||||||
|
|
||||||
|
return $subs;
|
||||||
|
} else {
|
||||||
|
return array('wamp');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,7 +78,7 @@ class WampServer implements WsServerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @{inheritdoc}
|
* {@inheritdoc}
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws JsonException
|
* @throws JsonException
|
||||||
*/
|
*/
|
||||||
|
@ -6,7 +6,6 @@ use Ratchet\ConnectionInterface;
|
|||||||
/**
|
/**
|
||||||
* A (not literal) extension of Ratchet\ConnectionInterface
|
* A (not literal) extension of Ratchet\ConnectionInterface
|
||||||
* onMessage is replaced by various types of messages for this protocol (pub/sub or rpc)
|
* onMessage is replaced by various types of messages for this protocol (pub/sub or rpc)
|
||||||
* @todo Thought: URI as class. Class has short and long version stored (if as prefix)
|
|
||||||
*/
|
*/
|
||||||
interface WampServerInterface extends ComponentInterface {
|
interface WampServerInterface extends ComponentInterface {
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,7 @@ use Ratchet\WebSocket\Guzzle\Http\Message\RequestFactory;
|
|||||||
class WsServer implements MessageComponentInterface {
|
class WsServer implements MessageComponentInterface {
|
||||||
/**
|
/**
|
||||||
* Decorated component
|
* Decorated component
|
||||||
* @var Ratchet\MessageComponentInterface
|
* @var Ratchet\MessageComponentInterface|WsServerInterface
|
||||||
*/
|
*/
|
||||||
protected $_decorating;
|
protected $_decorating;
|
||||||
|
|
||||||
@ -41,8 +41,17 @@ class WsServer implements MessageComponentInterface {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
* @temporary
|
* @temporary
|
||||||
*/
|
*/
|
||||||
public $accepted_subprotocols = array();
|
protected $acceptedSubProtocols = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag if we have checked the decorated component for sub-protocols
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
private $isSpGenerated = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Ratchet\MessageComponentInterface Your application to run with WebSockets
|
||||||
|
*/
|
||||||
public function __construct(MessageComponentInterface $component) {
|
public function __construct(MessageComponentInterface $component) {
|
||||||
$this->_decorating = $component;
|
$this->_decorating = $component;
|
||||||
$this->connections = new \SplObjectStorage;
|
$this->connections = new \SplObjectStorage;
|
||||||
@ -77,19 +86,10 @@ class WsServer implements MessageComponentInterface {
|
|||||||
$response = $from->WebSocket->version->handshake($from->WebSocket->headers);
|
$response = $from->WebSocket->version->handshake($from->WebSocket->headers);
|
||||||
$from->WebSocket->handshake = true;
|
$from->WebSocket->handshake = true;
|
||||||
|
|
||||||
// This block is to be moved/changed later
|
if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-Protocol', ',')))) {
|
||||||
$agreed_protocols = array();
|
$response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols);
|
||||||
$requested_protocols = $from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-Protocol', ',');
|
|
||||||
if (null !== $requested_protocols) {
|
|
||||||
foreach ($this->accepted_subprotocols as $sub_protocol) {
|
|
||||||
if (false !== $requested_protocols->hasValue($sub_protocol)) {
|
|
||||||
$agreed_protocols[] = $sub_protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count($agreed_protocols) > 0) {
|
|
||||||
$response->setHeader('Sec-WebSocket-Protocol', implode(',', $agreed_protocols));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->setHeader('X-Powered-By', \Ratchet\VERSION);
|
$response->setHeader('X-Powered-By', \Ratchet\VERSION);
|
||||||
$header = (string)$response;
|
$header = (string)$response;
|
||||||
|
|
||||||
@ -205,6 +205,42 @@ class WsServer implements MessageComponentInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isSubProtocolSupported($name) {
|
||||||
|
if (!$this->isSpGenerated) {
|
||||||
|
if ($this->_decorating instanceof WsServerInterface) {
|
||||||
|
$this->acceptedSubProtocols = array_flip($this->_decorating->getSubProtocols());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->isSpGenerated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_key_exists($name, $this->acceptedSubProtocols);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Traversable
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getSubProtocolString(\Traversable $requested = null) {
|
||||||
|
if (null === $requested) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = '';
|
||||||
|
|
||||||
|
foreach ($requested as $sub) {
|
||||||
|
if ($this->isSubProtocolSupported($sub)) {
|
||||||
|
$string .= $sub . ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr($string, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable a version of the WebSocket protocol *cough*Hixie76*cough*
|
* Disable a version of the WebSocket protocol *cough*Hixie76*cough*
|
||||||
* @param string The name of the version to disable
|
* @param string The name of the version to disable
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Ratchet\WebSocket;
|
namespace Ratchet\WebSocket;
|
||||||
use Ratchet\MessageComponentInterface;
|
|
||||||
|
|
||||||
interface WsServerInterface extends MessageComponentInterface {
|
interface WsServerInterface {
|
||||||
/**
|
/**
|
||||||
* Currently instead of this, I'm setting header in the Connection object passed around...not sure which I like more
|
* If any component in a stack supports a WebSocket sub-protocol return each supported in an array
|
||||||
* @param string
|
* @return array
|
||||||
|
* @temporary This method may be removed in future version (note tha twill not break code, just make some code obsolete)
|
||||||
*/
|
*/
|
||||||
//function setHeaders($headers);
|
function getSubProtocols();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function getSubProtocol();
|
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Ratchet\Tests\Mock;
|
namespace Ratchet\Tests\Mock;
|
||||||
use Ratchet\MessageComponentInterface;
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
use Ratchet\Tests\Mock\Socket as MockSocket;
|
use Ratchet\Tests\Mock\Socket as MockSocket;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
/**
|
class Component implements MessageComponentInterface, WsServerInterface {
|
||||||
* @todo Rename to MessageComponent
|
|
||||||
*/
|
|
||||||
class Component implements MessageComponentInterface {
|
|
||||||
public $last = array();
|
public $last = array();
|
||||||
|
|
||||||
|
public $protocols = array();
|
||||||
|
|
||||||
public function __construct(ComponentInterface $app = null) {
|
public function __construct(ComponentInterface $app = null) {
|
||||||
$this->last[__FUNCTION__] = func_get_args();
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
}
|
}
|
||||||
@ -29,4 +29,8 @@ class Component implements MessageComponentInterface {
|
|||||||
public function onError(ConnectionInterface $conn, \Exception $e) {
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
$this->last[__FUNCTION__] = func_get_args();
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSubProtocols() {
|
||||||
|
return $this->protocols;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,11 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Ratchet\Tests\Mock;
|
namespace Ratchet\Tests\Mock;
|
||||||
use Ratchet\Wamp\WampServerInterface;
|
use Ratchet\Wamp\WampServerInterface;
|
||||||
|
use Ratchet\WebSocket\WsServerInterface;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
class WampComponent implements WampServerInterface {
|
class WampComponent implements WampServerInterface, WsServerInterface {
|
||||||
public $last = array();
|
public $last = array();
|
||||||
|
|
||||||
|
public $protocols = array();
|
||||||
|
|
||||||
|
public function getSubProtocols() {
|
||||||
|
return $this->protocols;
|
||||||
|
}
|
||||||
|
|
||||||
public function onCall(ConnectionInterface $conn, $id, $procURI, array $params) {
|
public function onCall(ConnectionInterface $conn, $id, $procURI, array $params) {
|
||||||
$this->last[__FUNCTION__] = func_get_args();
|
$this->last[__FUNCTION__] = func_get_args();
|
||||||
}
|
}
|
||||||
|
@ -107,4 +107,19 @@ class SessionProviderTest extends \PHPUnit_Framework_TestCase {
|
|||||||
$this->assertEquals($conns[2], $mock->last['onError'][0]);
|
$this->assertEquals($conns[2], $mock->last['onError'][0]);
|
||||||
$this->assertEquals($e, $mock->last['onError'][1]);
|
$this->assertEquals($e, $mock->last['onError'][1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsReturnsArray() {
|
||||||
|
$mock = new MockComponent;
|
||||||
|
$comp = new SessionProvider($mock, new NullSessionHandler);
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($comp->getSubProtocols()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsGetFromApp() {
|
||||||
|
$mock = new MockComponent;
|
||||||
|
$mock->protocols = array('hello', 'world');
|
||||||
|
$comp = new SessionProvider($mock, new NullSessionHandler);
|
||||||
|
|
||||||
|
$this->assertGreaterThanOrEqual(2, count($comp->getSubProtocols()));
|
||||||
|
}
|
||||||
}
|
}
|
@ -203,4 +203,14 @@ class WampServerTest extends \PHPUnit_Framework_TestCase {
|
|||||||
$this->_comp->onOpen($conn);
|
$this->_comp->onOpen($conn);
|
||||||
$this->_comp->onMessage($conn, 'Hello World!');
|
$this->_comp->onMessage($conn, 'Hello World!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsReturnsArray() {
|
||||||
|
$this->assertTrue(is_array($this->_comp->getSubProtocols()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSubProtocolsGetFromApp() {
|
||||||
|
$this->_app->protocols = array('hello', 'world');
|
||||||
|
|
||||||
|
$this->assertGreaterThanOrEqual(3, count($this->_comp->getSubProtocols()));
|
||||||
|
}
|
||||||
}
|
}
|
48
tests/Ratchet/Tests/WebSocket/WsServerTest.php
Normal file
48
tests/Ratchet/Tests/WebSocket/WsServerTest.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\Tests\WebSocket;
|
||||||
|
use Ratchet\WebSocket\WsServer;
|
||||||
|
use Ratchet\Tests\Mock\Component as MockComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Ratchet\WebSocket\WsServer
|
||||||
|
*/
|
||||||
|
class WsServerTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $comp;
|
||||||
|
|
||||||
|
protected $serv;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$this->comp = new MockComponent;
|
||||||
|
$this->serv = new WsServer($this->comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsSubProtocolSupported() {
|
||||||
|
$this->comp->protocols = array('hello', 'world');
|
||||||
|
|
||||||
|
$this->assertTrue($this->serv->isSubProtocolSupported('hello'));
|
||||||
|
$this->assertFalse($this->serv->isSubProtocolSupported('nope'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function protocolProvider() {
|
||||||
|
return array(
|
||||||
|
array('hello,world', 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'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider protocolProvider
|
||||||
|
*/
|
||||||
|
public function testGetSubProtocolString($expected, $supported, $requested) {
|
||||||
|
$this->comp->protocols = $supported;
|
||||||
|
$req = (null === $requested ? $requested : new \ArrayIterator($requested));
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('Ratchet\\WebSocket\\WsServer');
|
||||||
|
$method = $class->getMethod('getSubProtocolString');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $method->invokeArgs($this->serv, array($req)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user