Gomoob WebSocket server
WebSocket server with tags management, forward messages on the right clients with ease !, (*1)
, (*2)
Introduction
The Gomoob WebSocket server is a simple Ratchet server which works with custom tags
to easily forward messages to clients depending on custom tags., (*3)
As an example let's suppose we have a Web Application with English and French users. English users should receive
English messages, French users should receive French messages., (*4)
Each application opens a Web Socket with a particular language tag., (*5)
// Web Application in English mode
var enWebSocket = new WebSocket('ws://localhost:8080?tags={"language":"EN}');
...
// Web Application in French mode
var frWebSocket = new WebSocket('ws://localhost:8080?tags={"language":"FR}');
...
On server side the Gomoob WebSocket server keeps track of the associations between tags and WebSocket connections. For
example this simple PHP peace of code allows to easily forward a message to all clients connected with the language=FR tag., (*6)
// PHP Server (in most cases a Web Server) to Web Socket server client, allows to send one message which is forwared to
// several opened WebSocket connections
$phpClient = new WebSocketClient('ws://localhost:8080');
$phpClient->send(WebSocketRequest::create($message, ['language' => 'FR']);
Installation
Server side (run the server)
Running a server requires only one line of code., (*7)
require __DIR__ . '/vendor/autoload.php';
echo "WebSocket server started, enter Ctrl+C to stop server." . PHP_EOL;
\Gomoob\WebSocket\Server\WebSocketServer::factory()->run();
Client side (PHP)
First pull the project with composer using the following dependency., (*8)
{
"require": {
"gomoob/php-websocket-server": "^1.0.0"
}
}
Then simply use the \Gomoob\WebSocket\Client\WebSocketClient class to send your messages., (*9)
// Open a Server / Server WebSocket connection
$phpClient = new WebSocketClient('ws://localhost:8080');
// Forward a message to all the WebSocket client connections associated to 'tag1' and 'tag2'
$response = $phpClient->send(
WebSocketRequest::create(
$message,
[
'tag1' => 'tag1Value',
'tag2' => 'tag2Value'
]
)
);
If you want to write solid unit tests we also provide the \Gomoob\WebSocket\Client\WebSocketClientMock
class. This class is a utility mock which is very easy to use., (*10)
// Somewhere in our code we use a \Gomoob\WebSocket\IWebSocketClient ...
// We suppose this code is implemented in MyPowerfulService->serviceMethod();
$phpClient->send(WebSocketRequest::create('Message 0.')->setTags(['tag0' => 'tag0Value']));
$phpClient->send(WebSocketRequest::create('Message 1.')->setTags(['tag1' => 'tag0Value']));
$phpClient->send(WebSocketRequest::create('Message 2.')->setTags(['tag0' => 'tag0Value', 'tag1' => 'tag1Value']));
// Then we write a test case by replacing the real WebSocket client implementation with the mock one
class SampleTestCase extends TestCase
{
public function setUp() {
$this->webSocketClient = new WebSocketClientMock();
$this->myPowerfulService->setWebSocketClient($this->webSocketClient);
}
public function testServiceMethod() {
// Calls the method to test
$this->myPowerfulService->serviceMethod();
// Checks the right requests were sent
$webSocketRequests = $this->webSocketClient->findByTags(['tag0' => 'tag0Value']);
$this->assertCount(2, $webSocketRequests);
$this->assertContains($webSocketRequest0, $webSocketRequests);
$this->assertNotContains($webSocketRequest1, $webSocketRequests);
$this->assertContains($webSocketRequest2, $webSocketRequests);
}
}
Advanced configuration
The default behavior of the Gomoob WebSocket server is the following !, (*11)
- Expose port
8080 and authorize connections from all IP addresses (i.e 0.0.0.0) ;
- Accept only plain string messages (exceptions are encountered if JSON messages are sent / received) ;
- Use a default PSR logger which output messages on the terminal ;
- Do not manage any authorization checks.
If one of those behaviors does not fit your need please read the following sub sections. You can also read the src/test/server.php file which shows how to start a server with custom message parsing and authorizations., (*12)
Message parser
By default the WebSocket server will accept plain string messages, if you try to send a JSON object then you'll
encounter the following exception., (*13)
The 'message' property is not a string, you must configure a message parser to parse messages !
This is the expected behavior, if you want the server to manage custom PHP object messages then you have to :, (*14)
- Make your PHP object messages extends
\JsonSerializable and implement the jsonSerialize() method
correctly ;
- Implement a custom message parser to create your custom PHP object messages when a plain JSON object is received.
A sample message object is provided in the \Gomoob\WebSocket\Message\Message class, feel free to read the
associated source code to understand how it works. You'll also found a sample message parser in the \Gomoob\WebSocket\Message\MessageParser., (*15)
To explain how to manage custom PHP object messages let's suppose we have the following message object to send., (*16)
class MyMessage {
private $messageProperty1;
public function __construct($messageProperty1) {
$this->messageProperty1 = $messageProperty1;
}
public function getMessageProperty1() {
return $this->messageProperty1;
}
}
Sending such a message in a browser on Javascript would require the following code., (*17)
var socket = new WebSocket('ws://localhost:8080');
socket.send(
JSON.stringify(
{
message : {
messageProperty1 : "Hello !"
},
tags : {
tag1 : 'tag1Value'
}
}
)
);
Or in PHP with the client we provide., (*18)
WebSocketClient::factory('ws://localhost:8080')->send(WebSocketRequest::create(new MyMessage('Hello !')));
As this this will not work because on server side the Gomoob WebSocket server will not know how to parse the messages
and how to re-create those messages to forward them to clients who opened WebSocket connections., (*19)
The first thing to do is to implement the \JsonSerializable class and the jsonSerializeMethod() in our
MyMessage class., (*20)
class MyMessage implements \JsonSerializable {
...
public function jsonSerialize() {
return [
'messageProperty1' => $this->messageProperty1;
];
}
}
Then we have to implement a message parser by extending the \Gomoob\WebSocket\IMessageParser class., (*21)
use Gomoob\WebSocket\IMessageParser;
class MyMessageParser implement IMessageParser {
public function parse(array $arrayMessage)
{
// Ensure the array contains only valid key names
foreach (array_keys($arrayMessage) as $key) {
if (!is_string($key) || !in_array($key, ['messageProperty1'])) {
throw new \InvalidArgumentException('Unexpected property \'' . $key . '\' !');
}
}
// The 'messageProperty1' property is mandatory
if (!array_key_exists('messageProperty1', $arrayMessage)) {
throw new \InvalidArgumentException('No \'messageProperty1\' property found !');
}
return new MyMessage($arrayMessage['messageProperty1']);
}
}
Finally we have to provide our parser when we create our WebSocket server., (*22)
WebSocketServer::factory(
[
'messageParser' => new MyMessageParser()
]
)->run();
Authorization Manager
By default the WebSocket server will accept all connections and message sendings, in most cases this behavior is not
expected because anybody could open a WebSocket on your server and try to forward messages to all connected clients
without authorization., (*23)
You can implement a custom authorization manager by implementing the \Gomoob\WebSocket\IAuthManager
interface, this interface has the following signature., (*24)
/**
* Interface which defines an authorization manager. An authorization manager allows to control authorization while
* opening Web Socket connections and sending messages over Web Sockets.
*
* @author Baptiste Gaillard (baptiste.gaillard@gomoob.com)
*/
interface IAuthManager
{
/**
* Function used to indicate if connection opening is authorized.
*
* @param \Ratchet\ConnectionInterface $connection the current Ratchet connection.
*
* @return boolean `true` if the connection opening is authorized, `false` otherwise.
*/
public function authorizeOpen(ConnectionInterface $connection);
/**
* Function used to indicate if message sending is authorized.
*
* @param \Ratchet\ConnectionInterface $connection the current Ratchet connection.
* @param \Gomoob\WebSocket\IWebSocketRequest $webSocketRequest the current Gomoob WebSocket request.
*/
public function authorizeSend(ConnectionInterface $connection, IWebSocketRequest $webSocketRequest);
}
So its very easy to manage authorizations, just return true or false with the authorizeOpen(...) or
authorizeSend(...) functions., (*25)
The ApplicationsAuthManager
To easier authorization we provide an authorization manager which allows to declare several applications with key
and secret properties., (*26)
This authorization manager is available in the \Gomoob\WebSocket\Auth\ApplicationsAuthManager class, it
works with a very simple YAML configuration file., (*27)
Here is a sample instanciation of the manager with a WebSocket server., (*28)
WebSocketServer::factory(
[
'authManager' => ApplicationsAuthManager::factory(
[
'authorizeOpen' => false,
'configurationFile' => __DIR__ . '/auth.yml'
]
)
]
)->run();
The content of the auth.yml file could be the following., (*29)
applications:
-
key: application1
secret: B4ajW3P7jfWEYPZsQV8mnteHg97G67uW
authorizeOpen: true
- key: application2
secret: 33yLWdynhaqm9tYjDFKf8gB8zmAPKdDP
authorizeOpen: false
Then the followig Javascript peace of code will apply., (*30)
// Does not work because required 'key' and 'secret' URL parameters are not provided
var socket1 = new WebSocket('wss://myserver.org:8080');
// Works because the 'key' and 'secret' URL parameters provided are valid
var socket2 = new WebSocket('wss://myserver.ord:8080?key=application1&secret=B4ajW3P7jfWEYPZsQV8mnteHg97G67uW');
// Does not work because the request does not provide the 'key' and 'secret' properties
socket2.send(
JSON.stringify(
{
message : {
messageProperty1 : "Hello !"
}
}
)
);
// Works because the request provides valid 'key' and 'secret' properties
socket2.send(
JSON.stringify(
{
message : {
messageProperty1 : "Hello !"
},
metadata : {
key : 'application2',
secret : '33yLWdynhaqm9tYjDFKf8gB8zmAPKdDP'
}
}
)
);
The same rules are also applicable with the PHP client we provide., (*31)
WebSocketClient::factory('ws://localhost:8080')->send(
WebSocketRequest::create(
new MyMessage('Hello !')
)->setMetadata(
[
'key' => 'application2',
'secret' => '33yLWdynhaqm9tYjDFKf8gB8zmAPKdDP'
]
)
);
Docker container
To help you start quickly we also provide a Docker container here https://hub.docker.com/r/gomoob/php-websocket-server., (*32)
Release history
1.2.0 (2016-08-23)
- Moves the
TagsTree class to \Gomoob\WebSocket\Util\TagsTree ;
- Add a new
TagsTree->reset() method ;
- Add a new
\Gomoob\WebSocket\Client\WebSocketClientMock class to easier unit testing ;
- Update composer dependencies.
1.1.0 (2016-08-18)
- Add more PHP Documentor documentation about the goals of
metadata in
the \Gomoob\WebSocket\IWebSocketRequest interface and the
\Gomoob\WebSocket\Request\WebSocketRequest class ;
- Add management of
defaultMetadata in the \Gomoob\WebSoscket\IWebSocketClient interface and the
\Gomoob\WebSocket\Client\WebSocketClient class ;
- Add management of
defaultTags in the \Gomoob\WebSocket\IWebSocketClient interface and the
\Gomoob\WebSocket\Client\WebSocketClient class ;
- Improve
\Gomoob\WebSocket\Message\Message serialization ;
- Improve
\Gomoob\WebSocket\Request\WebSocketRequest serialization ;
- Now all the factory methods can be calls with a
factory(...) method or an alias create(...) method.
1.0.3 (2016-08-17)
- Fix
port and address options problems while creating a WebSocketServer, the parameter were not
transmitted to the Ratchet server ;
- Now the default port number is
80 which is the default Ratchet server port.
1.0.2 (2016-08-17)
- Add missing
symfony/yaml composer dependency, otherwise problems was encountered while running
composer update --no-dev ;
- Add missing
monolog/monolog composer dependency, , otherwise problems was encountered while running
composer update --no-dev.
1.0.1 (2016-08-17)
- Configure specific Eclipse validator rules ;
- Add MIT license.
1.0.0 (2016-08-17)
About Gomoob
At Gomoob we build high quality software with awesome Open Source frameworks everyday. Would
you like to start your next project with us? That's great! Give us a call or send us an email and we will get back to
you as soon as possible !, (*33)
You can contact us by email at contact@gomoob.com or by phone number
(+33) 6 85 12 81 26 or (+33) 6 28 35 04 49., (*34)
Visit also http://gomoob.github.io to discover more Open Source softwares we develop., (*35)