VarspoolWebsocketBundle
Alpha stability. Provides websocket services, including an in-built server,
multiplexing, semantic configuration., (*1)
There may be better ways
to do Websockets in Symfony2, some of which will result in you not even needing a third-party
bundle. The amount of code needed to use Ratchet, for instance, is so small you
may decide to forgo custom configuration and using the Container to configure your server., (*2)
Installation
VarspoolWebsocketBundle depends on:, (*3)
And, of course, Symfony2. Mostly, the bundle is a light compatibility layer
over WebSocket 2.0 that allows it to be used with the Service Container., (*4)
Configuring dependencies
Composer
If you're using Composer, add the following packages to your project's
requires:, (*5)
{
"require": {
"varspool/websocket-bundle": "dev-master",
"wrench/wrench": "dev-master"
}
}
deps file
If you're using bin/vendors to configure your dependencies, add the following
lines to your deps
file:, (*6)
[wrench]
git=git://github.com/varspool/Wrench.git
version=origin/master
[VarspoolWebsocketBundle]
git=git://github.com/varspool/WebsocketBundle.git
target=/bundles/Varspool/WebsocketBundle
version=origin/master
Or, fork to your own repository first so you can send in pull requests and
improve upstream :+1:. Once you've done this you can use bin/vendors
to
obtain the bundles:, (*7)
$ bin/vendors update
[...]
> Installing/Updating wrench
> Installing/Updating VarspoolWebsocketBundle
app/autoload.php
Register the Varspool and Wrench namespaces in your autoloader (not
necessary if you're using Composer with Symfony 2.1):, (*8)
# app/autoload.php
$loader->registerNamespaces(array(
// [...]
'Varspool' => __DIR__.'/../vendor/bundles',
'Wrench' => __DIR__.'/../vendor/wrench/lib'
));
app/AppKernel.php
Register the VarspoolWebsocketBundle
:, (*9)
# app/AppKernel.php
public function registerBundles()
{
$bundles = array(
//...
new Varspool\WebsocketBundle\VarspoolWebsocketBundle(),
);
}
Usage
Server-side starts with a server
Any PHP process can only run a single Websocket server, serving a variable
number of clients. Performance is untested, and once you get into production
you might want to replace the server side. (The Multiplex interfaces descibed
below might help: put a message queue between your PHP application code and a
lightweight Websocket server.), (*10)
To start a server, the bundle provides a websocket:listen
console command,
accessible through app/console
:, (*11)
Usage:
websocket:listen [server_name]
Arguments:
server_name The server name (from your varspool_websocket configuration) (default: default)
The listen command takes a single required argument: the name of the server
configuration to use. Servers are defined in your Symfony2 configuration,
whether YAML, XML or PHP. You must define at least one to get started (we
suggest "default"). Here's what a definition might look like:, (*12)
varspool_websocket:
servers:
default: # Server name
listen: ws://192.168.1.103:8000 # default: ws://localhost:8000
# Applications this server will allow
applications:
- echo
- multiplex
# Origin control
check_origin: true
allow_origin: # default: just localhost (not useful!)
- "example.com"
- "development.localdomain"
# Other defaults
max_clients: 30
max_connections_per_ip: 5
max_requests_per_minute: 50
Once you've configured a server, run the websocket:listen command. When it
runs, the server will start up and serve applications you've
defined in your configuration., (*13)
Applications
The server on its own doesn't do anything until you write an application
for it. The server calls methods on your applications once they are registered., (*14)
Registering your application is easy. The server looks for services tagged as
varspool_websocket.application
. So, to run an application, export a service
with that tag., (*15)
A single server daemon can serve one or more applications. So, you'll also have
to include a key
attribute with your tag. This ends up in your application URL.
For example, if you use this service definition:, (*16)
<!-- Application\ChatBundle\Resources\config\services.xml -->
<service id="chat_service" class="Application\ChatBundle\Services\ChatService">
<tag name="varspool_websocket.application" key="chat" />
</service>
And your server is configured to listen on 192.168.1.10:8000, then the URL of
your application will be:, (*17)
ws://192.168.1.10:8000/chat
Applications are not registered on servers unless they are specified in the
server configuration. So, to enable the above application on the default
server, you configuration would need to contain:, (*18)
# app/config.yml
varspool_websocket:
servers:
default:
# ...
applications:
- chat
Here's another example service definition, this time in YAML:, (*19)
services:
websocket_example:
class: Application\ExampleBundle\Application\ExampleApplication
tags:
- { name: varspool_websocket.application, key: foobar }
For a simple example of an application, see Application\EchoApplication
., (*20)
I suggest you make your application classes extend
Varspool\WebsocketBundle\Application\Application
, but it's optional. A
tiny bit of type checking is done to see if your application would like logging
support, but that's about it. So, you're free to extend whatever class you
like: just implement a compatible interface. (This is the same approach taken
by php-websocket so far: an abstract WebSocket\Application
class is provided,
but the Server does no typechecking.), (*21)
Finally, here's what that the listen command looks like when you run it with a few services
defined:, (*22)
$ app/console websocket:listen default
info: Listening on 192.168.1.103:8080 with ssl off
info: Server created
info: Registering application: test (Application\TestBundle\Application\TestApplication)
info: Registering application: auth (Application\TestBundle\Application\AuthApplication)
info: Registering application: multiplex (Varspool\WebsocketBundle\Application\MultiplexApplication)
info: Registering application: echo (Varspool\WebsocketBundle\Application\EchoApplication)
Client-side
Of course, you'll need a browser that supports websockets., (*23)
As for Javascript libraries, they're mostly up to you. But unless you're
already using Coffeescript, you might find the ones shipped along with
php-websocket a pain to install., (*24)
Multiplexing
One thing I would recommend is multiplexing your javascript components'
connections. The SockJS way of doing that is pretty
elegant,
and is supported by an application shipped along with this bundle., (*25)
Client-side multiplexing
This bundle is compatible with the multiplex protocol that the SockJS
websocket-multiplex front-end
library uses. See
sockjs/websocket-multiplex for
downloads. They even have a handy CDN:, (*26)
<script src="http://cdn.sockjs.org/websocket-multiplex-0.1.js"></script>
This Javascript library provides a WebSocketMultiplex
object. You can feed it
any object compatible with a native WebSocket
. So, to start with you can feed
it a native WebSocket, and later on, when you decide to install a SockJS server
(or one is implemented in PHP) you can feed it a SockJS object. So, like this:, (*27)
var url = 'ws://example.com:8000/multiplex';
var socket;
if (window.MozWebSocket) {
socket = new MozWebSocket(url);
} else if (window.WebSocket) {
socket = new WebSocket(url);
} else {
throw "No websocket support detected"
}
socket.binaryType = 'blob';
var real_socket = new WebSocket(url);
var multiplexer = new WebSocketMultiplex(real_socket);
var foo = multiplexer.channel('bar');
// foo.send(), events: open, close, error, message
var logs = mutliplexer.channel('log_server');
// logs.send(), events: open, close, error, message
Server-side multiplexing
The default configuration for this bundle (in
Varspool/WebsocketBundle/Resources/config/services.xml) defines a server-side
multiplex application, with a key of "multiplex". Make sure this key is listed in
your config, under the allowed applications for your server., (*28)
When you're using the multiplex application, run by a server, your socket
is further abstracted into channels, identified by a topic string. All the
server-side listeners to a channel are notified of each message received from
a client. The listeners can then decide to reply to just the client who sent
the message, or to all clients subscribed to a channel., (*29)
- Clients cannot send messages to other clients, unless you specifically relay
them.
- Channels provide a handy abstraction to do so:
$channel->send('foo', 'text', false, array('except' => $client))
- Listeners cannot send messages to other listeners.
- But you can use whatever you like for that: listeners can be DI'd into the service
container.
The Listener/ConnectionListener interfaces
On the server side, you need only implement Multiplex\Listener
to be able to
listen to events on a channel:, (*30)
/**
* @param Channel $channel The channel is an object that holds all the active
* client connections to a given topic, and all the server-side
* subscribers. You can ->send($message) to the channel to broadcast
* it to all the subscribed client connections. ->getTopic() identifies
* the topic the message was received on.
*
* @param string $message The received message, as a string
*
* @param Connection $client The client connection the message was received
* from. You can ->send($string) to the client, but it is a raw Websocket
* connection, so if you want to send a multiplexed message to a single
* client, you'll probably use
* `Varspool\WebsocketBundle\Multiplex\Protocol::toString($type, $topic, $payload)`
* and the Protocol::TYPE_MESSAGE constant.
*/
public function onMessage(Channel $channel, $message, Connection $client);
Then just tag your service with
varspool_websocket.multiplex_listener
and the topic you want to listen to:, (*31)
<service id="example.custom" class="Application\ExampleBundle\Services\CustomService">
<tag name="varspool_websocket.multiplex_listener" topic="chat" />
</service>
All done. Your onMessage
method will be called with the details of messages
clients sent to the multiplex topic you specified. You might also like to
do something as clients "connect" to (actually, subscribe) and "disconnect"
from (either a real disconnect, or an unsubscribe) your service. To do this,
implement the additional Multiplex\ConnectionListener
interface as well:, (*32)
use Varspool\WebsocketBundle\Multiplex\Listener;
use Varspool\WebsocketBundle\Multiplex\ConnectionListener;
use Varspool\WebsocketBundle\Multiplex\Channel;
use Wrench\Connection;
class GameServer implements Listener, ConnectionListener
{
public function onMessage(Channel $channel, $message, Connection $client)
{
$client->send('Hello, player!');
$channel->send('Oh, wow, guys...' . $client->getClientId() . ' is here';
}
public function onConnect(Channel $channel, Connection $client)
{
$client->send('Welcome to the dungeon');
}
public function onDisconnect(Channel $channel, Connection $client)
{
$channel->send($client->getClientId() . ' is leaving! OH NOES!');
}
}
The MultiplexService parent service
For convenience, if you want to implement both of these interfaces, and some
other useful functionality (like getting access to the server or mulitplex
application instances), just extend Services\MultiplexService
, and export it
in your config with varspool_websocket.multiplex_service
as its parent., (*33)
Here's what that looks like in YAML (with bonus: mulitple channel listener):, (*34)
# config.yml
services:
example.websocket_auth:
class: Application\ExampleBundle\Services\AuthService
parent: varspool_websocket.multiplex_service
tags:
-
name: varspool_websocket.multiplex_listener
topic: auth
-
name: varspool_websocket.multiplex_listener
topic: login