Class
WebSocket
A WebSocket connection (RFC 6455), server side, on a ClientRequest or any Duplex.
Introduction#
WebSocket::from checks the handshake (and answers 426, 400 or 403 itself when it is
not one), then runs the callback in a coroutine of its own. The connection closes when the
callback returns (1000) or throws (1011, and swerve logs the exception); WebSocket::end
closes it early. The connection is read all the time, also when the callback only sends: pings
are answered, and when the client leaves the callback is cancelled, so the loop above ends with
its client. The server pings every connection every WebSocket::$pingInterval seconds, from
one coroutine for all of them, so proxies don't close a quiet one.
A message goes to the $onMessage listeners, one at a time, in the order it arrived: a slow
listener holds back the reading. The pull style is the alternative, and the two don't mix:
receive() throws while $onMessage has listeners.
send() and end() may be called from any coroutine. A client that reads slower than the
callback sends makes send() wait; one that sends faster than the callback receives is not
read from, so TCP holds it back. Neither makes the worker buffer.
WebSocket::accept is the lower level: the handler itself reads with receive(), as
$onMessage is not called then, and calls end() when it is done.
The contract, with the close codes, the limits and what drains do, is in docs/websocket.md.
Class synopsis#
final class WebSocket implements \IteratorAggregate, \Traversable {
/* Methods */
public static WebSocket::from(
ClientRequest $request,
callable $callback,
array $subprotocols = [],
?array $origins = null,
int $maxMessage = self::MAX_MESSAGE
): void
public static WebSocket::accept(
ClientRequest $request,
array $subprotocols = [],
?array $origins = null,
int $maxMessage = self::MAX_MESSAGE
): ?WebSocket
public static WebSocket::handshake(
string $method,
string $version,
array $headers,
bool $hasBody,
array $subprotocols = [],
?array $origins = null
): WebSocketHandshake
public static WebSocket::upgrade(
ClientRequest $request,
array $headers,
callable $callback,
int $maxMessage = self::MAX_MESSAGE
): void
public static WebSocket::run(
Duplex $connection,
callable $callback,
?string $subprotocol = null,
int $maxMessage = self::MAX_MESSAGE
): void
public WebSocket::receive(): ?string
public WebSocket::getIterator(): Generator
public WebSocket::isBinary(): bool
public WebSocket::send(string $text): void
public WebSocket::sendBinary(string $data): void
public WebSocket::end(int $code = 1000, string $reason = ''): void
public WebSocket::isClosed(): bool
}Constants#
| Name | Value | Description |
|---|---|---|
MAX_MESSAGE | 1048576 | The largest message received by default; a larger one closes the connection with 1009. |
INBOX | 64 | Messages received and not yet taken by receive() before the reading waits, in from(). |
WRITE_TIMEOUT | 30.0 | Seconds a send() waits for a client that doesn't read; then the client is given up and the connection closed. |
CLOSE_TIMEOUT | 5.0 | Seconds the close frame waits for a client that doesn't read. |
Properties#
| Name | Type | Description |
|---|---|---|
$pingInterval | static float | Seconds between the pings the server sends to every open connection (a process-wide setting). |
$onMessage | readonly Event | Called with (string $data, bool $binary) for each text or binary message received, in from(). |
$onClose | readonly Event | Called once with (int $code, string $reason) when the connection has ended, whichever side ended it. |
$subprotocol | readonly ?string | The subprotocol chosen from the client's offer and the server's list, or null when none was. |
Methods#
| Method | Description |
|---|---|
from | Answer the handshake and run $callback on the open connection; returns when the connection has closed. |
accept | Answer the handshake and return the open connection, or null after refusing the request. |
handshake | For framework adapters: decide a handshake from the parts of any request, answering nothing. |
upgrade | For framework adapters: send the 101 with the headers an adapter decided, and run $callback on the open connection. |
run | For adapters whose own code already sent the 101 (such as swerve-psr15's bridge, after its middleware decided the headers): run $callback on $connection as WebSocket::upgrade does, with the same close codes and cancellation. $connection need be no more than a Duplex: read(), write(), end() and close() are all this uses. |
receive | The next text or binary message, or null once the connection is closed. |
getIterator | The messages received, until the connection closes: foreach ($ws as $message). |
isBinary | Whether the message receive() returned last was binary, rather than text. |
send | Send a text message; nothing happens once the connection is closed. |
sendBinary | Send a binary message; nothing happens once the connection is closed. |
end | Close the connection: send a close frame with $code and $reason and end the sending side, once. |
isClosed | Whether the connection is closed: end() was called, the client said goodbye, or the connection ended. |
Examples#
use Swerve\ClientRequest;
use Swerve\RequestHandler;
use Swerve\WebSocket;
$app = new RequestHandler(function (ClientRequest $request) {
WebSocket::from($request, function (WebSocket $ws) {
foreach ($ws as $message) { // ends when the connection closes
if ('bye' === $message) {
return; // returning closes the connection (1000)
}
$ws->send("echo: $message");
}
});
});
if (debug_backtrace()) {
return $app; // swerve loads this file: it is the application's swerve.php
}
// Run directly: serve the application with swerve, and talk to it as a browser would
require __DIR__ . '/../serve.php';
websocket(__FILE__, '/echo', ['hello', 'world', 'bye']);HTTP/1.1 101 Switching Protocols upgrade: websocket connection: Upgrade sec-websocket-accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Server: Swerve > hello > world > bye < echo: hello < echo: world < close 1000
return new Swerve\RequestHandler(function (Swerve\ClientRequest $request) {
Swerve\WebSocket::from($request, function (Swerve\WebSocket $ws) {
$ws->onMessage->listen(function (string $data) { // inbound: events
Swerve::publish('chat', $data);
});
foreach (Swerve::subscribe('chat') as $out) { // outbound: plain sequential code
if ('end' === $out) {
return; // returning closes the socket (1000)
}
$ws->send($out);
}
});
});foreach ($ws as $message) { // ends when the connection closes
$ws->send("echo: $message");
}See also#
ServerSentEventsA Server-Sent Events stream (EventSourcein the browser) on aClientRequest.Swerve::publishSend a message to every subscriber of$topic, in every worker process of this swerve, this one included.Swerve::subscribeReceive what is published to$topicfrom now on.