Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Keep your users up-to-date in real-time with WebSockets!

Keep your users up-to-date in real-time with WebSockets!

Live document collaboration, playing cooperative and competetive games, updating sports scores, booking seats. Stateless and belated nature of HTTP requests is not a perfect match for these and other similar use cases. WebSockets offer immediate delivery of messages in two-way communication between the client and the server. Instead of periodic polling for new messages, they are pushed to the receiver over TCP/IP connection. Implementing WebSockets is not limited to technologies like node.js but has also been possible in PHP for quite some time with impressive results. In this talk, I introduced this technology and told the audience how to successfully adopt it in their PHP applications while avoiding problems and pitfalls.

https://joind.in/event/phpday-2016/keep-your-users-up-to-date-in-realtime-with-websockets

Ondřej Mirtes

May 14, 2016
Tweet

More Decks by Ondřej Mirtes

Other Decks in Programming

Transcript

  1. ...is boring! There's a much broader set of technologies you

    can use to your advantage, your users' comfort and to lower your monthly hosting bill. Just don't be afraid to try them out.
  2. Inspected webpage is doing the same AJAX request over and

    over again checking if there's something new to show to the user.
  3. ½ interval delay
 on average! User waits a really long

    time to see the update. This approach also causes unnecessary server strain - building and checking state from clean slate in every request.
  4. Event occurs Sending to server Server processing Sending event to

    client User is notified about it Push time With push principle, we leave waiting completely out of the picture, everything happens instantly. User is notified immediately. This whole timeline usually happens in tens of ms.
  5. WebSockets TCP/IP connection, not HTTP (Connection upgrade handshake at the

    beginning) Not request-response, but a two-way stream of messages I encourage adopting optimistic UI
  6. youtu.be/XrvleVBa6aE WebSockets is not only a web technology, you can

    use it in native mobile apps too. Video of a prototype I made shows communication between web and a native app, React.PHP server forwards messages behind the scenes.
  7. Booking theater seats I'd like to show a few cool

    things I made with WebSockets.
  8. PHP is ready for long-running processes. Memory leaks do not

    happen because of language's fault anymore. But it certainly requires more discipline and thinking about memory allocation.
  9. WebSockets in PHP Asynchronous processing PHP is single-threaded, but still

    capable of asynchronous processing. Waiting happens in the OS. Suitable for HTTP requests, SQL queries, filesystem access…
  10. WebSockets in PHP Event loop Handling Events Waiting
 for Events

    Core of async programming. Possible with vanilla PHP using stream_select function.
 Use abstraction libraries like React.PHP for nicer code.
  11. WebSockets in PHP Beware of blocking functions! Request 1 R1

    - blocking the thread Request 2 Request 3 R2 R3 Responses
  12. WebSockets in PHP Non-blocking – start processing ASAP Request 1

    Request 2 Request 3 Event loop controls the flow. PHP process does something useful at all times and does not block.
  13. How asynchronous code looks $db ->query('SELECT url FROM foo') ->then(function

    ($result) {
 return $httpClient->get($result['url']); }) ->then(function ($response) { $webSocketClient->send($response->body); }); echo "processing...\n"; Using promises. The echo is run first because the callbacks are called in the next loop run (tick) at the earliest.
  14. Initial setup $loop = React\EventLoop\Factory::create(); $server = new \React\Socket\Server($loop);
 $server->listen(8080,

    '0.0.0.0'); new \Ratchet\Server\IoServer(
 new \Ratchet\Http\HttpServer(
 new \Ratchet\WebSocket\WsServer($app)
 ),
 $server
 );
 $loop->run(); Outside your main app's MVC and router, usually inside Symfony Console. $loop->run() blocks – nothing after it gets executed until $loop->stop() is called.
  15. $app – \Ratchet\MessageComponentInterface function onOpen(ConnectionInterface $conn); function onClose(ConnectionInterface $conn); function

    onError( ConnectionInterface $conn, \Exception $e ); function onMessage(ConnectionInterface $from, $msg); Entrypoint to the WebSockets app. Something like a controller – don't make a fat one. When client connects, I don't know anything about him, what is he interested in etc. The first message should usually be about what the client subscribes to.
  16. Simple chat app private $clients; function __construct() { $this->clients =

    new \SplObjectStorage(); } function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); } function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); }
  17. Simple chat app function onMessage( ConnectionInterface $from, $msg ) {

    foreach ($this->clients as $client) { if ($client !== $from) { $client->send($msg); } } } Avoid sending the message back to the original author.
  18. Everything should be asynchronous function onMessage( ConnectionInterface $from, $msg )

    { $this->db->query('...')->then( function ($result) use ($from) { $from->send($result['foo']); } ); } Stuff dependent on incoming messages should be async in order not to block the server. Async is more complex – anything can happen between the request is sent and the response is received. The client could have already been disconnected.
  19. Debugging /** \Monolog\Logger */ private $logger; function onOpen(ConnectionInterface $conn) {

    $ua = $connection ->WebSocket ->request ->getHeader('User-Agent'); $this->logger->addDebug('Connected: ' . $ua); } Use Monolog even for debug output – easy to switch to logging somewhere else
 just by configuration.
  20. Timers $timer = $loop->addTimer(5, function () { echo "5 seconds

    passed!\n"; }); $loop->cancelTimer($timer); $loop->addPeriodicTimer(5, function () { echo "Every 5 seconds\n"; }); Non-blocking alternative to sleep(). Useful in WebSockets for expiration or tracking inactivity. Can occur a little bit later – after "waiting for events" loop phase.
  21. One process to rule them all $_SESSION One process handling

    all requests – global variables like $_SESSION not usable
  22. One process to rule them all Client Browser WebSockets Server

    HTTP Web Server Session Storage You need to abstract session storage, session data are connection-specific Solved by Ratchet SessionProvider connected to a Symfony session handler
  23. One process to rule them all Stateless API approach Or

    do not use sessions at all and authorize user via a token in an HTTP header
  24. Inter-process commucation PHP-FPM CLI WebSockets Server Client Browsers When something

    happens in the classic big web app and you need to notify the WebSockets clients about it, you can do it through a message queue.
  25. Deployment Use Supervisord to keep the process alive Restart the

    process when deploying a new version Implement pcntl_signal to handle kill signals
  26. Nginx as a proxy Client Browser HTTP or HTTPS 80

    or 443 HTTP any port Ratchet Exotic ports are blocked in corporate environments, you want to provide access to the WS server through 80 or 443. Ratchet does not support HTTPS, can be solved with Nginx proxy.
  27. Nginx as a proxy upstream websockets_ratchet { server localhost:8080; }

    Name of the upstream will be used in proxy configuration.
  28. Nginx as a proxy location /heartbeat-websocket { proxy_pass http://websockets_ratchet; proxy_http_version

    1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400; proxy_redirect off; proxy_buffering off; } It's important to turn the buffering off otherwise it doesn't work as well.
  29. Ratchet 1 Ratchet 2 Ratchet 3 Ratchet 4 Loadbalancer Client

    Browser Horizontal scaling by adding Ratchet servers. They have to communicate between each other.
  30. Client-side support detection 'WebSocket' in window It's better to detect

    certain features like this instead of a browser vendor and version, too difficult to maintain the matrix of supporting browsers.
  31. WebSockets JavaScript API 
 var ws = new WebSocket('ws://localhost:8080');
 var

    ws = new WebSocket('wss://localhost:8080'); Constructor causes the client to connect. You can connect to any server, no cross-origin restriction forced by the browser. wss is secure variant, on HTTPS you can connect to wss only.
  32. WebSockets JavaScript API ws.onopen = function () {} ws.onerror =

    function (error) {} ws.onclose = function () {} ws.onmessage = function (event) { alert(event.data); } ws.send(JSON.stringify({'foo': 'bar'}));
  33. Reconnecting var openConnection = function () { var ws =

    new WebSocket(...); ws.onclose = function () { setTimeout(function () { openConnection(); }, 2000); }; }; User can go offline and come back online at any time. Improve this implementation
 by presenting that the connection is offline and prolonging the interval between attempts.
  34. Content Security Policy connect-src ws://example.com CSP is used to harden

    the security of your app – essentially whitelisting what the browser
 can do. self does not work because it matches the scheme of the website - ws is not http.
  35. WebSockets PHP Client
 wat? Until now we've talked about the

    web browser as the WebSockets client. But PHP can be a client too!
  36. WebSockets PHP client Server monitoring Use it to provide HTTP

    endpoint for services like Pingdom or Uptime Robot that do not support WebSockets directly. Return HTTP 200 if you can connect to the server, 500 if the server is down.
  37. WebSockets PHP client Ratchet WebSockets AJAX Long-polling server WebSockets If

    you need to support older browsers, you can write another React.PHP server to provide
 AJAX longpolling to your WebSockets app. This server will persist WebSockets connections across HTTP requests.