From ac8644125c2332f8b07d0aa5e90dbf466cc98f88 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Mon, 24 Oct 2011 09:26:15 -0400 Subject: [PATCH] Refactoring Major restructure, dropped aggregate idea, can't get around php golden hammer, the all mighty array, problem Unit tests broken --- lib/Ratchet/Protocol/WebSocket.php | 18 +++- lib/Ratchet/ReceiverInterface.php | 11 ++- lib/Ratchet/Server.php | 96 ++++++++++++++++--- lib/Ratchet/Server/Aggregator.php | 69 +++++++++++++ lib/Ratchet/Server/Client.php | 13 +++ lib/Ratchet/Socket.php | 40 ++++++-- tests/Ratchet/Tests/Mock/Application.php | 12 ++- .../Tests/Mock/{Socket.php => FakeSocket.php} | 2 +- tests/Ratchet/Tests/Mock/Protocol.php | 11 ++- tests/Ratchet/Tests/Mock/SocketAggregator.php | 42 ++++++++ tests/Ratchet/Tests/ServerTest.php | 2 +- tests/Ratchet/Tests/SocketAggregatorTest.php | 94 ++++++++++++++++++ tests/Ratchet/Tests/SocketTest.php | 2 +- 13 files changed, 376 insertions(+), 36 deletions(-) create mode 100644 lib/Ratchet/Server/Aggregator.php create mode 100644 lib/Ratchet/Server/Client.php rename tests/Ratchet/Tests/Mock/{Socket.php => FakeSocket.php} (96%) create mode 100644 tests/Ratchet/Tests/Mock/SocketAggregator.php create mode 100644 tests/Ratchet/Tests/SocketAggregatorTest.php diff --git a/lib/Ratchet/Protocol/WebSocket.php b/lib/Ratchet/Protocol/WebSocket.php index 2d41df4..ddb80bf 100644 --- a/lib/Ratchet/Protocol/WebSocket.php +++ b/lib/Ratchet/Protocol/WebSocket.php @@ -1,6 +1,13 @@ _master = $socket; - $this->_debug = (boolean)$debug; + public function __construct(Socket $host) { + $this->_master = $host; + + $socket = $host->getResource(); + + $this->_resources[] = $socket; + $this->_connections[$socket] = $host; + } + + /** + * @todo Receive an interface that creates clients based on interface, decorator pattern for Socket + */ + public function setClientFactory($s) { } public function attatchReceiver(ReceiverInterface $receiver) { + $receiver->setUp($this); $this->_receivers[spl_object_hash($receiver)] = $receiver; } + public function getMaster() { + return $this->_master; + } + + public function getClients() { + return $this->_connections; + } + /* * @param mixed * @param int - * @throws Ratchet\Exception + * @throws Exception + * @todo Validate address. Use socket_get_option, if AF_INET must be IP, if AF_UNIX must be path */ public function run($address = '127.0.0.1', $port = 1025) { if (count($this->_receivers) == 0) { - throw new \RuntimeException("No receiver has been attatched to the server"); + throw new \RuntimeException("No receiver has been attached to the server"); } set_time_limit(0); ob_implicit_flush(); +// socket_create_listen($port); instead of create, bind, listen + if (false === ($this->_master->bind($address, (int)$port))) { // perhaps I should do some checks here... - throw new Exception(); + throw new Exception; } if (false === ($this->_master->listen())) { - throw new Exception(); + throw new Exception; } do { - $changed = $this->_connections; - $num_changed = socket_select($changed_sockets, $write = NULL, $except = NULL, NULL); -// foreach($changed as $) + $changed = $this->_resources; + $num_changed = @socket_select($changed, $write = NULL, $except = NULL, NULL); + foreach($changed as $resource) { + if ($this->_master->getResource() == $resource) { + $new_connection = clone $this->_master; + $this->_resources[] = $new_connection->getResource(); + $this->_connections[$new_connection->getResource()] = $new_connection; - } while (!$this->_debug); + // /here $this->_receivers->handleConnection($new_connection); + $this->tmpRIterator('handleConnect', $new_connection); + } else { + $conn = $this->_connections[$resource]; + $data = null; + $bytes = $conn->recv($data, 4096, 0); + + if ($bytes == 0) { + $this->tmpRIterator('handleClose', $conn); + // $this->_receivers->handleDisconnect($conn); + + unset($this->_connections[$resource]); + unset($this->_resources[array_search($resource, $this->_resources)]); + } else { + $this->tmpRIterator('handleMessage', $data, $conn); + // new Message + // $this->_receivers->handleMessage($msg, $conn); + } + } + } + } while (true); // $this->_master->set_nonblock(); // declare(ticks = 1); } + + protected function tmpRIterator() { + $args = func_get_args(); + $fn = array_shift($args); + foreach ($this->_receivers as $app) { + call_user_func_array(array($app, $fn), $args); + } + } } \ No newline at end of file diff --git a/lib/Ratchet/Server/Aggregator.php b/lib/Ratchet/Server/Aggregator.php new file mode 100644 index 0000000..1396701 --- /dev/null +++ b/lib/Ratchet/Server/Aggregator.php @@ -0,0 +1,69 @@ +_sockets = new \SplObjectStorage; + + $this->_master = $master; + $this->insert($this->_master); + } + + /** + * @return Socket + */ + public function getMaster() { + return $this->_master; + } + + /** + * @param resource + * @return Socket + */ + public function getClientByResource($resource) { + if ($this->_sockets->contains($resource)) { + return $this->_sockets[$resource]; + } + + throw new Exception("Resource not found"); + } + + protected function insert(Socket $socket) { + $resource = $socket->getResource(); + + $this->_sockets[$socket] = $resource; + $this->_resources[] = $resource; + } + + /** + * @return SplObjectStorage + */ + public function getIterator() { + return $this->_sockets; + } + + /** + * @return array + */ + public function asArray() { + return $this->_resources; + } +} \ No newline at end of file diff --git a/lib/Ratchet/Server/Client.php b/lib/Ratchet/Server/Client.php new file mode 100644 index 0000000..2bfcc6e --- /dev/null +++ b/lib/Ratchet/Server/Client.php @@ -0,0 +1,13 @@ +_socket = $socket; + } + + +} \ No newline at end of file diff --git a/lib/Ratchet/Socket.php b/lib/Ratchet/Socket.php index 263bf9a..9b01e89 100644 --- a/lib/Ratchet/Socket.php +++ b/lib/Ratchet/Socket.php @@ -10,7 +10,7 @@ class Socket { /** * @type resource */ - public $_resource; + protected $_resource; public static $_defaults = Array( 'domain' => AF_INET @@ -34,6 +34,17 @@ class Socket { } } + /** + * @return resource (Socket) + */ + public function getResource() { + return $this->_resource; + } + + public function __clone() { + $this->_resource = @socket_accept($this->_resource); + } + /** * @param Ratchet\Protocol\ProtocolInterface * @return Ratchet\Socket @@ -70,7 +81,7 @@ class Socket { } } - return Array($domain, $type, $protocol); + return array($domain, $type, $protocol); } /** @@ -88,7 +99,17 @@ class Socket { $write = static::mungForSelect($write); $except = static::mungForSelect($except); - socket_select($read, $write, $except, $tv_sec, $tv_usec); + return socket_select($read, $write, $except, $tv_sec, $tv_usec); + } + + /** + * @param string + * @param int + * @param int + * @return int + */ + public function recv(&$buf, $len, $flags) { + return socket_recv($this->_resource, $buf, $len, $flags); } /** @@ -105,9 +126,9 @@ class Socket { throw new \InvalidArgumentException('Object pass is not traversable'); } - $return = Array(); + $return = array(); foreach ($collection as $key => $socket) { - $return[$key] = ($socket instanceof \Ratchet\Socket ? $socket->_resource : $socket); + $return[$key] = ($socket instanceof \Ratchet\Socket ? $socket->getResource() : $socket); } return $return; @@ -118,12 +139,19 @@ class Socket { * @param string * @param Array * @return mixed + * @throws Exception * @throws \BadMethodCallException */ public function __call($method, $arguments) { if (function_exists('socket_' . $method)) { array_unshift($arguments, $this->_resource); - return call_user_func_array('socket_' . $method, $arguments); + $result = @call_user_func_array('socket_' . $method, $arguments); + + if (false === $result) { + throw new Exception; + } + + return $result; } throw new \BadMethodCallException("{$method} is not a valid socket function"); diff --git a/tests/Ratchet/Tests/Mock/Application.php b/tests/Ratchet/Tests/Mock/Application.php index 08e8c79..7848b37 100644 --- a/tests/Ratchet/Tests/Mock/Application.php +++ b/tests/Ratchet/Tests/Mock/Application.php @@ -1,18 +1,24 @@ _arguments['domain'], $this->_arguments['type'], $this->_arguments['protocol']) = static::getConfig($domain, $type, $protocol); + } + + public function accept() { + } + + public function bind($address, $port) { + } + + public function close() { + } + + public function get_option($level, $optname) { + return $this->_options[$level][$optname]; + } + + public function listen($backlog) { + } + + public function recv($buf, $len, $flags) { + } + + public function set_option($level, $optname, $optval) { + if (!isset($this->_options[$level])) { + $this->_options[$level] = Array(); + } + + $this->_options[$level][$optname] = $optval; + } + + public function write($buffer, $length = 0) { + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/ServerTest.php b/tests/Ratchet/Tests/ServerTest.php index b7d193e..d1fe6a5 100644 --- a/tests/Ratchet/Tests/ServerTest.php +++ b/tests/Ratchet/Tests/ServerTest.php @@ -1,7 +1,7 @@ getMethod($name); + $method->setAccessible(true); + + return $method; + } + + public function setUp() { + $this->_socket = new Socket(); + } + +/* + public function testWhatGoesInConstructComesOut() { + $this->assertTrue(false); + } +*/ + + public function testGetDefaultConfigForConstruct() { + $ref_conf = static::getMethod('getConfig'); + $config = $ref_conf->invokeArgs($this->_socket, Array()); + + $this->assertEquals(array_values(Socket::$_defaults), $config); + } + + public function testInvalidConstructorArguments() { + $this->setExpectedException('\\Ratchet\\Exception'); + $socket = new RealSocket('invalid', 'param', 'derp'); + } + + public function testConstructAndCallByOpenAndClose() { + $socket = new RealSocket(); + $socket->close(); + } + + public function testInvalidSocketCall() { + $this->setExpectedException('\\BadMethodCallException'); + $this->_socket->fake_method(); + } + + public function testConstructionFromProtocolInterfaceConfig() { + $protocol = new Protocol(); + $socket = Socket::createFromConfig($protocol); + + $this->assertInstanceOf('\\Ratchet\\Socket', $socket); + } + + public function testCreationFromConfigOutputMatchesInput() { + $protocol = new Protocol(); + $socket = Socket::createFromConfig($protocol); + $config = $protocol::getDefaultConfig(); + + // change this to array_filter late + unset($config['options']); + + $this->assertAttributeEquals($config, '_arguments', $socket); + } + + public function asArrayProvider() { + return Array( + Array(Array('hello' => 'world'), Array('hello' => 'world')) + , Array(null, null) + , Array(Array('hello' => 'world'), new \ArrayObject(Array('hello' => 'world'))) + ); + } + + /** + * @dataProvider asArrayProvider + */ + public function testMethodMungforselectReturnsExpectedValues($output, $input) { + $method = static::getMethod('mungForSelect'); + $return = $method->invokeArgs($this->_socket, Array($input)); + + $this->assertEquals($return, $output); + } + + public function testMethodMungforselectRejectsNonTraversable() { + $this->setExpectedException('\\InvalidArgumentException'); + $method = static::getMethod('mungForSelect'); + $method->invokeArgs($this->_socket, Array('I am upset with PHP ATM')); + } +} \ No newline at end of file diff --git a/tests/Ratchet/Tests/SocketTest.php b/tests/Ratchet/Tests/SocketTest.php index e9f9e9e..cce6619 100644 --- a/tests/Ratchet/Tests/SocketTest.php +++ b/tests/Ratchet/Tests/SocketTest.php @@ -1,6 +1,6 @@