Real-time Web Apps

… and check why 5600+ Rails engineers read also this

Real-time Web Apps

From the very beginning web pages were just static. Internet browsers received some HTML code to render and displayed it to user. When someone wanted to check if there are any news on our page, he had to refresh it and then some new data might appeared. Time to change it.

Nowadays, more and more often we’d like to have a real-time experience in our web applications. We are lazy and we want to be notified about changes without continuously clicking on refresh button in our browsers. Unfortunately HTTP protocol is based on request-response paradigm, so rather than being notified by a sever or listening on an open connection, we make some GETs or POSTs and receive data as an answer.

So far, to simulate this real-time behavior, we had to do some work-arounds such as a interval-timeout JS pattern or more sophisticated, but confusing long pooling design concept. Luckily, things have changed. In the age of modern browsers, HTML5 API, smarter users and skilled developers we have (and should use) great tools to build real real-time web applications.

Let’s start from WebSockets

WebSockets establish persistent connection between user’s browser and a server. Both of them can use it any time to send messages to each other. Every side of such connection listens on it to immediately receive incoming data. These messages can be primitives or even binary data. WebSockets allow to cross-domain communication so developers should pay attention on security issues on their own, because we aren’t bound to same-origin policy anymore and can communicate across domains.

There’s no special WebSocket opening method. After created, browser immediately tries to open a new connection. One of WebSocket property is readyState which is initialized with WebSocket.CONNECTING. Once connected, state changes to WebSocket.OPEN.

Client side:


ws = new WebSocket 'ws://arkency.com/notifications'
ws.onopen = -> ws.send 'connected!' # listen on state transition from WebSocket.CONNECTING to WebSocket.OPEN
ws.onmessage = (message) -> console.log message.data

Server side:


WebSocketServer = require('ws').Server
wss = new WebSocketServer(port: 8080)
wss.on 'connection', (ws) ->
  ws.on 'message', (message) ->
    console.log "received: #{message}"

  ws.send 'Connected!'

Source: dsheiko.com

Note about websockets

Because establishing a WebSocket connection might be a little bit tricky, it is worth to describe here some more details about that.

A client connects with a server using so called handshake process. The initial request should look like this:

Request URL:ws://echo.websocket.org/?encoding=text
Request Method:GET
Request Headers
    Connection:Upgrade
    Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
    Sec-WebSocket-Key:PmqLMA8neyQndMnwL4ptCg==
    Sec-WebSocket-Version:13
    Upgrade:websocket

And a server’s response:

Response Headers
    Access-Control-Allow-Headers:x-websocket-protocol
    Access-Control-Allow-Headers:x-websocket-version
    Access-Control-Allow-Headers:x-websocket-extensions
    Connection:Upgrade
    Sec-WebSocket-Accept:s9nX9CIyg7mR+ZgzLBvFzYdIL+g=
    Upgrade:WebSocket

We can see here that client sends Sec-WebSocket-Key header with some base64 value. Next, server append to it 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 string and return base64 of SHA-1 from this concatenation as a Sec-WebSocket-Accept header. This handshake is supposed to replace initial HTTP protocol with WebSocket using the same TCP/IP connection under the hood. Provisioning process allows to get known both sides and be recognized in future messages. Here is some nice demo.

A word about Event Machine

If you are reading us, there is a high chance that you are a Ruby developer. So naturally you might be tempted to use ruby solution for you server side code such as EventMachine like that:


EM.run {
  EM::WebSocket.run(host: "0.0.0.0", port: 8080) do |ws|
    ws.onopen { |handshake|
      puts "WebSocket connection open"
      ws.send "Hello Client, you connected to #{handshake.path}"
    }

    ws.onclose { puts "Connection closed" }

    ws.onmessage { |msg|
      puts "Recieved message: #{msg}"
      ws.send "Pong: #{msg}"
    }
  end

That might be great learning exercise to get more familiar with async code and feel more confident. However we would advise against using EM on production (speaking from experience because we once did).

Long story short. EM is quite OK, but the ecosystem around is unfortunately pretty small. And sometimes buggy in hard to reproduce or notice way. There are a bunch of Ruby libraries that can help integrate your software with many 3rd party solutions and speed up the development process. However you can’t use most of them in your EM-based application because their behavior is blocking and it would block your event loop (and take away all the benefits from using EM and its non-blocking approach). If you wanted to integrate such a blocking library into your EM application anyway you would have to schedule calling the library by using EventMachine.defer. That would lead to mixing non-blocking event based approach represented by EM and blocking behavior from your library. The mental overhead of it is not small.

At first sight it might seem that EM-compatible libraries are existing and working. There are certainly some HTTP libraries and database drivers. But once you start looking for those that will work properly with more advanced options such as replication you might find your options limited. It’s a shaky ground.

Another problem is that you won’t probably find much commercial options available for deployment of EM-based applications. So you will have to figure out the problem of deployment, rolling deploys, clean exit yourself.

So if you need non-blocking, server side solution for your application our recommendation would be to rather go with node.js which is much more popular and battle tested solution compared to EM. We really enjoyed using EM and a few gem which were nicely designed and tested. But the more you move away from get N-bytes from here and send them out there usecase and more towards application logic, flow and business processes, the more painful experience it becomes to keep using EM.

TLDR: EM for fun? Yes. EM in production? You better consider twice your options.

Now move on to Server-Sent Events

Server-Sent Events intended for streaming text-based event data from server directly to client. There are two requirements to use this mechanism: browser interface for EventSource and server 'text/event-stream' content type. SSE are used to push notifications into your web application, which makes it more interactive with user and provides dynamic content at the time it appears.

Client side:


es = new EventSource '/notifications'
es.onopen = -> es.send 'connected!'
es.onmessage = (event) -> console.log event.data

Server side:


class NotificationsController < ApplicationController
  include ActionController::Live

  def index
    response.headers['Content-Type'] = 'text/event-stream'
    begin
      sse = SSE.new(response.stream, retry: 300, event: "event-name")
      loop do
        sse.write({ base: SecureRandom.urlsafe_base64 })
        sleep 5.second
      end
    rescue IOError # Raised when browser interrupts the connection
    ensure
      sse.close
    end
  end
end

Source: dsheiko.com

Basic differences

WebSockets

  • Supported in all major modern browsers (IE+10)
  • Two-directional communication
  • Siutable for chats or online gaming
  • Based on custom protocol (ws:// and encrypted wss://)

Server-sent Events

  • Require Polyfill as they are still candidate recommendation and have no IE support
  • One-way messaging (server -> browser)
  • Best for push notifications and status updates
  • Leverages HTTP protocol

Similarities

  • Both can provide real-time web application experience in area of notifications and updates
  • JavaScript API
  • Both are pretty new and may not be supported in every environment

References and resources

  1. https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events
  2. https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_client_applications
  3. http://dev.w3.org/html5/eventsource/
  4. http://dev.w3.org/html5/websockets/
  5. http://dsheiko.com/weblog/websockets-vs-sse-vs-long-polling/
  6. http://html5doctor.com/methods-of-communication/

You might also like