You are viewing the documentation for the 1.x branch of the WebSocket Server package which has not yet been released. Be aware that the API for this version may change before release.
Architecture
WAMP v1 Compliant
This package aims to be compliant to the WAMP v1 specification. The terminology used in this package does its best to align with the specification's features, to include support for:
- Remote Procedure Calls (RPC Message Handler)
- Publish & Subscribe (PubSub) message pattern (Topic Message Handler)
- URIs represented in the CURIE (Compact URI Expression) syntax
Why WAMP v1 and Not v2?
The below is the opinion of the package author (Michael Babker).
To be honest, I've found the v2 specification difficult to follow and could not find a good reference implementation to study in a way that made sense to me. On the other hand, I've been working with Ratchet for several years, including maintaining an older Symfony bundle providing an integration of that package into Symfony.
While the WAMP v1 specification is absolutely deprecated, and finding resources supporting it is difficult (including the original v1 specification and the Autobahn|JS v1 implementations no longer being readily accessible), I've found the Ratchet architecture paired with ReactPHP to be more than suitable for my client work.
While some of the implementation details between this package and Ratchet differ, this package was very much designed as a modernized implementation of Ratchet to adapt to changes in modern PHP development and as an opportunity for me to re-evaluate the integrations I've been maintaining on my own for the last several years and build a fully comprehensive standalone library with framework integration.
Server
At the root of the WebSocket Server package is the BabDev\WebSocket\Server\Server interface. The server is responsible for listening for incoming connections and dispatching messages to the middleware stack.
The default server implementation is based on the ReactPHP Socket component.
Error Handling
Any Throwable raised by the middleware stack while opening a connection, processing incoming data, or closing a connection is forwarded to the stack's onError() method, so a failure on one connection does not stop the server for every other connection.
If the onError() method itself throws (for example, an event listener for the ConnectionError event fails), the server closes the affected connection and reports the failure to an optional PSR-3 logger. The logger can be provided as the fourth argument to the BabDev\WebSocket\Server\ReactPhpServer constructor, or with the withLogger() method when using the Application class. Without a logger, these failures are silently discarded.
Slow Clients
Data sent to a client which is not reading it is buffered in memory. To prevent a slow or unresponsive client from exhausting the server's memory, the server closes the connection once 1 MiB of data has been written to it after its write buffer is full. The limit can be changed with the fifth argument to the BabDev\WebSocket\Server\ReactPhpServer constructor, or with the withWriteBufferLimit() method when using the Application class; pass null to disable it.
Graceful Shutdown
The BabDev\WebSocket\Server\ReactPhpServer::shutdown() method stops the server: it stops accepting new connections, closes all open connections, and stops the event loop once they are closed. Connections which have not closed when the timeout expires (5 seconds by default) are forcibly closed. To close WebSocket connections with a "1001 Going Away" close frame first, call the closeAllConnections() method of the BabDev\WebSocket\Server\WebSocket\Middleware\EstablishWebSocketConnection middleware before shutting down the server.
When using the Application class, the server is shut down this way when the process receives a SIGTERM or SIGINT signal (for example, when a container is stopped or Ctrl+C is pressed), provided the event loop supports signals (which for the default event loop requires the pcntl extension). A second signal stops the process immediately. The timeout can be changed with the withShutdownTimeout() method; pass null to not handle these signals.
Middleware Based Design
Similar to Ratchet, the WebSocket Server package uses a middleware based architecture to represent the application.
One of the biggest differences with this package in comparison to Ratchet is that each middleware is isolated with full dependency injection (along with sane defaults where practical), which makes it simpler to customize the application if desired.
Message Handlers
A message handler is responsible for handling an incoming WAMP message. For RPC message handlers, they are responsible for sending the corresponding "CALLRESULT" or "CALLERROR" message back to the client.
Message Handler Resolver
The BabDev\WebSocket\Server\WAMP\MessageHandler\MessageHandlerResolver interface defines a service locator for message handlers. The resolver will provide the message handler, which may include any decorating message middleware for that object. Fundamentally, this resolver service is similar in scope/design to the controller resolvers found in Symfony's HttpKernel component.
Connection
The BabDev\WebSocket\Server\Connection interface represents a client connection to the server. During the course of the message lifecycle, this connection is decorated and message middleware and handlers will receive a BabDev\WebSocket\Server\WAMP\WAMPConnection implementation which provides access to several shortcut methods to assist with handling all server-to-client WAMP messages, as well as providing the URI resolver for CURIEs.
Connections have an attribute store attached to them to allow storing arbitrary data for each connection (similar to the "attributes" ParameterBag on a Symfony Request object).
Topic Registry
The WebSocket Server package uses a BabDev\WebSocket\Server\WAMP\TopicRegistry to centrally track all active topic (PubSub) channels and all active connections to those channels. This registry is highly beneficial in applications which need to broadcast messages from a message handler to all connected clients and can be viewed as the core data store for the active connections to the server.
Message Flow
The below represents the message flow for an application using all middleware available in this package (optional middleware are noted as such):
BabDev\WebSocket\Server\Http\Middleware\ParseHttpRequest- Parses the HTTP request used to establish the connection to the server and stores it as a PSR-7Psr\Http\Message\RequestInterfaceon the connection's attribute storeBabDev\WebSocket\Server\Http\Middleware\ResolveForwardedClientAddress(optional) - When the server is behind a trusted reverse proxy, replaces the connection's remote address with the client's IP address from the forwarding headersBabDev\WebSocket\Server\Http\Middleware\RejectBlockedIpAddress(optional) - Rejects incoming connections based on the client's IP address, supports both single addresses and subnet rangesBabDev\WebSocket\Server\Http\Middleware\RestrictToAllowedOrigins(optional) - Rejects incoming messages based on theOriginheader from the HTTP requestBabDev\WebSocket\Server\Session\Middleware\InitializeSession(optional) - Reads the data from the active session for the client connection from the main web frontend using the session classes from Symfony's HttpFoundation component and stores theSessionobject on the connection's attribute storeBabDev\WebSocket\Server\WebSocket\Middleware\EstablishWebSocketConnection- Validates the incoming HTTP request and sends the HTTP response to the client, which will validate the WebSocket handshake and establish the connection or close the connection immediately; this middleware also supports a keepalive ping from the server to all connected clientsBabDev\WebSocket\Server\WAMP\Middleware\ParseWAMPMessage- Parses and validates an incoming WAMP message; for "PREFIX" messages the middleware will register a prefix to the connection's attribute store for use with CURIEs, and for all other client-to-server messages it will forward the message along the chain to the message handlersBabDev\WebSocket\Server\WAMP\Middleware\UpdateTopicSubscriptions- Ensures that theBabDev\WebSocket\Server\WAMP\Topicobject representing a PubSub channel is stored in the registry (clearing it if all connections for that channel are closed) and updates the connection's attribute store with a list of active topic subscriptionsBabDev\WebSocket\Server\WAMP\Middleware\DispatchMessageToHandler- Using Symfony's Routing component, this middleware will match the URI for the client-to-server message to a defined route for the application, locates the appropriate message handler using the message handler resolver, and forwards the message; as the last middleware in the server stack, this middleware can also optionally emit events using a PSR-14 compatible event dispatcher when a connection is opened, closed, or an error occurs
Application
The BabDev\WebSocket\Server\Application class is available to help with bootstrapping the server application. It is instantiated with the arguments for a React\Socket\SocketServer instance (please see the ReactPHP Socket component documentation for details) and allows all optional features to be configured before running the server.
The application builds a fixed middleware stack (see the message flow section) and does not support adding custom middleware; applications needing their own middleware should build the middleware stack themselves, such as with the WebSocket bundle for Symfony applications.
To access the active topics from application code (for example, to broadcast a message from a timer), create a topic registry and provide it to the application with the withTopicRegistry() method.
The application is configured with the following methods:
| Method | Purpose |
|---|---|
route() | Registers a message handler for a URI pattern |
withMessageHandlerResolver() | Replaces the message handler resolver |
withErrorUriResolver() | Replaces the error URI resolver |
withEventDispatcher() | Dispatches connection events with a PSR-14 event dispatcher |
withLogger() | Reports failures the middleware stack cannot handle to a PSR-3 logger (see error handling) |
withSession() | Reads the session data for each connection (see the initialize session middleware) |
withTopicRegistry() | Uses the given topic registry, allowing application code to access the active topics |
allowOrigin() / removeAllowedOrigin() | Restricts connections to the allowed origins (see the restrict to allowed origins middleware) |
blockAddress() / allowAddress() | Rejects connections from blocked IP addresses (see the reject blocked IP address middleware) |
withTrustedProxies() | Resolves the client address when behind a reverse proxy (see the resolve forwarded client address middleware) |
withMaxRequestSize() | Sets the maximum size of the HTTP request (see the parse HTTP request middleware) |
withRequestTimeout() | Sets the time a client has to send its HTTP request, 10 seconds by default |
withStrictSubProtocolCheck() | Sets whether clients must request a supported WebSocket sub-protocol (see the establish WebSocket connection middleware) |
withMessageSizeLimits() | Sets the maximum size of messages and frames received from clients |
withKeepAlive() | Enables the keepalive ping-pong |
withServerIdentity() / withMaxPrefixes() | Configure the parse WAMP message middleware |
withWriteBufferLimit() | Sets how much data can wait to be sent to a slow client, 1 MiB by default (see slow clients) |
withShutdownTimeout() | Sets the time to wait for connections to close when shutting down, 5 seconds by default (see graceful shutdown) |