Skip to content

Commit

Permalink
Server shutdown
Browse files Browse the repository at this point in the history
  • Loading branch information
sirn-se committed Jul 4, 2024
1 parent de5ce2d commit e51f316
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Server `getConnections()`, `getReadableConnections()`, `getWritableConnections()` (@sirn-se)
* `onHandshake(...)` listener (will deprecate `onConnect(...)`) (@sirn-se)
* Server `setMaxConnections(int|null)` (@sirn-se)
* Server `shutdown()` orderly close server (@sirn-se)

### `3.0.0`

Expand Down
3 changes: 3 additions & 0 deletions docs/Server.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ $server->start();
// Stop server - When called, server will no longer listen to incoming messages but will not disconnect clients
$server->stop();

// Orderly shutdown server - Will initate close procedure on all connected clients and stop running when all are disconnected
$server->shutdown();

// Disconnect server - Server will immediately stop and disconnect all clients without normal close procedure
$server->disconnect();
```
Expand Down
4 changes: 4 additions & 0 deletions examples/echoserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
echo "< [{$connection->getRemoteName()}] Stop server\n";
$server->stop();
break;
case '@server-shutdown':
echo "< [{$connection->getRemoteName()}] Shutdown server\n";
$server->shutdown();
break;
case '@server-close':
echo "< [{$connection->getRemoteName()}] Broadcast Close\n";
$server->send(new \WebSocket\Message\Close());
Expand Down
34 changes: 34 additions & 0 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ public function start(): void
try {
// Clear closed connections
$this->detachUnconnected();
if (is_null($this->streams)) {
$this->stop();
return;
}

// Get streams with readable content
$readables = $this->streams->waitRead($this->timeout);
Expand Down Expand Up @@ -394,6 +398,36 @@ public function isRunning(): bool

/* ---------- Connection management ---------------------------------------------------------------------------- */

/**
* Orderly shutdown of server.
* @param int $closeStatus Default is 1001 "Going away"
*/
public function shutdown(int $closeStatus = 1001): void
{
$this->logger->info('[server] Shutting dowm');
if ($this->getConnectionCount() == 0) {
$this->disconnect();
}
// Store and reset settings, lock new connections, reset listeners
$max = $this->maxConnections;
$this->maxConnections = 0;
$listeners = $this->listeners;
$this->listeners = [];
// Track disconnects
$this->onDisconnect(function () use ($max, $listeners) {
if ($this->getConnectionCount() > 0) {
return;
}
$this->disconnect();
// Restore settings
$this->maxConnections = $max;
$this->listeners = $listeners;
});
// Close all current connections, listen to acks
$this->close($closeStatus);
$this->start();
}

/**
* Disconnect all connections and stop server.
*/
Expand Down
62 changes: 61 additions & 1 deletion tests/suites/server/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
Pong,
Text
};
use WebSocket\Middleware\Callback;
use WebSocket\Middleware\{
Callback,
CloseHandler
};
use WebSocket\Test\{
MockStreamTrait,
MockUri
Expand Down Expand Up @@ -389,6 +392,63 @@ public function testDetachConnection(): void
unset($server);
}

public function testShutdown(): void
{
$this->expectStreamFactory();
$server = new Server(8000);
$server->setStreamFactory(new StreamFactory());
$server->addMiddleware(new CloseHandler());

$server->onHandshake(function ($server, $connection, $request, $response) {
$server->shutdown();
});

$this->expectWsServerSetup(scheme: 'tcp', port: 8000);
$this->expectWsSelectConnections(['@server']);
// Accept connection
$this->expectSocketServerAccept();
$this->expectSocketStream();
$this->expectSocketStreamGetMetadata();
$this->expectSocketStreamGetRemoteName()->setReturn(function () {
return 'fake-connection-1';
});
$this->expectStreamCollectionAttach();
$this->expectSocketStreamGetLocalName()->setReturn(function () {
return 'fake-connection-1';
});
$this->expectSocketStreamGetRemoteName();
$this->expectSocketStreamSetTimeout();
$this->expectWsServerPerformHandshake();

$this->expectSocketStreamIsWritable();
$this->expectSocketStreamWrite();
$this->expectSocketStreamIsReadable();
$this->expectSocketStreamCloseWrite();
$this->expectSocketStreamGetMetadata();

$this->expectSocketStreamIsConnected();
$this->expectWsSelectConnections(['fake-connection-1']);
$this->expectSocketStreamRead()->addAssert(function (string $method, array $params) {
$this->assertEquals(2, $params[0]);
})->setReturn(function () {
return base64_decode('iIA=');
});
$this->expectSocketStreamRead()->addAssert(function (string $method, array $params) {
$this->assertEquals(4, $params[0]);
})->setReturn(function () {
return base64_decode('RExLFw==');
});
$this->expectSocketStreamIsWritable();
$this->expectSocketStreamClose();
$this->expectSocketStreamIsConnected();
$this->expectStreamCollectionDetach();
$this->expectSocketServerClose();

$server->start();

unset($server);
}

public function testAlreadyStarted(): void
{
$this->expectStreamFactory();
Expand Down

0 comments on commit e51f316

Please sign in to comment.