Merge branch 'refs/heads/master' into 0.4

This commit is contained in:
Chris Boden 2014-06-08 11:45:43 -04:00
commit b6ec4aa904
17 changed files with 135 additions and 43 deletions

View File

@ -6,9 +6,6 @@ php:
- 5.5 - 5.5
- hhvm - hhvm
matrix:
allow_failures:
- php: hhvm
before_script: before_script:
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "session.serialize_handler = php" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;'
- composer install --dev --prefer-source - composer install --dev --prefer-source

View File

@ -8,6 +8,23 @@ CHANGELOG
--- ---
* 0.3.2 (2014-06-08)
* BF: No messages after closing handshake (fixed rare race condition causing 100% CPU)
* BF: Fixed accidental BC break from v0.3.1
* Added autoDelete parameter to Topic to destroy when empty of connections
* Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App)
* Normalized Exceptions in WAMP
* 0.3.1 (2014-05-26)
* Added query parameter support to Router, set in HTTP request (ws://server?hello=world)
* HHVM compatibility
* BF: React/0.4 support; CPU starvation bug fixes
* BF: Allow App::route to ignore Host header
* Added expected filters to WAMP Topic broadcast method
* Resource cleanup in WAMP TopicManager
* 0.3.0 (2013-10-14) * 0.3.0 (2013-10-14)
* Added the `App` class to help making Ratchet so easy to use it's silly * Added the `App` class to help making Ratchet so easy to use it's silly

View File

@ -9,7 +9,7 @@ Build up your application through simple interfaces and re-use your application
##WebSocket Compliance ##WebSocket Compliance
* Supports the RFC6455, HyBi-10+, and Hixie76 protocol versions (at the same time) * Supports the RFC6455, HyBi-10+, and Hixie76 protocol versions (at the same time)
* Tested on Chrome 13 - 31, Firefox 6 - 26, Safari 5.0.1 - 6.1, iOS 4.2 - 7 * Tested on Chrome 13+, Firefox 6+, Safari 5+, iOS 4.2+, IE 8+
* Ratchet [passes](http://socketo.me/reports/ab/) the [Autobahn Testsuite](http://autobahn.ws/testsuite) (non-binary messages) * Ratchet [passes](http://socketo.me/reports/ab/) the [Autobahn Testsuite](http://autobahn.ws/testsuite) (non-binary messages)
##Requirements ##Requirements
@ -19,7 +19,7 @@ To avoid proxy/firewall blockage it's recommended WebSockets are requested on po
In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines. 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). You can find more details in the [server conf docs](http://socketo.me/docs/deploy#serverconfiguration).
PHP 5.3.9 (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 (or higher) is *highly* recommended for its performance improvements.
### Documentation ### Documentation

View File

@ -107,7 +107,9 @@ class App {
$decorated = $controller; $decorated = $controller;
} }
$httpHost = $httpHost ?: $this->httpHost; if ($httpHost === null) {
$httpHost = $this->httpHost;
}
$allowedOrigins = array_values($allowedOrigins); $allowedOrigins = array_values($allowedOrigins);
if (0 === count($allowedOrigins)) { if (0 === count($allowedOrigins)) {

View File

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

View File

@ -72,6 +72,18 @@ class FlashPolicy implements MessageComponentInterface {
return $this; return $this;
} }
/**
* Removes all domains from the allowed access list.
*
* @return \Ratchet\Server\FlashPolicy
*/
public function clearAllowedAccess() {
$this->_access = array();
$this->_cacheValid = false;
return $this;
}
/** /**
* site-control defines the meta-policy for the current domain. A meta-policy specifies acceptable * site-control defines the meta-policy for the current domain. A meta-policy specifies acceptable
* domain policy files other than the master policy file located in the target domain's root and named * domain policy files other than the master policy file located in the target domain's root and named

View File

@ -27,6 +27,12 @@ class IoServer {
*/ */
protected $handlers; protected $handlers;
/**
* The socket server the Ratchet Application is run off of
* @var \React\Socket\ServerInterface
*/
public $socket;
/** /**
* @param \Ratchet\MessageComponentInterface $app The Ratchet application stack to host * @param \Ratchet\MessageComponentInterface $app The Ratchet application stack to host
* @param \React\Socket\ServerInterface $socket The React socket server to run the Ratchet application off of * @param \React\Socket\ServerInterface $socket The React socket server to run the Ratchet application off of
@ -42,6 +48,7 @@ class IoServer {
$this->loop = $loop; $this->loop = $loop;
$this->app = $app; $this->app = $app;
$this->socket = $socket;
$socket->on('connection', array($this, 'handleConnect')); $socket->on('connection', array($this, 'handleConnect'));

View File

@ -57,14 +57,7 @@ class SessionProvider implements HttpServerInterface {
$this->setOptions($options); $this->setOptions($options);
if (null === $serializer) { if (null === $serializer) {
// Temporarily fixing HHVM issue w/ reading ini values $serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh?
$handler_name = ini_get('session.serialize_handler');
if ('' === $handler_name) {
trigger_error('ini value session.seralize_handler was empty, assuming "php" - tmp hack/fix, bad things might happen', E_USER_WARNING);
$handler_name = 'php';
}
$serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase($handler_name)}Handler"; // awesome/terrible hack, eh?
if (!class_exists($serialClass)) { if (!class_exists($serialClass)) {
throw new \RuntimeException('Unable to parse session serialize handler'); throw new \RuntimeException('Unable to parse session serialize handler');
} }

View File

@ -79,8 +79,8 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
* @throws \Exception * @throws \Ratchet\Wamp\Exception
* @throws JsonException * @throws \Ratchet\Wamp\JsonException
*/ */
public function onMessage(ConnectionInterface $from, $msg) { public function onMessage(ConnectionInterface $from, $msg) {
$from = $this->connections[$from]; $from = $this->connections[$from];
@ -90,7 +90,7 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface {
} }
if (!is_array($json) || $json !== array_values($json)) { if (!is_array($json) || $json !== array_values($json)) {
throw new \UnexpectedValueException("Invalid WAMP message format"); throw new Exception("Invalid WAMP message format");
} }
switch ($json[0]) { switch ($json[0]) {
@ -134,7 +134,7 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface {
break; break;
default: default:
throw new Exception('Invalid message type'); throw new Exception('Invalid WAMP message type');
} }
} }

View File

@ -6,6 +6,13 @@ use Ratchet\ConnectionInterface;
* A topic/channel containing connections that have subscribed to it * A topic/channel containing connections that have subscribed to it
*/ */
class Topic implements \IteratorAggregate, \Countable { class Topic implements \IteratorAggregate, \Countable {
/**
* If true the TopicManager will destroy this object if it's ever empty of connections
* @deprecated in v0.4
* @type bool
*/
public $autoDelete = false;
private $id; private $id;
private $subscribers; private $subscribers;

View File

@ -54,13 +54,12 @@ class TopicManager implements WsServerInterface, WampServerInterface {
public function onUnsubscribe(ConnectionInterface $conn, $topic) { public function onUnsubscribe(ConnectionInterface $conn, $topic) {
$topicObj = $this->getTopic($topic); $topicObj = $this->getTopic($topic);
if ($conn->WAMP->subscriptions->contains($topicObj)) { if (!$conn->WAMP->subscriptions->contains($topicObj)) {
$conn->WAMP->subscriptions->detach($topicObj);
} else {
return; return;
} }
$this->topicLookup[$topic]->remove($conn); $this->cleanTopic($topicObj, $conn);
$this->app->onUnsubscribe($conn, $topicObj); $this->app->onUnsubscribe($conn, $topicObj);
} }
@ -77,11 +76,8 @@ class TopicManager implements WsServerInterface, WampServerInterface {
public function onClose(ConnectionInterface $conn) { public function onClose(ConnectionInterface $conn) {
$this->app->onClose($conn); $this->app->onClose($conn);
foreach ($this->topicLookup as $topic => $storage) { foreach ($this->topicLookup as $topic) {
$storage->remove($conn); $this->cleanTopic($topic, $conn);
if (0 === $storage->count()) {
unset($this->topicLookup[$topic]);
}
} }
} }
@ -114,4 +110,16 @@ class TopicManager implements WsServerInterface, WampServerInterface {
return $this->topicLookup[$topic]; return $this->topicLookup[$topic];
} }
protected function cleanTopic(Topic $topic, ConnectionInterface $conn) {
if ($conn->WAMP->subscriptions->contains($topic)) {
$conn->WAMP->subscriptions->detach($topic);
}
$this->topicLookup[$topic->getId()]->remove($conn);
if ($topic->autoDelete && 0 === $topic->count()) {
unset($this->topicLookup[$topic->getId()]);
}
}
} }

View File

@ -39,9 +39,9 @@ class WampServer implements MessageComponentInterface, WsServerInterface {
public function onMessage(ConnectionInterface $conn, $msg) { public function onMessage(ConnectionInterface $conn, $msg) {
try { try {
$this->wampProtocol->onMessage($conn, $msg); $this->wampProtocol->onMessage($conn, $msg);
} catch (JsonException $je) { } catch (Exception $we) {
$conn->close(1007); $conn->close(1007);
} catch (\UnexpectedValueException $uve) { } catch (JsonException $je) {
$conn->close(1007); $conn->close(1007);
} }
} }

View File

@ -8,12 +8,19 @@ use Ratchet\AbstractConnectionDecorator;
*/ */
class Connection extends AbstractConnectionDecorator { class Connection extends AbstractConnectionDecorator {
public function send($msg) { public function send($msg) {
if (!$this->WebSocket->closing) {
$this->getConnection()->send(chr(0) . $msg . chr(255)); $this->getConnection()->send(chr(0) . $msg . chr(255));
}
return $this; return $this;
} }
public function close() { public function close() {
if (!$this->WebSocket->closing) {
$this->getConnection()->send(chr(255));
$this->getConnection()->close(); $this->getConnection()->close();
$this->WebSocket->closing = true;
}
} }
} }

View File

@ -8,12 +8,17 @@ use Ratchet\WebSocket\Version\DataInterface;
* @property \StdClass $WebSocket * @property \StdClass $WebSocket
*/ */
class Connection extends AbstractConnectionDecorator { class Connection extends AbstractConnectionDecorator {
/**
* {@inheritdoc}
*/
public function send($msg) { public function send($msg) {
if (!$this->WebSocket->closing) {
if (!($msg instanceof DataInterface)) { if (!($msg instanceof DataInterface)) {
$msg = new Frame($msg); $msg = new Frame($msg);
} }
$this->getConnection()->send($msg->getContents()); $this->getConnection()->send($msg->getContents());
}
return $this; return $this;
} }
@ -22,6 +27,10 @@ class Connection extends AbstractConnectionDecorator {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function close($code = 1000) { public function close($code = 1000) {
if ($this->WebSocket->closing) {
return;
}
if ($code instanceof DataInterface) { if ($code instanceof DataInterface) {
$this->send($code); $this->send($code);
} else { } else {
@ -29,5 +38,7 @@ class Connection extends AbstractConnectionDecorator {
} }
$this->getConnection()->close(); $this->getConnection()->close();
$this->WebSocket->closing = true;
} }
} }

View File

@ -79,6 +79,7 @@ class WsServer implements HttpServerInterface {
$conn->WebSocket = new \StdClass; $conn->WebSocket = new \StdClass;
$conn->WebSocket->request = $request; $conn->WebSocket->request = $request;
$conn->WebSocket->established = false; $conn->WebSocket->established = false;
$conn->WebSocket->closing = false;
$this->attemptUpgrade($conn); $this->attemptUpgrade($conn);
} }
@ -87,6 +88,10 @@ class WsServer implements HttpServerInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function onMessage(ConnectionInterface $from, $msg) { public function onMessage(ConnectionInterface $from, $msg) {
if ($from->WebSocket->closing) {
return;
}
if (true === $from->WebSocket->established) { if (true === $from->WebSocket->established) {
return $from->WebSocket->version->onMessage($this->connections[$from], $msg); return $from->WebSocket->version->onMessage($this->connections[$from], $msg);
} }

View File

@ -258,7 +258,7 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase {
* @dataProvider badFormatProvider * @dataProvider badFormatProvider
*/ */
public function testValidJsonButInvalidProtocol($message) { public function testValidJsonButInvalidProtocol($message) {
$this->setExpectedException('\UnexpectedValueException'); $this->setExpectedException('\Ratchet\Wamp\Exception');
$conn = $this->newConn(); $conn = $this->newConn();
$this->_comp->onOpen($conn); $this->_comp->onOpen($conn);

View File

@ -159,9 +159,7 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase {
$this->mngr->onClose($this->conn); $this->mngr->onClose($this->conn);
} }
public function testConnIsRemovedFromTopicOnClose() { protected function topicProvider($name) {
$name = 'State testing';
$class = new \ReflectionClass('Ratchet\Wamp\TopicManager'); $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
$method = $class->getMethod('getTopic'); $method = $class->getMethod('getTopic');
$method->setAccessible(true); $method->setAccessible(true);
@ -171,14 +169,42 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase {
$topic = $method->invokeArgs($this->mngr, array($name)); $topic = $method->invokeArgs($this->mngr, array($name));
return array($topic, $attribute);
}
public function testConnIsRemovedFromTopicOnClose() {
$name = 'State Testing';
list($topic, $attribute) = $this->topicProvider($name);
$this->assertCount(1, $attribute->getValue($this->mngr)); $this->assertCount(1, $attribute->getValue($this->mngr));
$this->mngr->onSubscribe($this->conn, $name); $this->mngr->onSubscribe($this->conn, $name);
$this->mngr->onClose($this->conn); $this->mngr->onClose($this->conn);
$this->assertFalse($topic->has($this->conn)); $this->assertFalse($topic->has($this->conn));
}
$this->assertCount(0, $attribute->getValue($this->mngr)); public static function topicConnExpectationProvider() {
return array(
array(true, 'onClose', 0)
, array(true, 'onUnsubscribe', 0)
, array(false, 'onClose', 1)
, array(false, 'onUnsubscribe', 1)
);
}
/**
* @dataProvider topicConnExpectationProvider
*/
public function testTopicRetentionFromLeavingConnections($autoDelete, $methodCall, $expectation) {
$topicName = 'checkTopic';
list($topic, $attribute) = $this->topicProvider($topicName);
$topic->autoDelete = $autoDelete;
$this->mngr->onSubscribe($this->conn, $topicName);
call_user_func_array(array($this->mngr, $methodCall), array($this->conn, $topicName));
$this->assertCount($expectation, $attribute->getValue($this->mngr));
} }
public function testOnErrorBubbles() { public function testOnErrorBubbles() {