diff --git a/docs/component/network.md b/docs/component/network.md index 876b5a13..5d4bdd15 100644 --- a/docs/component/network.md +++ b/docs/component/network.md @@ -12,8 +12,10 @@ #### `Interfaces` -- [ServerInterface](./../../src/Psl/Network/ServerInterface.php#L10) +- [ServerInterface](./../../src/Psl/Network/ServerInterface.php#L12) - [SocketInterface](./../../src/Psl/Network/SocketInterface.php#L15) +- [StreamServerInterface](./../../src/Psl/Network/StreamServerInterface.php#L14) +- [StreamSocketInterface](./../../src/Psl/Network/StreamSocketInterface.php#L17) #### `Classes` diff --git a/docs/component/tcp.md b/docs/component/tcp.md index cea677a0..dc91af9c 100644 --- a/docs/component/tcp.md +++ b/docs/component/tcp.md @@ -14,10 +14,6 @@ - [connect](./../../src/Psl/TCP/connect.php#L18) -#### `Interfaces` - -- [SocketInterface](./../../src/Psl/TCP/SocketInterface.php#L9) - #### `Classes` - [ConnectOptions](./../../src/Psl/TCP/ConnectOptions.php#L7) diff --git a/docs/component/unix.md b/docs/component/unix.md index 36a68d69..89734989 100644 --- a/docs/component/unix.md +++ b/docs/component/unix.md @@ -14,10 +14,6 @@ - [connect](./../../src/Psl/Unix/connect.php#L17) -#### `Interfaces` - -- [SocketInterface](./../../src/Psl/Unix/SocketInterface.php#L9) - #### `Classes` - [Server](./../../src/Psl/Unix/Server.php#L16) diff --git a/examples/tcp/basic-http-server.php b/examples/tcp/basic-http-server.php index e0c7ec30..2d7f2a98 100644 --- a/examples/tcp/basic-http-server.php +++ b/examples/tcp/basic-http-server.php @@ -17,7 +17,7 @@ IO\write_error_line('Server is listening on http://localhost:3030'); - $watcher = Async\Scheduler::onSignal(SIGINT, $server->stopListening(...)); + $watcher = Async\Scheduler::onSignal(SIGINT, $server->close(...)); Async\Scheduler::unreference($watcher); try { diff --git a/examples/tcp/concurrent.php b/examples/tcp/concurrent.php index 24137536..f91c7e96 100644 --- a/examples/tcp/concurrent.php +++ b/examples/tcp/concurrent.php @@ -33,7 +33,7 @@ IO\write_error_line('< connection closed.'); - $server->stopListening(); + $server->close(); IO\write_error_line('< server stopped.'); }, diff --git a/examples/unix/concurrent.php b/examples/unix/concurrent.php index 04b5a6e5..0f654d86 100644 --- a/examples/unix/concurrent.php +++ b/examples/unix/concurrent.php @@ -43,7 +43,7 @@ IO\write_error_line('< connection closed.'); - $server->stopListening(); + $server->close(); IO\write_error_line("< server stopped\n"); }, diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index cfca7311..ae8d9c64 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -537,9 +537,9 @@ final class Loader 'Psl\File\ReadWriteHandleInterface', 'Psl\Network\Exception\ExceptionInterface', 'Psl\Network\SocketInterface', + 'Psl\Network\StreamSocketInterface', 'Psl\Network\ServerInterface', - 'Psl\TCP\SocketInterface', - 'Psl\Unix\SocketInterface', + 'Psl\Network\StreamServerInterface', 'Psl\Channel\SenderInterface', 'Psl\Channel\ReceiverInterface', 'Psl\Channel\Exception\ExceptionInterface', @@ -669,12 +669,11 @@ final class Loader 'Psl\Network\Exception\InvalidArgumentException', 'Psl\Network\Address', 'Psl\Network\SocketOptions', + 'Psl\Network\Internal\Socket', 'Psl\TCP\ConnectOptions', 'Psl\TCP\ServerOptions', 'Psl\TCP\Server', - 'Psl\TCP\Internal\Socket', 'Psl\Unix\Server', - 'Psl\Unix\Internal\Socket', 'Psl\Channel\Internal\ChannelState', 'Psl\Channel\Internal\Sender', 'Psl\Channel\Internal\Receiver', diff --git a/src/Psl/TCP/Internal/Socket.php b/src/Psl/Network/Internal/Socket.php similarity index 94% rename from src/Psl/TCP/Internal/Socket.php rename to src/Psl/Network/Internal/Socket.php index b26447c2..be2345b7 100644 --- a/src/Psl/TCP/Internal/Socket.php +++ b/src/Psl/Network/Internal/Socket.php @@ -2,14 +2,13 @@ declare(strict_types=1); -namespace Psl\TCP\Internal; +namespace Psl\Network\Internal; use Psl\IO; use Psl\IO\Exception; use Psl\IO\Internal; use Psl\Network; use Psl\Network\Address; -use Psl\TCP; use function is_resource; @@ -18,7 +17,7 @@ * * @codeCoverageIgnore */ -final class Socket implements IO\Stream\CloseReadWriteHandleInterface, TCP\SocketInterface +final class Socket implements Network\StreamSocketInterface { use IO\WriteHandleConvenienceMethodsTrait; use IO\ReadHandleConvenienceMethodsTrait; diff --git a/src/Psl/Network/ServerInterface.php b/src/Psl/Network/ServerInterface.php index 575453d4..37a0e8ca 100644 --- a/src/Psl/Network/ServerInterface.php +++ b/src/Psl/Network/ServerInterface.php @@ -4,10 +4,12 @@ namespace Psl\Network; +use Psl\IO; + /** * Generic interface for a class able to accept socket connections. */ -interface ServerInterface +interface ServerInterface extends IO\CloseHandleInterface { /** * Retrieve the next pending connection. @@ -30,5 +32,5 @@ public function getLocalAddress(): Address; /** * Stop listening; open connection are not closed. */ - public function stopListening(): void; + public function close(): void; } diff --git a/src/Psl/Network/StreamServerInterface.php b/src/Psl/Network/StreamServerInterface.php new file mode 100644 index 00000000..592bd761 --- /dev/null +++ b/src/Psl/Network/StreamServerInterface.php @@ -0,0 +1,20 @@ +deferred?->getAwaitable()->then(static fn() => null, static fn() => null)->await(); @@ -113,7 +113,7 @@ public function nextConnection(): SocketInterface try { /** @psalm-suppress PossiblyNullReference */ - return new Internal\Socket($this->deferred->getAwaitable()->await()); + return new Network\Internal\Socket($this->deferred->getAwaitable()->await()); } finally { Async\Scheduler::disable($this->watcher); $this->deferred = null; @@ -134,13 +134,14 @@ public function getLocalAddress(): Network\Address public function __destruct() { - $this->stopListening(); + /** @psalm-suppress MissingThrowsDocblock */ + $this->close(); } /** * {@inheritDoc} */ - public function stopListening(): void + public function close(): void { Async\Scheduler::cancel($this->watcher); @@ -156,4 +157,12 @@ public function stopListening(): void $this->deferred = null; $deferred?->error(new Network\Exception\AlreadyStoppedException('Server socket has already been stopped.')); } + + /** + * {@inheritDoc} + */ + public function getStream(): mixed + { + return $this->impl; + } } diff --git a/src/Psl/TCP/SocketInterface.php b/src/Psl/TCP/SocketInterface.php deleted file mode 100644 index 729ed5fe..00000000 --- a/src/Psl/TCP/SocketInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -handle = new Internal\ResourceHandle($stream, read: true, write: true, seek: false, close: true); - } - - /** - * {@inheritDoc} - */ - public function readImmediately(?int $max_bytes = null): string - { - return $this->handle->readImmediately($max_bytes); - } - - /** - * {@inheritDoc} - */ - public function read(?int $max_bytes = null, ?float $timeout = null): string - { - return $this->handle->read($max_bytes, $timeout); - } - - /** - * {@inheritDoc} - */ - public function writeImmediately(string $bytes): int - { - return $this->handle->writeImmediately($bytes); - } - - /** - * {@inheritDoc} - */ - public function write(string $bytes, ?float $timeout = null): int - { - return $this->handle->write($bytes, $timeout); - } - - /** - * {@inheritDoc} - */ - public function getStream(): mixed - { - return $this->handle->getStream(); - } - - /** - * {@inheritDoc} - */ - public function getLocalAddress(): Address - { - $stream = $this->handle->getStream(); - if (!is_resource($stream)) { - throw new Exception\AlreadyClosedException('Socket handle has already been closed.'); - } - - return Network\Internal\get_sock_name($stream); - } - - /** - * {@inheritDoc} - */ - public function getPeerAddress(): Address - { - $stream = $this->handle->getStream(); - if (!is_resource($stream)) { - throw new Exception\AlreadyClosedException('Socket handle has already been closed.'); - } - - return Network\Internal\get_peer_name($stream); - } - - /** - * {@inheritDoc} - */ - public function close(): void - { - $this->handle->close(); - } -} diff --git a/src/Psl/Unix/Server.php b/src/Psl/Unix/Server.php index 3e4a10ed..de47d0e2 100644 --- a/src/Psl/Unix/Server.php +++ b/src/Psl/Unix/Server.php @@ -13,7 +13,7 @@ use const PHP_OS_FAMILY; -final class Server implements Network\ServerInterface +final class Server implements Network\StreamServerInterface { /** * @var resource|null $impl @@ -88,7 +88,7 @@ public static function create(string $file): self /** * {@inheritDoc} */ - public function nextConnection(): SocketInterface + public function nextConnection(): Network\StreamSocketInterface { $this->deferred?->getAwaitable()->then(static fn() => null, static fn() => null)->await(); @@ -103,7 +103,7 @@ public function nextConnection(): SocketInterface try { /** @psalm-suppress PossiblyNullReference */ - return new Internal\Socket($this->deferred->getAwaitable()->await()); + return new Network\Internal\Socket($this->deferred->getAwaitable()->await()); } finally { Async\Scheduler::disable($this->watcher); $this->deferred = null; @@ -122,10 +122,16 @@ public function getLocalAddress(): Network\Address return Network\Internal\get_sock_name($this->impl); } + public function __destruct() + { + /** @psalm-suppress MissingThrowsDocblock */ + $this->close(); + } + /** * {@inheritDoc} */ - public function stopListening(): void + public function close(): void { if (null === $this->impl) { return; @@ -145,8 +151,11 @@ public function stopListening(): void $deferred?->error(new Network\Exception\AlreadyStoppedException('Server socket has already been stopped.')); } - public function __destruct() + /** + * {@inheritDoc} + */ + public function getStream(): mixed { - $this->stopListening(); + return $this->impl; } } diff --git a/src/Psl/Unix/SocketInterface.php b/src/Psl/Unix/SocketInterface.php deleted file mode 100644 index 04e98195..00000000 --- a/src/Psl/Unix/SocketInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -writeAll(Str\reverse($request)); $connection->close(); - $server->stopListening(); + $server->close(); }, 'client' => static function (): void { $client = TCP\connect( diff --git a/tests/unit/TCP/ServerTest.php b/tests/unit/TCP/ServerTest.php index 7bb16a20..b4a8df22 100644 --- a/tests/unit/TCP/ServerTest.php +++ b/tests/unit/TCP/ServerTest.php @@ -27,7 +27,7 @@ public function testNextConnectionOnStoppedServer(): void ) ); - $server->stopListening(); + $server->close(); $this->expectException(AlreadyStoppedException::class); $this->expectExceptionMessage('Server socket has already been stopped.'); @@ -38,7 +38,7 @@ public function testNextConnectionOnStoppedServer(): void public function testGetLocalAddressOnStoppedServer(): void { $server = TCP\Server::create('127.0.0.1'); - $server->stopListening(); + $server->close(); $this->expectException(AlreadyStoppedException::class); $this->expectExceptionMessage('Server socket has already been stopped.'); @@ -71,6 +71,26 @@ public function testWaitsForPendingOperation(): void $first_connection->close(); $second_connection->close(); - $server->stopListening(); + $server->close(); + } + + public function testAccessUnderlyingStream(): void + { + $server = TCP\Server::create('127.0.0.1'); + $stream = $server->getStream(); + $deferred = new Async\Deferred(); + $watcher = Async\Scheduler::onReadable($stream, static fn() => $deferred->complete(true)); + $client = TCP\connect('127.0.0.1', $server->getLocalAddress()->port); + + static::assertTrue($deferred->isComplete()); + + Async\Scheduler::cancel($watcher); + $connection = $server->nextConnection(); + $client->write('hello'); + + static::assertSame('hello', $connection->read(5)); + + $client->close(); + $server->close(); } } diff --git a/tests/unit/Unix/ConnectTest.php b/tests/unit/Unix/ConnectTest.php index 82e3c759..6966eb6a 100644 --- a/tests/unit/Unix/ConnectTest.php +++ b/tests/unit/Unix/ConnectTest.php @@ -31,7 +31,7 @@ public function testConnect(): void self::assertSame('Hello, World!', $request); $connection->writeAll(Str\reverse($request)); $connection->close(); - $server->stopListening(); + $server->close(); }, 'client' => static function () use ($sock): void { $client = Unix\connect($sock); diff --git a/tests/unit/Unix/ServerTest.php b/tests/unit/Unix/ServerTest.php index aada8149..5e9ba818 100644 --- a/tests/unit/Unix/ServerTest.php +++ b/tests/unit/Unix/ServerTest.php @@ -22,7 +22,7 @@ public function testNextConnectionOnStoppedServer(): void $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; $server = Unix\Server::create($sock); - $server->stopListening(); + $server->close(); $this->expectException(Exception\AlreadyStoppedException::class); $this->expectExceptionMessage('Server socket has already been stopped.'); @@ -38,7 +38,7 @@ public function testGetLocalAddressOnStoppedServer(): void $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; $server = Unix\Server::create($sock); - $server->stopListening(); + $server->close(); $this->expectException(Exception\AlreadyStoppedException::class); $this->expectExceptionMessage('Server socket has already been stopped.'); @@ -76,6 +76,31 @@ public function testWaitsForPendingOperation(): void $first_connection->close(); $second_connection->close(); - $server->stopListening(); + $server->close(); + } + + public function testAccessUnderlyingStream(): void + { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Unix Server is not supported on Windows platform.'); + } + + $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; + $server = Unix\Server::create($sock); + $stream = $server->getStream(); + $deferred = new Async\Deferred(); + $watcher = Async\Scheduler::onReadable($stream, static fn() => $deferred->complete(true)); + $client = Unix\connect($sock); + + static::assertTrue($deferred->isComplete()); + + Async\Scheduler::cancel($watcher); + $connection = $server->nextConnection(); + $client->write('hello'); + + static::assertSame('hello', $connection->read(5)); + + $client->close(); + $server->close(); } }