Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions docs/Class_Synopsis.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ abstract class WebSocket\Message\Message imlements Stringable
public method setPayload(string $payload = ""): void;
}

class WebSocket\Client imlements Psr\Log\LoggerAwareInterface, Stringable
class WebSocket\Client imlements WebSocket\Runtime\IdentityInterface, Psr\Log\LoggerAwareInterface, Stringable
{
use WebSocket\Trait\ListenerTrait;
use WebSocket\Trait\LoggerAwareTrait;
Expand All @@ -43,6 +43,7 @@ class WebSocket\Client imlements Psr\Log\LoggerAwareInterface, Stringable
public method getContext(): Phrity\Net\Context;
public method getFrameSize(): int;
public method getHandshakeResponse(): Psr\Http\Message\ResponseInterface|null;
public method getIdentity(): string;
public method getMeta(string $key): mixed;
public method getName(): string|null;
public method getRemoteName(): string|null;
Expand All @@ -64,7 +65,7 @@ class WebSocket\Client imlements Psr\Log\LoggerAwareInterface, Stringable
public method stop(): void;
}

class WebSocket\Connection imlements Psr\Log\LoggerAwareInterface, Stringable
class WebSocket\Connection imlements WebSocket\Runtime\IdentityInterface, Psr\Log\LoggerAwareInterface, Stringable
{
use WebSocket\Trait\LoggerAwareTrait;
use WebSocket\Trait\SendMethodsTrait;
Expand All @@ -81,6 +82,7 @@ class WebSocket\Connection imlements Psr\Log\LoggerAwareInterface, Stringable
public method getFrameSize(): int;
public method getHandshakeRequest(): Psr\Http\Message\RequestInterface|null;
public method getHandshakeResponse(): Psr\Http\Message\ResponseInterface|null;
public method getIdentity(): string;
public method getMeta(string $key): mixed;
public method getName(): string|null;
public method getRemoteName(): string|null;
Expand Down Expand Up @@ -382,7 +384,7 @@ class WebSocket\Middleware\SubprotocolNegotiation imlements Psr\Log\LoggerAwareI
public method processHttpOutgoing(WebSocket\Middleware\ProcessHttpStack $stack, WebSocket\Connection $connection, Psr\Http\Message\MessageInterface $message): Psr\Http\Message\MessageInterface;
}

class WebSocket\Server imlements Psr\Log\LoggerAwareInterface, Stringable
class WebSocket\Server imlements WebSocket\Runtime\IdentityInterface, Psr\Log\LoggerAwareInterface, Stringable
{
use WebSocket\Trait\ListenerTrait;
use WebSocket\Trait\LoggerAwareTrait;
Expand All @@ -397,6 +399,7 @@ class WebSocket\Server imlements Psr\Log\LoggerAwareInterface, Stringable
public method getConnections(): array;
public method getContext(): Phrity\Net\Context;
public method getFrameSize(): int;
public method getIdentity(): string;
public method getPort(): int;
public method getReadableConnections(): array;
public method getScheme(): string;
Expand Down Expand Up @@ -474,6 +477,11 @@ inteface WebSocket\Middleware\ProcessTickInterface imlements WebSocket\Middlewar
public method processTick(WebSocket\Middleware\ProcessTickStack $stack, WebSocket\Connection $connection): void;
}

inteface WebSocket\Runtime\IdentityInterface
{
public method getIdentity(): string;
}

