Studium przypadku – aktualizacje w czasie rzeczywistym na stronie Stream Congress

Luigi Montanez
Luigi Montanez

Wstęp

Dzięki protokołom WebSockets i EventSource język HTML5 umożliwia programistom tworzenie aplikacji internetowych komunikujących się z serwerem w czasie rzeczywistym. Stream Congress (dostępny w Chrome Web Store) zapewnia bieżące informacje o działaniach Kongresu Stanów Zjednoczonych. Udostępnia on aktualne informacje z wyboru Izby Reprezentantów i Senatu, ważne wiadomości, tweety posła i inne informacje z mediów społecznościowych. Aplikacja powinna być otwarta przez cały dzień, ponieważ przedstawia działalność Kongresu.

Zaczynamy od WebSockets

Specyfikacja WebSockets cieszy się dużym zainteresowaniem ze względu na to, co umożliwia: stabilne, dwukierunkowe gniazdo TCP między przeglądarką a serwerem. Na gniazdo TCP nie są nałożone żadne formaty danych. Deweloper ma prawo definiować protokół przesyłania wiadomości. W praktyce najwygodniej jest przekazać obiekty JSON w postaci ciągów tekstowych. Kod JavaScript po stronie klienta służący do nasłuchiwania aktualizacji na żywo jest prosty i przejrzysty:

var liveSocket = new WebSocket("ws://streamcongress.com:8080/live");

liveSocket.onmessage = function (payload) {
  addToStream(JSON.parse(payload.data).reverse());
};

Obsługa WebSockets przez przeglądarki jest prosta, jednak obsługa po stronie serwera wciąż znajduje się w fazie formatowania. Środowisko Socket.IO w środowisku Node.js jest jednym z najbardziej zaawansowanych i najbardziej niezawodnych rozwiązań po stronie serwera. WebSockets nadaje się do pracy z serwerem opartym na zdarzeniach, takim jak Node.js. Deweloperzy Pythona mogą użyć tagów Twisted i Tornado, a programiści w języku Ruby – EventMachine.

Przedstawiamy Cramp

Cramp to asynchroniczna platforma internetowa Ruby, która działa na platformie EventMachine. Jej autorem jest Pratik Naik, członek podstawowego zespołu Ruby on Rails. Cramp to idealne rozwiązanie dla programistów internetowych w języku Ruby, które zapewnia język specyficzny dla domeny (DSL) dla aplikacji internetowych w czasie rzeczywistym. Ci, którzy znają kontrolery do pisania w grze Ruby on Rails, rozpoznają styl Crampa:

require "rubygems"
require "bundler"
Bundler.require
require 'cramp'
require 'http_router'
require 'active_support/json'
require 'thin'

Cramp::Websocket.backend = :thin

class LiveSocket < Cramp::Websocket
periodic_timer :check_activities, :every => 15

def check_activities
    @latest_activity ||= nil
    new_activities = find_activities_since(@latest_activity)
    @latest_activity = new_activities.first unless new_activities.empty?
    render new_activities.to_json
end
end

routes = HttpRouter.new do
add('/live').to(LiveSocket)
end
run routes

Cramp działa na zasadzie nieblokowania EventMachine, więc trzeba pamiętać o kilku kwestiach:

  • Należy używać nieblokujących sterowników baz danych, takich jak MySQLPlus czy em-mongo.
  • Należy używać serwerów WWW opartych na zdarzeniach. Wbudowana jest obsługa cienkich i tęczowych.
  • Aplikacja Cramp musi być uruchomiona niezależnie od głównej aplikacji Rails, która obsługuje Stream Congress, oraz być uruchamiana ponownie i monitorowana niezależnie.

Obecne ograniczenia

8 grudnia 2010 roku system WebSockets był nieskuteczny, gdy ogłoszono lukę w zabezpieczeniach. Zarówno przeglądarka Firefox, jak i Opera usunęła obsługę protokołu WebSockets w przeglądarkach. Chociaż nie istnieje czyste elementy polyfill w języku JavaScript, istnieje powszechnie stosowana zastępcza kreacja Flash. Jednak poleganie na technologii Flash nie jest najlepszym wyborem. Mimo że Chrome i Safari nadal obsługują WebSockets, stało się jasne, że aby obsługiwać wszystkie nowoczesne przeglądarki bez konieczności korzystania z Flasha, konieczne będzie zastąpienie WebSockets.

Powrót do odpytywania AJAX

