diff --git a/.travis.yml b/.travis.yml index 3a474d9..37a4fce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,14 @@ language: php -dist: trusty php: - 7.4 - 7.3 - 7.2 - 7.1 - - 7.0 - - 5.6 - - 5.5 - - 5.4 before_script: - make install build script: - - make coverage cs-check + - make coverage + - make cs-check diff --git a/Makefile b/Makefile index ef50184..cf69f90 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ cs-check: vendor/bin/phpunit coverage: vendor/bin/phpunit build ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml - ./vendor/bin/coveralls -v + ./vendor/bin/php-coveralls -v composer.phar: curl -s http://getcomposer.org/installer | php diff --git a/README.md b/README.md index 84973e2..476736d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ Preferred way to install is with [Composer](https://getcomposer.org/). composer require textalk/websocket ``` -Currently support PHP versions `^5.4` and `^7.0`. +Current version support PHP versions `^7.1`. +For PHP `^5.4` and `^7.0` support use version `1.3`. ## Client @@ -40,6 +41,7 @@ WebSocket\Client { public setTimeout(int $seconds) : void public setFragmentSize(int $fragment_size) : self public getFragmentSize() : int + public setLogger(Psr\Log\LoggerInterface $logger = null) : void } ``` @@ -82,6 +84,7 @@ The `$options` parameter in constructor accepts an associative array of options. * `fragment_size` - Maximum payload size. Default 4096 chars. * `context` - A stream context created using [stream_context_create](https://www.php.net/manual/en/function.stream-context-create). * `headers` - Additional headers as associative array name => content. +* `logger` - A [PSR-3](https://www.php-fig.org/psr/psr-3/) compatible logger. * `persistent` - Connection is re-used between requests until time out is reached. Default false. ```php @@ -131,6 +134,7 @@ WebSocket\Server { public setTimeout(int $seconds) : void public setFragmentSize(int $fragment_size) : self public getFragmentSize() : int + public setLogger(Psr\Log\LoggerInterface $logger = null) : void } ``` @@ -173,6 +177,7 @@ The `$options` parameter in constructor accepts an associative array of options. * `timeout` - Time out in seconds. Default 5 seconds. * `port` - The server port to listen to. Default 8000. * `fragment_size` - Maximum payload size. Default 4096 chars. +* `logger` - A [PSR-3](https://www.php-fig.org/psr/psr-3/) compatible logger. ```php $server = new WebSocket\Server([ @@ -191,6 +196,11 @@ $server = new WebSocket\Server([ ## Development and contribution +Requirements on pull requests; +* All tests **MUST** pass. +* Code coverage **MUST** remain on 100%. +* Code **MUST** adhere to PSR-1 and PSR-12 code standards. + Install or update dependencies using [Composer](https://getcomposer.org/). ``` # Install dependencies @@ -233,11 +243,19 @@ See [Copying](COPYING). Fredrik Liljegren, Armen Baghumian Sankbarani, Ruslan Bekenev, Joshua Thijssen, Simon Lipp, Quentin Bellus, Patrick McCarren, swmcdonnell, -Ignas Bernotas, Mark Herhold, Andreas Palm, Sören Jensen, pmaasz, Alexey Stavrov. +Ignas Bernotas, Mark Herhold, Andreas Palm, Sören Jensen, pmaasz, Alexey Stavrov, +Michael Slezak. ## Changelog +1.4.0 + + * Dropped support of old PHP versions (@sirn-se) + * Added PSR-3 Logging support (@sirn-se) + * Persistent connection option (@slezakattack) + * TimeoutException on connection time out (@slezakattack) + 1.3.1 * Allow control messages without payload (@Logioniz) diff --git a/codestandard.xml b/codestandard.xml index a354b81..bb1cd26 100644 --- a/codestandard.xml +++ b/codestandard.xml @@ -6,7 +6,5 @@ - - - + \ No newline at end of file diff --git a/composer.json b/composer.json index 582fba5..0373d7b 100644 --- a/composer.json +++ b/composer.json @@ -5,8 +5,7 @@ "type": "library", "authors": [ { - "name": "Fredrik Liljegren", - "email": "fredrik.liljegren@textalk.se" + "name": "Fredrik Liljegren" }, { "name": "Sören Jensen", @@ -24,11 +23,12 @@ } }, "require": { - "php": "^5.4|^7.0" + "php": "^7.1", + "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.1", - "php-coveralls/php-coveralls": "^0.7", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "php-coveralls/php-coveralls": "^2.0", "squizlabs/php_codesniffer": "^3.5" } } diff --git a/lib/Base.php b/lib/Base.php index 97a1674..4665622 100644 --- a/lib/Base.php +++ b/lib/Base.php @@ -9,13 +9,18 @@ namespace WebSocket; -class Base +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + +class Base implements LoggerAwareInterface { protected $socket; protected $options = []; protected $is_closing = false; protected $last_opcode = null; protected $close_status = null; + protected $logger; protected static $opcodes = array( 'continuation' => 0, @@ -61,6 +66,11 @@ public function getFragmentSize() return $this->options['fragment_size']; } + public function setLogger(LoggerInterface $logger = null) + { + $this->logger = $logger ?: new NullLogger(); + } + public function send($payload, $opcode = 'text', $masked = true) { if (!$this->isConnected()) { @@ -68,20 +78,25 @@ public function send($payload, $opcode = 'text', $masked = true) } if (!in_array($opcode, array_keys(self::$opcodes))) { - throw new BadOpcodeException("Bad opcode '$opcode'. Try 'text' or 'binary'."); + $warning = "Bad opcode '{$opcode}'. Try 'text' or 'binary'."; + $this->logger->warning($warning); + throw new BadOpcodeException($warning); } $payload_chunks = str_split($payload, $this->options['fragment_size']); + $frame_opcode = $opcode; for ($index = 0; $index < count($payload_chunks); ++$index) { $chunk = $payload_chunks[$index]; $final = $index == count($payload_chunks) - 1; - $this->sendFragment($final, $chunk, $opcode, $masked); + $this->sendFragment($final, $chunk, $frame_opcode, $masked); // all fragments after the first will be marked a continuation - $opcode = 'continuation'; + $frame_opcode = 'continuation'; } + + $this->logger->info("Sent '{$opcode}' message"); } protected function sendFragment($final, $payload, $opcode, $masked) @@ -150,6 +165,7 @@ public function receive() $payload .= $response[0]; } while (!$response[1]); + $this->logger->info("Received '{$this->last_opcode}' message"); return $payload; } @@ -170,10 +186,9 @@ protected function receiveFragment() $opcode_int = ord($data[0]) & 31; // Bits 4-7 $opcode_ints = array_flip(self::$opcodes); if (!array_key_exists($opcode_int, $opcode_ints)) { - throw new ConnectionException( - "Bad opcode in websocket frame: $opcode_int", - ConnectionException::BAD_OPCODE - ); + $warning = "Bad opcode in websocket frame: {$opcode_int}"; + $this->logger->warning($warning); + throw new ConnectionException($warning, ConnectionException::BAD_OPCODE); } $opcode = $opcode_ints[$opcode_int]; @@ -219,6 +234,7 @@ protected function receiveFragment() // if we received a ping, send a pong if ($opcode === 'ping') { + $this->logger->debug("Received 'ping', sending 'pong'."); $this->send($payload, 'pong', true); } @@ -234,6 +250,8 @@ protected function receiveFragment() $payload = substr($payload, 2); } + $this->logger->debug("Received 'close', status: {$this->close_status}."); + if ($this->is_closing) { $this->is_closing = false; // A close response, all done. } else { @@ -267,6 +285,7 @@ public function close($status = 1000, $message = 'ttfn') $status_str .= chr(bindec($binstr)); } $this->send($status_str . $message, 'close', true); + $this->logger->debug("Closing with status: {$status_str}."); $this->is_closing = true; $this->receive(); // Receiving a close frame will close the socket now. @@ -274,18 +293,18 @@ public function close($status = 1000, $message = 'ttfn') protected function write($data) { + $length = strlen($data); $written = fwrite($this->socket, $data); if ($written === false) { - $length = strlen($data); fclose($this->socket); - $this->throwException("Failed to write $length bytes."); + $this->throwException("Failed to write {$length} bytes."); } if ($written < strlen($data)) { - $length = strlen($data); fclose($this->socket); - $this->throwException("Could only write $written out of $length bytes."); + $this->throwException("Could only write {$written} out of {$length} bytes."); } + $this->logger->debug("Wrote {$written} of {$length} bytes."); } protected function read($length) @@ -295,7 +314,7 @@ protected function read($length) $buffer = fread($this->socket, $length - strlen($data)); if ($buffer === false) { $read = strlen($data); - $this->throwException("Broken frame, read $read of stated $length bytes."); + $this->throwException("Broken frame, read {$read} of stated {$length} bytes."); } if ($buffer === '') { $this->throwException("Empty read; connection dead?"); @@ -311,12 +330,14 @@ protected function throwException($message, $code = 0) $json_meta = json_encode($meta); if (!empty($meta['timed_out'])) { $code = ConnectionException::TIMED_OUT; - throw new TimeoutException("$message Stream state: $json_meta", $code); + $this->logger->warning("{$message}", (array)$meta); + throw new TimeoutException("{$message} Stream state: {$json_meta}", $code); } if (!empty($meta['eof'])) { $code = ConnectionException::EOF; } - throw new ConnectionException("$message Stream state: $json_meta", $code); + $this->logger->error("{$message}", (array)$meta); + throw new ConnectionException("{$message} Stream state: {$json_meta}", $code); } /** diff --git a/lib/Client.php b/lib/Client.php index 1375af7..5dec6bb 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -18,6 +18,7 @@ class Client extends Base 'fragment_size' => 4096, 'context' => null, 'headers' => null, + 'logger' => null, 'origin' => null, // @deprecated ]; @@ -36,6 +37,7 @@ public function __construct($uri, $options = array()) { $this->options = array_merge(self::$default_options, $options); $this->socket_uri = $uri; + $this->setLogger($this->options['logger']); } public function __destruct() @@ -53,9 +55,9 @@ protected function connect() { $url_parts = parse_url($this->socket_uri); if (empty($url_parts) || empty($url_parts['scheme']) || empty($url_parts['host'])) { - throw new BadUriException( - "Invalid url '$this->socket_uri' provided." - ); + $error = "Invalid url '{$this->socket_uri}' provided."; + $this->logger->error($error); + throw new BadUriException($error); } $scheme = $url_parts['scheme']; $host = $url_parts['host']; @@ -75,9 +77,9 @@ protected function connect() } if (!in_array($scheme, array('ws', 'wss'))) { - throw new BadUriException( - "Url should have scheme ws or wss, not '$scheme' from URI '$this->socket_uri' ." - ); + $error = "Url should have scheme ws or wss, not '{$scheme}' from URI '{$this->socket_uri}'."; + $this->logger->error($error); + throw new BadUriException($error); } $host_uri = ($scheme === 'wss' ? 'ssl' : 'tcp') . '://' . $host; @@ -88,9 +90,9 @@ protected function connect() if (@get_resource_type($this->options['context']) === 'stream-context') { $context = $this->options['context']; } else { - throw new \InvalidArgumentException( - "Stream context in \$options['context'] isn't a valid context" - ); + $error = "Stream context in \$options['context'] isn't a valid context."; + $this->logger->error($error); + throw new \InvalidArgumentException($error); } } else { $context = stream_context_create(); @@ -110,9 +112,9 @@ protected function connect() ); if (!$this->isConnected()) { - throw new ConnectionException( - "Could not open socket to \"$host:$port\": $errstr ($errno)." - ); + $error = "Could not open socket to \"{$host}:{$port}\": {$errstr} ({$errno})."; + $this->logger->error($error); + throw new ConnectionException($error); } // Set timeout on the stream as well. @@ -165,13 +167,13 @@ function ($key, $value) { /// @todo Handle version switching + $address = "{$scheme}://{$host}{$path_with_query}"; + // Validate response. if (!preg_match('#Sec-WebSocket-Accept:\s(.*)$#mUi', $response, $matches)) { - $address = $scheme . '://' . $host . $path_with_query; - throw new ConnectionException( - "Connection to '{$address}' failed: Server sent invalid upgrade response:\n" - . $response - ); + $error = "Connection to '{$address}' failed: Server sent invalid upgrade response: {$response}"; + $this->logger->error($error); + throw new ConnectionException($error); } $keyAccept = trim($matches[1]); @@ -179,8 +181,12 @@ function ($key, $value) { = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); if ($keyAccept !== $expectedResonse) { - throw new ConnectionException('Server sent bad upgrade response.'); + $error = 'Server sent bad upgrade response.'; + $this->logger->error($error); + throw new ConnectionException($error); } + + $this->logger->info("Client connected to to {$address}"); } /** diff --git a/lib/ConnectionException.php b/lib/ConnectionException.php index e8c2d03..b20932f 100644 --- a/lib/ConnectionException.php +++ b/lib/ConnectionException.php @@ -5,7 +5,7 @@ class ConnectionException extends Exception { // Native codes in interval 0-106 - const TIMED_OUT = 1024; - const EOF = 1025; - const BAD_OPCODE = 1026; + public const TIMED_OUT = 1024; + public const EOF = 1025; + public const BAD_OPCODE = 1026; } diff --git a/lib/Server.php b/lib/Server.php index 8e0de40..6b23d6d 100644 --- a/lib/Server.php +++ b/lib/Server.php @@ -16,6 +16,7 @@ class Server extends Base 'timeout' => null, 'fragment_size' => 4096, 'port' => 8000, + 'logger' => null, ]; protected $addr; @@ -35,14 +36,19 @@ public function __construct(array $options = array()) { $this->options = array_merge(self::$default_options, $options); $this->port = $this->options['port']; + $this->setLogger($this->options['logger']); do { $this->listening = @stream_socket_server("tcp://0.0.0.0:$this->port", $errno, $errstr); } while ($this->listening === false && $this->port++ < 10000); if (!$this->listening) { - throw new ConnectionException("Could not open listening socket: $errstr", $errno); + $error = "Could not open listening socket: {$errstr} ({$errno})"; + $this->logger->error($error); + throw new ConnectionException($error, $errno); } + + $this->logger->info("Server listening to port {$this->port}"); } public function __destruct() @@ -90,17 +96,22 @@ protected function connect() if (empty($this->options['timeout'])) { $this->socket = @stream_socket_accept($this->listening); if (!$this->socket) { - throw new ConnectionException('Server failed to connect.'); + $error = 'Server failed to connect.'; + $this->logger->error($error); + throw new ConnectionException($error); } } else { $this->socket = @stream_socket_accept($this->listening, $this->options['timeout']); if (!$this->socket) { - throw new ConnectionException('Server failed to connect.'); + $error = 'Server failed to connect.'; + $this->logger->error($error); + throw new ConnectionException($error); } stream_set_timeout($this->socket, $this->options['timeout']); } $this->performHandshake(); + $this->logger->info("Server connected to port {$this->port}"); } protected function performHandshake() @@ -113,7 +124,9 @@ protected function performHandshake() } while (!feof($this->socket) && $metadata['unread_bytes'] > 0); if (!preg_match('/GET (.*) HTTP\//mUi', $request, $matches)) { - throw new ConnectionException("No GET in request:\n" . $request); + $error = "No GET in request: {$request}"; + $this->logger->error($error); + throw new ConnectionException($error); } $get_uri = trim($matches[1]); $uri_parts = parse_url($get_uri); @@ -123,7 +136,9 @@ protected function performHandshake() /// @todo Get query and fragment as well. if (!preg_match('#Sec-WebSocket-Key:\s(.*)$#mUi', $request, $matches)) { - throw new ConnectionException("Client had no Key in upgrade request:\n" . $request); + $error = "Client had no Key in upgrade request: {$request}"; + $this->logger->error($error); + throw new ConnectionException($error); } $key = trim($matches[1]); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 076fb33..16dee9c 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -5,17 +5,21 @@ * Note that this test is performed by mocking socket/stream calls. */ +declare(strict_types=1); + namespace WebSocket; -class ClientTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class ClientTest extends TestCase { - public function setUp() + public function setUp(): void { error_reporting(-1); } - public function testClientMasked() + public function testClientMasked(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -46,7 +50,7 @@ public function testClientMasked() $this->assertTrue(MockSocket::isEmpty()); } - public function testDestruct() + public function testDestruct(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -56,7 +60,7 @@ public function testDestruct() MockSocket::initialize('client.destruct', $this); } - public function testClienExtendedUrl() + public function testClienExtendedUrl(): void { MockSocket::initialize('client.connect-extended', $this); $client = new Client('ws://localhost:8000/my/mock/path?my_query=yes#my_fragment'); @@ -64,7 +68,7 @@ public function testClienExtendedUrl() $this->assertTrue(MockSocket::isEmpty()); } - public function testClientWithTimeout() + public function testClientWithTimeout(): void { MockSocket::initialize('client.connect-timeout', $this); $client = new Client('ws://localhost:8000/my/mock/path', ['timeout' => 300]); @@ -72,7 +76,7 @@ public function testClientWithTimeout() $this->assertTrue(MockSocket::isEmpty()); } - public function testClientWithContext() + public function testClientWithContext(): void { MockSocket::initialize('client.connect-context', $this); $client = new Client('ws://localhost:8000/my/mock/path', ['context' => '@mock-stream-context']); @@ -80,7 +84,7 @@ public function testClientWithContext() $this->assertTrue(MockSocket::isEmpty()); } - public function testClientAuthed() + public function testClientAuthed(): void { MockSocket::initialize('client.connect-authed', $this); $client = new Client('wss://usename:password@localhost:8000/my/mock/path'); @@ -88,7 +92,7 @@ public function testClientAuthed() $this->assertTrue(MockSocket::isEmpty()); } - public function testWithHeaders() + public function testWithHeaders(): void { MockSocket::initialize('client.connect-headers', $this); $client = new Client('ws://localhost:8000/my/mock/path', [ @@ -99,7 +103,7 @@ public function testWithHeaders() $this->assertTrue(MockSocket::isEmpty()); } - public function testPayload128() + public function testPayload128(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -115,7 +119,7 @@ public function testPayload128() $this->assertTrue(MockSocket::isEmpty()); } - public function testPayload65536() + public function testPayload65536(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -133,7 +137,7 @@ public function testPayload65536() $this->assertEquals(65540, $client->getFragmentSize()); } - public function testMultiFragment() + public function testMultiFragment(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -149,7 +153,7 @@ public function testMultiFragment() $this->assertEquals(8, $client->getFragmentSize()); } - public function testPingPong() + public function testPingPong(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -173,7 +177,7 @@ public function testPingPong() $this->assertEquals('ping', $client->getLastOpcode()); } - public function testRemoteClose() + public function testRemoteClose(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -191,7 +195,7 @@ public function testRemoteClose() $this->assertTrue(MockSocket::isEmpty()); } - public function testSetTimeout() + public function testSetTimeout(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -204,7 +208,7 @@ public function testSetTimeout() $this->assertTrue(MockSocket::isEmpty()); } - public function testReconnect() + public function testReconnect(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); @@ -226,7 +230,7 @@ public function testReconnect() $this->assertTrue(MockSocket::isEmpty()); } - public function testPersistentConnection() + public function testPersistentConnection(): void { MockSocket::initialize('client.connect-persistent', $this); $client = new Client('ws://localhost:8000/my/mock/path', ['persistent' => true]); @@ -234,156 +238,132 @@ public function testPersistentConnection() $this->assertTrue(MockSocket::isEmpty()); } - /** - * @expectedException WebSocket\BadUriException - * @expectedExceptionMessage Url should have scheme ws or wss - */ - public function testBadScheme() + public function testBadScheme(): void { MockSocket::initialize('client.connect', $this); $client = new Client('bad://localhost:8000/my/mock/path'); + $this->expectException('WebSocket\BadUriException'); + $this->expectExceptionMessage('Url should have scheme ws or wss'); $client->send('Connect'); } - /** - * @expectedException WebSocket\BadUriException - * @expectedExceptionMessage Invalid url 'this is not an url' provided. - */ - public function testBadUrl() + public function testBadUrl(): void { MockSocket::initialize('client.connect', $this); $client = new Client('this is not an url'); + $this->expectException('WebSocket\BadUriException'); + $this->expectExceptionMessage('Invalid url \'this is not an url\' provided.'); $client->send('Connect'); } - /** - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Stream context in $options['context'] isn't a valid context - */ - public function testBadStreamContext() + public function testBadStreamContext(): void { MockSocket::initialize('client.connect-bad-context', $this); $client = new Client('ws://localhost:8000/my/mock/path', ['context' => 'BAD']); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Stream context in $options[\'context\'] isn\'t a valid context'); $client->send('Connect'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Could not open socket to "localhost:8000" - */ - public function testFailedConnection() + public function testFailedConnection(): void { MockSocket::initialize('client.connect-failed', $this); $client = new Client('ws://localhost:8000/my/mock/path'); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Could not open socket to "localhost:8000"'); $client->send('Connect'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Connection to 'ws://localhost/my/mock/path' failed - */ - public function testInvalidUpgrade() + public function testInvalidUpgrade(): void { MockSocket::initialize('client.connect-invalid-upgrade', $this); $client = new Client('ws://localhost:8000/my/mock/path'); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Connection to \'ws://localhost/my/mock/path\' failed'); $client->send('Connect'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Server sent bad upgrade response - */ - public function testInvalidKey() + public function testInvalidKey(): void { MockSocket::initialize('client.connect-invalid-key', $this); $client = new Client('ws://localhost:8000/my/mock/path'); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Server sent bad upgrade response'); $client->send('Connect'); } - /** - * @expectedException WebSocket\BadOpcodeException - * @expectedExceptionMessage Bad opcode 'bad'. Try 'text' or 'binary'. - */ - public function testSendBadOpcode() + public function testSendBadOpcode(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); $client->send('Connect'); MockSocket::initialize('send-bad-opcode', $this); + $this->expectException('WebSocket\BadOpcodeException'); + $this->expectExceptionMessage('Bad opcode \'bad\'. Try \'text\' or \'binary\'.'); $client->send('Bad Opcode', 'bad'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1026 - * @expectedExceptionMessage Bad opcode in websocket frame: 12 - */ - public function testRecieveBadOpcode() + public function testRecieveBadOpcode(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); $client->send('Connect'); MockSocket::initialize('receive-bad-opcode', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(1026); + $this->expectExceptionMessage('Bad opcode in websocket frame: 12'); $message = $client->receive(); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1025 - * @expectedExceptionMessage Could only write 18 out of 22 bytes. - */ - public function testBrokenWrite() + public function testBrokenWrite(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); $client->send('Connect'); MockSocket::initialize('send-broken-write', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(1025); + $this->expectExceptionMessage('Could only write 18 out of 22 bytes.'); $client->send('Failing to write'); } - /** - * @expectedException WebSocket\TimeoutException - * @expectedExceptionCode 1024 - * @expectedExceptionMessage Failed to write 22 bytes. - */ - public function testFailedWrite() + public function testFailedWrite(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); $client->send('Connect'); MockSocket::initialize('send-failed-write', $this); + $this->expectException('WebSocket\TimeoutException'); + $this->expectExceptionCode(1024); + $this->expectExceptionMessage('Failed to write 22 bytes.'); $client->send('Failing to write'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1025 - * @expectedExceptionMessage Broken frame, read 0 of stated 2 bytes. - */ - public function testBrokenRead() + public function testBrokenRead(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); $client->send('Connect'); MockSocket::initialize('receive-broken-read', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(1025); + $this->expectExceptionMessage('Broken frame, read 0 of stated 2 bytes.'); $client->receive(); } - /** - * @expectedException WebSocket\TimeoutException - * @expectedExceptionCode 1024 - * @expectedExceptionMessage Empty read; connection dead? - */ - public function testEmptyRead() + public function testEmptyRead(): void { MockSocket::initialize('client.connect', $this); $client = new Client('ws://localhost:8000/my/mock/path'); $client->send('Connect'); MockSocket::initialize('receive-empty-read', $this); + $this->expectException('WebSocket\TimeoutException'); + $this->expectExceptionCode(1024); + $this->expectExceptionMessage('Empty read; connection dead?'); $client->receive(); } } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 6a83649..a69b67a 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -5,17 +5,21 @@ * Note that this test is performed by mocking socket/stream calls. */ +declare(strict_types=1); + namespace WebSocket; -class ServerTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class ServerTest extends TestCase { - public function setUp() + public function setUp(): void { error_reporting(-1); } - public function testServerMasked() + public function testServerMasked(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -62,7 +66,7 @@ public function testServerMasked() $server->close(); // Already closed } - public function testDestruct() + public function testDestruct(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -72,7 +76,7 @@ public function testDestruct() $message = $server->receive(); } - public function testServerWithTimeout() + public function testServerWithTimeout(): void { MockSocket::initialize('server.construct', $this); $server = new Server(['timeout' => 300]); @@ -84,7 +88,7 @@ public function testServerWithTimeout() $this->assertTrue(MockSocket::isEmpty()); } - public function testPayload128() + public function testPayload128(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -105,7 +109,7 @@ public function testPayload128() $this->assertTrue(MockSocket::isEmpty()); } - public function testPayload65536() + public function testPayload65536(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -127,7 +131,7 @@ public function testPayload65536() $this->assertTrue(MockSocket::isEmpty()); } - public function testMultiFragment() + public function testMultiFragment(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -147,7 +151,7 @@ public function testMultiFragment() $this->assertTrue(MockSocket::isEmpty()); } - public function testPingPong() + public function testPingPong(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -177,7 +181,7 @@ public function testPingPong() $this->assertEquals('ping', $server->getLastOpcode()); } - public function testRemoteClose() + public function testRemoteClose(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -200,7 +204,7 @@ public function testRemoteClose() $this->assertEquals('close', $server->getLastOpcode()); } - public function testSetTimeout() + public function testSetTimeout(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -218,96 +222,79 @@ public function testSetTimeout() $this->assertTrue(MockSocket::isEmpty()); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Could not open listening socket: - */ - public function testFailedSocketServer() + public function testFailedSocketServer(): void { MockSocket::initialize('server.construct-failed-socket-server', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Could not open listening socket:'); $server = new Server(['port' => 9999]); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Server failed to connect - */ - public function testFailedConnect() + public function testFailedConnect(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); MockSocket::initialize('server.accept-failed-connect', $this); $server->accept(); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Server failed to connect'); $server->send('Connect'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Server failed to connect - */ - public function testFailedConnectTimeout() + public function testFailedConnectTimeout(): void { MockSocket::initialize('server.construct', $this); $server = new Server(['timeout' => 300]); MockSocket::initialize('server.accept-failed-connect', $this); $server->accept(); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Server failed to connect'); $server->send('Connect'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage No GET in request - */ - public function testFailedHttp() + public function testFailedHttp(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); MockSocket::initialize('server.accept-failed-http', $this); $server->accept(); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('No GET in request'); $server->send('Connect'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Client had no Key in upgrade request - */ - public function testFailedWsKey() + public function testFailedWsKey(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); MockSocket::initialize('server.accept-failed-ws-key', $this); $server->accept(); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Client had no Key in upgrade request'); $server->send('Connect'); } - /** - * @expectedException WebSocket\BadOpcodeException - * @expectedExceptionCode 0 - * @expectedExceptionMessage Bad opcode 'bad'. Try 'text' or 'binary'. - */ - public function testSendBadOpcode() + public function testSendBadOpcode(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); MockSocket::initialize('server.accept', $this); $server->accept(); $server->send('Connect'); + $this->expectException('WebSocket\BadOpcodeException'); + $this->expectExceptionCode(0); + $this->expectExceptionMessage('Bad opcode \'bad\'. Try \'text\' or \'binary\'.'); $server->send('Bad Opcode', 'bad'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1026 - * @expectedExceptionMessage Bad opcode in websocket frame: 12 - */ - public function testRecieveBadOpcode() + public function testRecieveBadOpcode(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -315,15 +302,13 @@ public function testRecieveBadOpcode() $server->accept(); $server->send('Connect'); MockSocket::initialize('receive-bad-opcode', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(1026); + $this->expectExceptionMessage('Bad opcode in websocket frame: 12'); $message = $server->receive(); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1025 - * @expectedExceptionMessage Could only write 18 out of 22 bytes. - */ - public function testBrokenWrite() + public function testBrokenWrite(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -331,15 +316,13 @@ public function testBrokenWrite() $server->accept(); $server->send('Connect'); MockSocket::initialize('send-broken-write', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(1025); + $this->expectExceptionMessage('Could only write 18 out of 22 bytes.'); $server->send('Failing to write'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1024 - * @expectedExceptionMessage Failed to write 22 bytes. - */ - public function testFailedWrite() + public function testFailedWrite(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -347,15 +330,13 @@ public function testFailedWrite() $server->accept(); $server->send('Connect'); MockSocket::initialize('send-failed-write', $this); + $this->expectException('WebSocket\TimeoutException'); + $this->expectExceptionCode(1024); + $this->expectExceptionMessage('Failed to write 22 bytes.'); $server->send('Failing to write'); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1025 - * @expectedExceptionMessage Broken frame, read 0 of stated 2 bytes. - */ - public function testBrokenRead() + public function testBrokenRead(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -363,15 +344,13 @@ public function testBrokenRead() $server->accept(); $server->send('Connect'); MockSocket::initialize('receive-broken-read', $this); + $this->expectException('WebSocket\ConnectionException'); + $this->expectExceptionCode(1025); + $this->expectExceptionMessage('Broken frame, read 0 of stated 2 bytes.'); $server->receive(); } - /** - * @expectedException WebSocket\ConnectionException - * @expectedExceptionCode 1024 - * @expectedExceptionMessage Empty read; connection dead? - */ - public function testEmptyRead() + public function testEmptyRead(): void { MockSocket::initialize('server.construct', $this); $server = new Server(); @@ -379,6 +358,9 @@ public function testEmptyRead() $server->accept(); $server->send('Connect'); MockSocket::initialize('receive-empty-read', $this); + $this->expectException('WebSocket\TimeoutException'); + $this->expectExceptionCode(1024); + $this->expectExceptionMessage('Empty read; connection dead?'); $server->receive(); } }