trait WebSocket\Trait\ListenerTrait
{
public method onBinary(Closure $closure): self;
Expand Down
515 changes: 515 additions & 0 deletions docs/tmp

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use WebSocket\Http\DefaultHttpFactory;
use WebSocket\Message\Message;
use WebSocket\Middleware\MiddlewareInterface;
use WebSocket\Runtime\IdentityInterface;
use WebSocket\Trait\{
ListenerTrait,
LoggerAwareTrait,
Expand All @@ -50,7 +51,7 @@
* WebSocket\Client class.
* Entry class for WebSocket client.
*/
class Client implements LoggerAwareInterface, Stringable
class Client implements IdentityInterface, LoggerAwareInterface, Stringable
{
/** @use ListenerTrait<Client> */
use ListenerTrait;
Expand All @@ -77,6 +78,8 @@ class Client implements LoggerAwareInterface, Stringable
private StreamCollection|null $streams = null;
private bool $running = false;
private HttpFactory $httpFactory;
/** @var non-empty-string $identity */
private string $identity = 'client/<unconnected>';


/* ---------- Magic methods ------------------------------------------------------------------------------------ */
Expand All @@ -91,6 +94,7 @@ public function __construct(UriInterface|string $uri)
$this->context = new Context();
$this->setStreamFactory(new StreamFactory());
$this->httpFactory = new DefaultHttpFactory();
$this->identity = "client/{$this->socketUri->getHost()}";
}

/**
Expand All @@ -105,6 +109,11 @@ public function __toString(): string

/* ---------- Configuration ------------------------------------------------------------------------------------ */

public function getIdentity(): string
{
return $this->identity;
}

/**
* Set stream factory to use.
* @param StreamFactory $streamFactory
Expand Down Expand Up @@ -446,15 +455,15 @@ public function connect(): void
$this->logger->error("[client] {$error}", ['exception' => $e]);
throw new ClientException($error);
}
$name = $stream->getRemoteName();
$this->streams->attach($stream, $name);
$this->connection = new Connection(
$stream,
true,
false,
$hostUri->getScheme() === 'ssl',
$this->httpFactory
);
$this->streams->attach($stream, $this->connection->getIdentity());

$this->connection->setFrameSize($this->frameSize);
$this->connection->setTimeout($this->timeout);
$this->connection->setLogger($this->logger);
Expand Down
21 changes: 20 additions & 1 deletion src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
MiddlewareHandler,
MiddlewareInterface
};
use WebSocket\Runtime\IdentityInterface;
use WebSocket\Trait\{
LoggerAwareTrait,
SendMethodsTrait,
Expand All @@ -54,7 +55,7 @@
* WebSocket\Connection class.
* A client/server connection, wrapping socket stream.
*/
class Connection implements LoggerAwareInterface, Stringable
class Connection implements IdentityInterface, LoggerAwareInterface, Stringable
{
use LoggerAwareTrait;
use SendMethodsTrait;
Expand All @@ -75,6 +76,8 @@ class Connection implements LoggerAwareInterface, Stringable
/** @var array<string, mixed> $meta */
private array $meta = [];
private bool $closed = false;
/** @var non-empty-string $identity */
private string $identity = '*/connection/<unconnected>';


/* ---------- Magic methods ------------------------------------------------------------------------------------ */
Expand All @@ -92,6 +95,11 @@ public function __construct(
$this->middlewareHandler = new MiddlewareHandler($this->messageHandler, $this->httpHandler);
$this->localName = $this->stream->getLocalName() ?? '<unknown>';
$this->remoteName = $this->stream->getRemoteName() ?? '<unknown>';
$this->identity = sprintf(
'*/connection/%s/%s',
$this->getIdentityPart($this->localName),
$this->getIdentityPart($this->remoteName),
);
$this->initLogger();
}

Expand All @@ -110,6 +118,11 @@ public function __toString(): string

/* ---------- Configuration ------------------------------------------------------------------------------------ */

public function getIdentity(): string
{
return $this->identity;
}

/**
* Set logger.
* @param LoggerInterface $logger Logger implementation
Expand Down Expand Up @@ -432,4 +445,10 @@ protected function throwException(Throwable $e): never
$this->logger->error("[connection] {$e->getMessage()}", ['exception' => $e]);
throw new ConnectionFailureException();
}

protected function getIdentityPart(string $source): string
{
preg_match('/([0-9]+)$/', $source, $result);
return empty($result) ? $source : array_shift($result);
}
}
19 changes: 19 additions & 0 deletions src/Runtime/IdentityInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/**
* Copyright (C) 2014-2025 Textalk and contributors.
* This file is part of Websocket PHP and is free software under the ISC License.
*/

namespace WebSocket\Runtime;

/**
* WebSocket\Runtime\IdentityInterface interface.
*/
interface IdentityInterface
{
/**
* @return non-empty-string
*/
public function getIdentity(): string;
}
20 changes: 14 additions & 6 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use WebSocket\Http\DefaultHttpFactory;
use WebSocket\Message\Message;
use WebSocket\Middleware\MiddlewareInterface;
use WebSocket\Runtime\IdentityInterface;
use WebSocket\Trait\{
ListenerTrait,
LoggerAwareTrait,
Expand All @@ -51,7 +52,7 @@
* WebSocket\Server class.
* Entry class for WebSocket server.
*/
class Server implements LoggerAwareInterface, Stringable
class Server implements IdentityInterface, LoggerAwareInterface, Stringable
{
/** @use ListenerTrait<Server> */
use ListenerTrait;
Expand Down Expand Up @@ -79,6 +80,8 @@ class Server implements LoggerAwareInterface, Stringable
private array $middlewares = [];
private int|null $maxConnections = null;
private HttpFactory $httpFactory;
/** @var non-empty-string $identity */
private string $identity;


/* ---------- Magic methods ------------------------------------------------------------------------------------ */
Expand All @@ -99,6 +102,7 @@ public function __construct(int $port = 80, bool $ssl = false)
$this->context = new Context();
$this->httpFactory = new DefaultHttpFactory();
$this->setStreamFactory(new StreamFactory());
$this->identity = "server/{$port}";
}

/**
Expand All @@ -113,6 +117,11 @@ public function __toString(): string

/* ---------- Configuration ------------------------------------------------------------------------------------ */

public function getIdentity(): string
{
return $this->identity;
}

/**
* Set stream factory to use.
* @param StreamFactory $streamFactory
Expand Down Expand Up @@ -511,7 +520,7 @@ protected function createSocketServer(): void
$uri = new Uri("{$this->scheme}://0.0.0.0:{$this->port}");
$this->server = $this->streamFactory->createSocketServer($uri, $this->context);
$this->streams = $this->streamFactory->createStreamCollection();
$this->streams->attach($this->server, '@server');
$this->streams->attach($this->server, $this->identity);
$this->logger->info("[server] Starting server on {$uri}.");
} catch (Throwable $e) {
$error = "Server failed to start: {$e->getMessage()}";
Expand All @@ -529,15 +538,14 @@ protected function acceptSocket(SocketServer $socket): void
try {
/** @var SocketStream $stream */
$stream = $socket->accept();
$name = $stream->getRemoteName() ?? 'unknown';
$this->streams()->attach($stream, $name);
$connection = new Connection(
$stream,
false,
true,
$this->isSsl(),
$this->httpFactory
);
$this->streams()->attach($stream, $connection->getIdentity());
} catch (StreamException $e) {
throw new ConnectionFailureException("Server failed to accept: {$e->getMessage()}");
}
Expand All @@ -552,8 +560,8 @@ protected function acceptSocket(SocketServer $socket): void
}
/** @throws StreamException */
$request = $this->performHandshake($connection);
$this->connections[$name] = $connection;
$this->logger->info("[server] Accepted connection from {$name}.");
$this->connections[$connection->getIdentity()] = $connection;
$this->logger->info("[server] Accepted connection from {$connection->getIdentity()}.");
$this->dispatch('handshake', [
$this,
$connection,
Expand Down
30 changes: 22 additions & 8 deletions tests/mock/MockStreamTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,14 @@ private function expectWsClientConnect(
$this->expectContext()->addAssert(function ($method, $params) {
$this->assertIsResource($params[0]);
});
$this->expectSocketStreamGetRemoteName()->setReturn(function () use ($host, $port) {
return "{$host}:{$port}";
});
$this->expectStreamCollectionAttach();
$this->expectSocketStreamGetLocalName()->setReturn(function () use ($local) {
return "{$local}";
});
$this->expectSocketStreamGetRemoteName()->setReturn(function () use ($host, $port) {
return "{$host}:{$port}";
});

$this->expectStreamCollectionAttach();
$this->expectSocketStreamSetTimeout()->addAssert(function ($method, $params) use ($timeout) {
$this->assertEquals($timeout, $params[0]);
});
Expand Down Expand Up @@ -179,8 +176,8 @@ private function expectWsServerSetup(string $scheme = 'tcp', int $port = 8000, a
$this->expectSocketServerGetMetadata();
$this->expectStreamFactoryCreateStreamCollection();
$this->expectStreamCollection();
$this->expectStreamCollectionAttach()->addAssert(function ($method, $params) {
$this->assertEquals('@server', $params[1]);
$this->expectStreamCollectionAttach()->addAssert(function ($method, $params) use ($port) {
$this->assertEquals("server/{$port}", $params[1]);
});
}

Expand All @@ -190,10 +187,9 @@ private function expectWsServerSetup(string $scheme = 'tcp', int $port = 8000, a
private function expectWsSelectConnections(array $keys = []): StackItem
{
$this->expectStreamCollectionWaitRead()->setReturn(function ($params, $default, $collection) use ($keys) {
$keys = array_flip($keys);
$selected = new StreamCollection();
foreach ($collection as $key => $stream) {
if (array_key_exists($key, $keys)) {
if (in_array($key, $keys)) {
$selected->attach($stream, $key);
}
}
Expand All @@ -206,6 +202,24 @@ private function expectWsSelectConnections(array $keys = []): StackItem
return $last;
}

private function expectWsServerAccept(
string $local = 'localhost:8000',
string $remote = '127.0.0.1:12345'
): StackItem {
$this->expectSocketServerAccept();
$this->expectSocketStream();
$this->expectSocketStreamGetMetadata();
$this->expectContext();
$this->expectSocketStreamGetLocalName()->setReturn(function () use ($local) {
return $local;
});
$this->expectSocketStreamGetRemoteName()->setReturn(function () use ($remote) {
return $remote;
});
$this->expectStreamCollectionAttach();
return $this->expectSocketStreamSetTimeout();
}

/**
* @param array<mixed> $headers
*/
Expand Down
3 changes: 1 addition & 2 deletions tests/suites/client/ClientErrorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ public function testFailedConnection(): void
$this->expectSocketStream();
$this->expectSocketStreamGetMetadata();
$this->expectContext();
$this->expectSocketStreamGetRemoteName();
$this->expectStreamCollectionAttach();
$this->expectSocketStreamGetLocalName();
$this->expectSocketStreamGetRemoteName();
$this->expectStreamCollectionAttach();
$this->expectSocketStreamSetTimeout();
$this->expectSocketStreamIsConnected()->setReturn(function () {
return false;
Expand Down
Loading