diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..131c4e4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". + +/examples export-ignore +/tests export-ignore +/.travis.yml export-ignore +/phpunit.xml export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..646419c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +atlassian-ide-plugin.xml diff --git a/.travis.yml b/.travis.yml index 8662f1b..16514eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,5 @@ before_script: - composer require --dev phpunit/phpunit 3.7.18 - wget https://scrutinizer-ci.com/ocular.phar script: - - phpunit --bootstrap=bootstrap.php --coverage-clover=coverage.clover --coverage-text="php://stdout" --configuration="phpunit.xml" src/Navarr/Socket/Tests + - vendor/bin/phpunit --coverage-clover=coverage.clover --configuration="phpunit.xml" - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/bootstrap.php b/bootstrap.php deleted file mode 100644 index eec395e..0000000 --- a/bootstrap.php +++ /dev/null @@ -1,9 +0,0 @@ -register(); -$loader->registerNamespace('Navarr', __DIR__ .'/src'); diff --git a/composer.json b/composer.json index e654003..bf11ceb 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "Navarr/Socket", + "name": "navarr/sockets", "description": "Sockets in PHP", "minimum-stability": "stable", "authors": [ @@ -19,7 +19,7 @@ "phpunit/phpunit": "3.7.*" }, "autoload": { - "psr-0": { + "psr-4": { "Navarr\\Socket\\": "src/" } } diff --git a/examples/EchoServer.php b/examples/EchoServer.php index 2fa27e6..4e4cc6b 100644 --- a/examples/EchoServer.php +++ b/examples/EchoServer.php @@ -1,10 +1,10 @@ clientMap[(string)$client] = new WebClient($server, $client); + $this->clientMap[(string) $client] = new WebClient($server, $client); } public function onInput(Server $server, Socket $client, $message) @@ -32,14 +32,14 @@ public function onInput(Server $server, Socket $client, $message) $messages = explode("\n", $message); foreach ($messages as $message) { $message .= "\n"; - $this->clientMap[(string)$client]->dispatch($message); + $this->clientMap[(string) $client]->dispatch($message); } } public function onDisconnect(Server $server, Socket $client, $message) { echo "Disconnect\n"; - unset($this->clientMap[(string)$client]); + unset($this->clientMap[(string) $client]); } } @@ -68,17 +68,18 @@ public function dispatch($message) echo trim($message), "\n"; $message = trim($message); if ($this->firstLine === null) { - $tokens = explode(" ", $message, 3); - $this->verb = $tokens[0]; + $tokens = explode(" ", $message, 3); + $this->verb = $tokens[0]; $this->resource = $tokens[1]; $this->protocol = $tokens[2]; $this->firstLine = $message; - $this->lastLine = $message; + $this->lastLine = $message; + return; } if ($message !== '') { - $tokens = explode(": ", $message, 2); + $tokens = explode(": ", $message, 2); $this->headers[$tokens[0]] = $tokens[1]; } if ($this->lastLine === '' && $message === '') { @@ -86,7 +87,7 @@ public function dispatch($message) $this->writeLine('Content-Type: text/plain'); $this->writeLine(); - $url = $this->headers['Host'].$this->resource; + $url = $this->headers['Host'] . $this->resource; $this->writeLine( "You requested {$url} using verb {$this->verb} over {$this->protocol}" ); diff --git a/phpunit.xml b/phpunit.xml index bede6dd..03592c3 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,19 +1,17 @@ - + - - src/Navarr/Socket/Tests + + ./tests - - - src - - src/Navarr/Socket/Tests - - - - - + + - \ No newline at end of file + diff --git a/src/Navarr/Socket/Exception/SocketException.php b/src/Exception/SocketException.php similarity index 92% rename from src/Navarr/Socket/Exception/SocketException.php rename to src/Exception/SocketException.php index 77de70b..555fa63 100644 --- a/src/Navarr/Socket/Exception/SocketException.php +++ b/src/Exception/SocketException.php @@ -13,7 +13,8 @@ public function __construct($message = null) } elseif (is_resource($message)) { $errno = socket_last_error($message); } else { - parent::__construct((string)$message); + parent::__construct((string) $message); + return; } diff --git a/src/Navarr/Socket/Server.php b/src/Server.php similarity index 88% rename from src/Navarr/Socket/Server.php rename to src/Server.php index 40dced3..e09da0b 100644 --- a/src/Navarr/Socket/Server.php +++ b/src/Server.php @@ -22,6 +22,12 @@ class Server */ protected $port; + /** + * Seconds to wait on a socket before timing out + * @var int|null + */ + protected $timeout = null; + /** * Domain * @see http://php.net/manual/en/function.socket-create.php @@ -75,6 +81,11 @@ class Server */ const HOOK_DISCONNECT = '__NAVARR_SOCKET_SERVER_DISCONNECT__'; + /** + * Constant String for Server Timeout + */ + const HOOK_TIMEOUT = '__NAVARR_SOCKET_SERVER_TIMEOUT__'; + /** * Return value from a hook callable to tell the server not to run the other hooks */ @@ -90,12 +101,15 @@ class Server * * @param string $address An IPv4, IPv6, or Unix socket address * @param int $port + * @param int $timeout Seconds to wait on a socket before timing it out */ - public function __construct($address, $port = 0) + public function __construct($address, $port = 0, $timeout = null) { set_time_limit(0); $this->address = $address; - $this->port = $port; + $this->port = $port; + $this->timeout = $timeout; + switch (true) { case filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4): $this->domain = AF_INET; @@ -145,14 +159,20 @@ protected function loopOnce() $read = array_merge(array($this->masterSocket), $this->clients); // Set up a block call to socket_select - $write = null; + $write = null; $except = null; - Socket::select($read, $write, $except, null); + $ret = Socket::select($read, $write, $except, $this->timeout); + if ($this->timeout != null && $ret == 0) { + if ($this->triggerHooks(self::HOOK_TIMEOUT, $this->masterSocket) === false) { + // This only happens when a hook tells the server to shut itself down. + return false; + } + } // If there is a new connection, add it if (in_array($this->masterSocket, $read)) { unset($read[array_search($this->masterSocket, $read)]); - $socket = $this->masterSocket->accept(); + $socket = $this->masterSocket->accept(); $this->clients[] = $socket; if ($this->triggerHooks(self::HOOK_CONNECT, $socket) === false) { @@ -191,7 +211,9 @@ protected function loopOnce() /** * Overrideable Read Functionality + * * @param Socket $client + * * @return string */ protected function read(Socket $client) @@ -201,14 +223,16 @@ protected function read(Socket $client) /** * Disconnect the supplied Client Socket + * * @param Socket $client * @param string $message Disconnection Message. Could be used to trigger a disconnect with a status code + * * @return bool Whether or not to continue running the server (true: continue, false: shutdown) */ public function disconnect(Socket $client, $message = '') { $clientIndex = array_search($client, $this->clients); - $return = $this->triggerHooks( + $return = $this->triggerHooks( self::HOOK_DISCONNECT, $this->clients[$clientIndex], $message @@ -229,9 +253,11 @@ public function disconnect(Socket $client, $message = '') /** * Triggers the hooks for the supplied command - * @param string $command Hook to listen for (e.g. HOOK_CONNECT, HOOK_INPUT, HOOK_DISCONNECT) + * + * @param string $command Hook to listen for (e.g. HOOK_CONNECT, HOOK_INPUT, HOOK_DISCONNECT, HOOK_TIMEOUT) * @param Socket $client * @param string $input Message Sent along with the Trigger + * * @return bool Whether or not to continue running the server (true: continue, false: shutdown) */ protected function triggerHooks($command, Socket $client, $input = null) @@ -249,14 +275,17 @@ protected function triggerHooks($command, Socket $client, $input = null) unset($continue); } } + return true; } /** * Attach a Listener to a Hook + * * @param string $command Hook to listen for * @param callable $callable A callable with the signature (Server, Socket, string). * Callable should return false if it wishes to stop the server, and true if it wishes to continue. + * * @return void */ public function addHook($command, $callable) @@ -276,8 +305,10 @@ public function addHook($command, $callable) /** * Remove the provided Callable from the provided Hook + * * @param string $command Hook to remove callable from * @param callable $callable The callable to be removed + * * @return void */ public function removeHook($command, $callable) diff --git a/src/Navarr/Socket/Socket.php b/src/Socket.php similarity index 91% rename from src/Navarr/Socket/Socket.php rename to src/Socket.php index 8fd13f4..300e9c5 100644 --- a/src/Navarr/Socket/Socket.php +++ b/src/Socket.php @@ -21,12 +21,13 @@ class Socket /** * Sets up the Socket Resource + * * @param resource $resource */ protected function __construct($resource) { - $this->resource = $resource; - self::$map[(string)$resource] = $this; + $this->resource = $resource; + self::$map[(string) $resource] = $this; } /** @@ -45,7 +46,7 @@ public function __destruct() */ public function __toString() { - return (string)$this->resource; + return (string) $this->resource; } /** @@ -68,6 +69,7 @@ public function accept() /** * @param string $address * @param int $port + * * @return bool * @throws Exception\SocketException */ @@ -89,7 +91,7 @@ public function bind($address, $port = 0) */ public function close() { - unset(self::$map[(string)$this->resource]); + unset(self::$map[(string) $this->resource]); @socket_close($this->resource); } @@ -98,6 +100,7 @@ public function close() * * @param $address * @param int $port + * * @return bool * @throws Exception\SocketException */ @@ -108,11 +111,13 @@ public function connect($address, $port = 0) if ($return === false) { throw new SocketException($this->resource); } + return true; } /** * @param array $resources + * * @return Socket[] */ protected static function constructFromResources(array $resources) @@ -132,6 +137,7 @@ protected static function constructFromResources(array $resources) * @param integer $domain * @param integer $type * @param integer $protocol + * * @return Socket * @throws Exception\SocketException */ @@ -143,9 +149,9 @@ public static function create($domain, $type, $protocol) throw new SocketException(); } - $socket = new self($return); - $socket->domain = $domain; - $socket->type = $type; + $socket = new self($return); + $socket->domain = $domain; + $socket->type = $type; $socket->protocol = $protocol; return $socket; @@ -154,6 +160,7 @@ public static function create($domain, $type, $protocol) /** * @param $port * @param int $backlog + * * @return Socket * @throws Exception\SocketException */ @@ -165,7 +172,7 @@ public static function createListen($port, $backlog = 128) throw new SocketException(); } - $socket = new self($return); + $socket = new self($return); $socket->domain = AF_INET; return $socket; @@ -175,12 +182,13 @@ public static function createListen($port, $backlog = 128) * @param $domain * @param $type * @param $protocol + * * @return Socket[] * @throws Exception\SocketException */ public static function createPair($domain, $type, $protocol) { - $array = array(); + $array = array(); $return = @socket_create_pair($domain, $type, $protocol, $array); if ($return === false) { @@ -190,8 +198,8 @@ public static function createPair($domain, $type, $protocol) $sockets = self::constructFromResources($array); foreach ($sockets as $socket) { - $socket->domain = $domain; - $socket->type = $type; + $socket->domain = $domain; + $socket->type = $type; $socket->protocol = $protocol; } @@ -201,6 +209,7 @@ public static function createPair($domain, $type, $protocol) /** * @param $level * @param $optname + * * @return mixed * @throws Exception\SocketException */ @@ -218,6 +227,7 @@ public function getOption($level, $optname) /** * @param $address * @param $port + * * @return bool * @throws Exception\SocketException */ @@ -235,6 +245,7 @@ public function getPeerName(&$address, &$port) /** * @param string $address * @param integer $port + * * @return bool * @throws Exception\SocketException */ @@ -255,6 +266,7 @@ public function getSockName(&$address, &$port) /** * @param $stream + * * @return Socket * @throws Exception\SocketException */ @@ -271,6 +283,7 @@ public static function importStream($stream) /** * @param int $backlog + * * @return bool * @throws Exception\SocketException */ @@ -288,6 +301,7 @@ public function listen($backlog = 0) /** * @param int $length * @param int $type + * * @return string * @throws Exception\SocketException */ @@ -306,6 +320,7 @@ public function read($length, $type = PHP_BINARY_READ) * @param $buffer * @param int $length * @param int $flags + * * @return int * @throws Exception\SocketException */ @@ -329,6 +344,7 @@ public function receive(&$buffer, $length, $flags) * @param integer $timeoutSeconds * @param integer $timeoutMilliseconds * @param Socket[] $read + * * @return integer * @throws SocketException */ @@ -339,8 +355,8 @@ public static function select( $timeoutSeconds, $timeoutMilliseconds = 0 ) { - $readSockets = null; - $writeSockets = null; + $readSockets = null; + $writeSockets = null; $exceptSockets = null; if ($read !== null) { @@ -374,23 +390,23 @@ public static function select( throw new SocketException(); } - $read = array(); - $write = array(); + $read = array(); + $write = array(); $except = array(); if ($readSockets) { foreach ($readSockets as $rawSocket) { - $read[] = self::$map[(string)$rawSocket]; + $read[] = self::$map[(string) $rawSocket]; } } if ($writeSockets) { foreach ($writeSockets as $rawSocket) { - $write[] = self::$map[(string)$rawSocket]; + $write[] = self::$map[(string) $rawSocket]; } } if ($exceptSockets) { foreach ($exceptSockets as $rawSocket) { - $except[] = self::$map[(string)$rawSocket]; + $except[] = self::$map[(string) $rawSocket]; } } @@ -400,6 +416,7 @@ public static function select( /** * @param $buffer * @param int $length + * * @return int * @throws Exception\SocketException */ @@ -431,9 +448,11 @@ public function write($buffer, $length = null) /** * Sends data to a connected socket + * * @param $buffer * @param int $flags * @param int $length + * * @return int * @throws Exception\SocketException */ @@ -467,6 +486,7 @@ public function send($buffer, $flags = 0, $length = null) * Set the socket to blocking / non blocking. * * @param boolean + * * @return void */ public function setBlocking($bool) diff --git a/src/Navarr/Socket/Tests/ServerTest.php b/tests/ServerTest.php similarity index 93% rename from src/Navarr/Socket/Tests/ServerTest.php rename to tests/ServerTest.php index 437c0a9..cc4a308 100644 --- a/src/Navarr/Socket/Tests/ServerTest.php +++ b/tests/ServerTest.php @@ -1,14 +1,13 @@ addHook( Server::HOOK_CONNECT, @@ -17,7 +16,7 @@ function () { } ); - $serverClass = new \ReflectionClass($server); + $serverClass = new \ReflectionClass($server); $hooksProperty = $serverClass->getProperty('hooks'); $hooksProperty->setAccessible(true); @@ -39,7 +38,9 @@ function () { /** * @param Server $server + * * @depends testAddingSingleHookWorksProperly + * @return Server */ public function testAddingMultipleHooksWorksProperly($server) { @@ -50,7 +51,7 @@ function () { } ); - $serverClass = new \ReflectionClass($server); + $serverClass = new \ReflectionClass($server); $hooksProperty = $serverClass->getProperty('hooks'); $hooksProperty->setAccessible(true); @@ -70,6 +71,7 @@ function () { /** * @param Server $server + * * @depends testAddingMultipleHooksWorksProperly */ public function testAddingSameHookMultipleTimesWorksProperly($server) @@ -80,7 +82,7 @@ public function testAddingSameHookMultipleTimesWorksProperly($server) $server->addHook(Server::HOOK_CONNECT, $callable); $server->addHook(Server::HOOK_CONNECT, $callable); - $serverClass = new \ReflectionClass($server); + $serverClass = new \ReflectionClass($server); $hooksProperty = $serverClass->getProperty('hooks'); $hooksProperty->setAccessible(true); diff --git a/src/Navarr/Socket/Tests/SocketTest.php b/tests/SocketTest.php similarity index 96% rename from src/Navarr/Socket/Tests/SocketTest.php rename to tests/SocketTest.php index f0c4f30..42e570a 100644 --- a/src/Navarr/Socket/Tests/SocketTest.php +++ b/tests/SocketTest.php @@ -3,7 +3,6 @@ namespace Navarr\Socket\Test; use Navarr\Socket\Socket; -use Navarr\Socket\Exception\SocketException; class SocketTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..c87e5cf --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,9 @@ +