Real-Time Web applications with PHP and Ratchet.

This post is a set of approaches and rules that SmartGamma uses for building real-time web applications. Our real-time solutions mostly based on articles and recommendations of Phil Leggetter — a real-time web evangelist and consultant. His articles give us a very clear understanding of real-time web technologies that we can use to build such real-time applications.

What is real-time?

The real-time web is a set technologies and practices that enable users to receive information as soon as it is published by its authors, rather than requiring that they or their software check a source periodically for updates. (wikipedia).

Theoretically this means “immediate data delivery”. However, practically data should be delivered in a relevant period and this period always depends on a context. Another important moment here is to deliver data without regular request by server from the client side.

Transport mechanism

Technically we have several transport mechanisms to provide real-time or so called “near real-time” connections:

  • HTTP long polling — client polls the server requesting new information. The server holds the request open until new data is available. Once available, the server responds with a new data and connection drops. When the client receives the response, it immediately sends another request, and the operation is repeated.
  • HTTP streaming — based on persistent connection between client and server. So when server has new data it can push it to the client.
  • HTML5 WebSockets (RFC6455) — it is a specification that allows a bidirectional communication between client and server over a single TCP connection. It was initially created for web browsers, but can be used on any client which can handle TCP connections.

Why we decided to choose WebSockets?

  • Main advantage of websockets that it is not a HTTP request, after initial handshake request a single TCP connection created and it exists as long as we need. We do not need to send headers at each message — so no overhead, just data.
  • Very efficient if application requires frequent data exchange in both ways. This means that client and server sends data in one connection. In other words it is truly bidirectional.
  • Large number of client libraries allow to implement client on any language.
  • Can be used with SSL encryption — wss:// URLs, that was mandatory in our application’s requirements.

WebSockets with PHP

In one of our Symfony2 live video streaming project we had a requirement to build a realtime interaction between users — some kind of chatting. So we started to learn how it can be implemented. First we found a Ratchet library by Chris Boden (GitHub). This library implements a WebSocket server on top of ReactPHP. It can be easily wrapped into Symfony console command (found here) that runs in infinite react loop.

So here is some code examples of our implementation. First of all a Symfony console command to run the server:

class ListenCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('websocket:listen')
->setDescription('Listen for websocket')
->addOption('port', 'p', InputOption::VALUE_REQUIRED, 'The port to listen on', 8000)
->addOption('interface', 'i', InputOption::VALUE_REQUIRED, 'The interface to listen on', '0.0.0.0')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$application = new WebSocketApplication(
$this->getContainer()->get('snc_redis.default'),
$this->getContainer()->get('doctrine.orm.entity_manager'),
);
$server = IoServer::factory(
new HttpServer(
new WsServer(
new SessionProvider(
$application,
new RedisSessionHandler($redis)
)
)
),
$input->getOption('port'),
$input->getOption('interface')
);
$output->writeln(sprintf('Listening on: %s:%s', $input->getOption('interface'), $input->getOption('port')));
$server->run();
}
}

Looks clear and simple. A WebSocketApplication class should implement MessageComponentInterface provided by Ratchet. It contains onOpen, onClose, onMessage and onError methods. Most important here is onMessage method and here how it looks in our application:

public function onMessage(ConnectionInterface $from, $msg)
{
$messageData = $this->decodeJSONAndCheckMessage($from, $msg);
if (!$messageData) {
return;
}
$loginResult = $this->deviceLogin($from, $messageData);
if (!$loginResult instanceof Error) {
$this->handleMessage($from, $messageData, $loginResult);
} else {
$this->onError($from, new \Exception('Cannot login device.'));
}
}

Here we process incoming message as JSON string and pass data to a handleMessage() method:

private function handleMessage(ConnectionInterface $from, stdClass $messageData)
{
$message = $messageData->message;
if (method_exists($this, $message)) {
try {
$this->$message($from, $messageData);
}
} catch (\Exception $ex) {
$this->onError($from, $ex);
}
} else {
$this->onError($from, new \Exception(sprintf('Unknown method "%s"', $message)));
}
}

So it is simply calling a method which came from a websocket message ($messageData->message) and passes ConnectionIntreface object in case we need to respond with some data to a client. This is a simplified example, but it shows the basics how to handle websocket with PHP and Symfony2. Of course we have a lot of application logic in a WebSocketApplication class.

Pros:

1. It is not hard to implement websocket application with Ratchet. We use it in production and it works well for a long period of time.

2. Ratchet implements a WAMP subprotocol, so you can implement RPC and Publish & Subscribe communication patterns through websockets, but for WAMP you will need a client library which is listed here.

Cons:

1. Ratchet does not support SSL for server. This problem can be solved with proxying websocket requests to Nginx, HAProxy or stunnel on a same machine.

2. One error will stop a websocket server. It means that we should handle all possible errors in websocket application and of course to cover it by functional tests.

3. Hard to scale horizontally. You need to implement your own solution for scaling to share connections between different machines.HAProxy can help with this.

4. If you are interacting with database inside a WebSocketApplication class you always need to check it availability and reconnect to it if connection was dropped. For example Mysql server drops connections after some time inactivity (by default 8 hours). This applies to any service or application layer. Here is what we do when Doctrine ORM cannot connect to a database. If there was no database related activity in socket connection for a long time we had a “MySQL server has gone away” error. Solved this with try … catch block.

try {
$result = $repo->findOneBy($findOneByParams);
} catch (\Doctrine\DBAL\DBALException $ex) {
$this->reconnectToDatabase();
$result = $repo->findOneBy($findOneByParams);
}

Useful links:

http://socketo.me/ — Ratchet PHP library to create WebSocket applications
http://reactphp.org/ — Event-driven, non-blocking I/O with PHP

http://www.leggetter.co.uk/ — Phil Leggeter’s page. Real-Time Web Evangelist & Consultant

http://www.leggetter.co.uk/real-time-web-technologies-guide/ — Real-Time technoligies guide

http://wamp-proto.org/ — The Web Application Messaging Protocol

https://www.youtube.com/watch?v=04jVCKLXttk — Chris Boden talks about Ratchet at SKI PHP Conference

Instead of P.S.

Another interesting solution we are investigating right now is a proxy server called Pushpin which is reverse proxy for the real-time Web. It is an open source project developed by Fanout.io. It provides long polling, HTTP streaming and Websocket connections for clients and proxies these connections to your backend application. As a result — backend should not handle a long lived connection at all. Instead it should receive and send a simple HTTP requests, which is definitely a job for PHP and Symfony and for any other backend as well.

Thus, integration of Pushpin is our work in progress. We’ve decided to develop a Symfony2 bundle (https://github.com/smart-gamma/pushpin-bundle) to integrate it with php-gripcontrol library into our application. Right now this bundle is under development process so check for our updates.

The article was made base on speech “Real-Time Web applications. Websocket usage in PHP” of our Symfony2 Senior developer — Stanislav Zozulya, during our company internal team building conference 28.11.2015, Uzhorod, Ukraine

Slideshare of speech

Originally posted on www.smart-gamma.com