Podjęliśmy decyzję, aby odejść od WebSockets i wrócić do „starych” ankiet AJAX. Choć operacje wejścia-wyjścia dla dysku i sieci są znacznie mniej wydajne, odpytywanie AJAX uprościło techniczne wdrożenie Stream Congress. Przede wszystkim wyeliminowano konieczność korzystania z osobnej aplikacji Cramp. Punkt końcowy AJAX został udostępniony przez aplikację Rails. Kod po stronie klienta został zmodyfikowany tak, aby obsługiwał sondowanie jQuery AJAX:

var fillStream = function(mostRecentActivity) {
  $.getJSON(requestURL, function(data) {
    addToStream(data.reverse());

    setTimeout(function() {
      fillStream(recentActivities.last());
    }, 15000);
  });
};

AJAX polling, though, is not without its downsides. Relying on the HTTP request/response cycle means that the server sees constant load even when there aren't any new updates. And of course, AJAX polling doesn't take advantage of what HTML5 has to offer.

## EventSource: The right tool for the job

Up to this point, a key factor was ignored about the nature of Stream Congress: the app only needs to stream updates one way, from server to client - downstream. It didn't need to be real-time, upstream client-to-server communication. 

In this sense, WebSockets is overkill for Stream Congress. Server-to-client communication is so common that it's been given a general term: push. In fact, many existing solutions for WebSockets, from the hosted [PusherApp](http://pusherapp.com) to the Rails library [Socky](https://github.com/socky), optimize for push and don't support client-to-server communication at all.

Enter EventSource, also called Server-Sent Events. The specification compares favorably to WebSockets in the context to server to client push:

- A similar, simple JavaScript API on the browser side.
- The open connection is HTTP-based, not dropping to the low level of TCP.
- Automatic reconnection when the connection is closed.

### Going Back to Cramp

In recent months, Cramp has added support for EventSource. The code is very similar to the WebSockets implementation:

```ruby
class LiveEvents < Cramp::Action
self.transport = :sse

periodic_timer :latest, :every => 15

def latest
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end

routes = HttpRouter.new do
add('/').to(LiveEvents)
end
run routes

Istotnym problemem, o którym należy pamiętać w przypadku EventSource, jest to, że połączenia między domenami nie są dozwolone. Oznacza to, że aplikacja Cramp musi być udostępniana z tej samej domeny streamcongress.com co główna aplikacja Rails. Można to zrobić, korzystając z serwera proxy na serwerze WWW. Zakładając, że aplikacja Cramp jest obsługiwana w środowisku cienkim i działa na porcie 8000, konfiguracja Apache wygląda tak:

LoadModule  proxy_module             /usr/lib/apache2/modules/mod_proxy.so
LoadModule  proxy_http_module        /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule  proxy_balancer_module    /usr/lib/apache2/modules/mod_proxy_balancer.so

<VirtualHost *:80>
  ServerName streamcongress.com
  DocumentRoot /projects/streamcongress/www/current/public
  RailsEnv production
  RackEnv production

  <Directory /projects/streamcongress/www/current/public>
    Order allow,deny
    Allow from all
    Options -MultiViews
  </Directory>

  <Proxy balancer://thin>
    BalancerMember http://localhost:8000
  </Proxy>

  ProxyPass /live balancer://thin/
  ProxyPassReverse /live balancer://thin/
  ProxyPreserveHost on
</VirtualHost>

Ta konfiguracja ustawia punkt końcowy EventSource na streamcongress.com/live.

Stabilny

Jedną z najważniejszych zalet EventSource w porównaniu z WebSockets jest to, że kreacje zastępcze działają całkowicie w języku JavaScript i nie są uzależnione od Flasha. Jest to możliwe dzięki zastosowaniu przez Remy’ego Sharpa polyfill z wykorzystaniem długiego pola wyboru w przeglądarkach, które nie obsługują natywnie obsługi EventSource. Dlatego EventSource działa obecnie we wszystkich nowoczesnych przeglądarkach z włączoną obsługą JavaScriptu.

Podsumowanie

HTML5 otwiera przed Tobą wiele nowych, ciekawych możliwości tworzenia stron internetowych. Dzięki technologii WebSockets i EventSource twórcy stron internetowych mają teraz przejrzyste, dobrze zdefiniowane standardy umożliwiające działanie aplikacji internetowych w czasie rzeczywistym. Jednak nie wszyscy użytkownicy korzystają z nowoczesnych przeglądarek. Decydując się na wdrożenie tych technologii, należy wziąć pod uwagę pogorszenie wyników bez utraty danych. Natomiast tworzenie narzędzi po stronie serwera dla WebSockets i EventSource jest wciąż na wczesnym etapie. Warto pamiętać o tych czynnikach podczas tworzenia aplikacji HTML5 w czasie rzeczywistym.