Studium przypadku – aktualizacje w czasie rzeczywistym na stronie Stream Congress

Luigi Montanez
Luigi Montanez

Wprowadzenie

Dzięki WebSockets i EventSource HTML5 umożliwia programistom tworzenie aplikacji internetowych, które komunikują się z serwerem w czasie rzeczywistym. Stream Congress (dostępny w Chrome Web Store) dostarcza na żywo aktualnych informacji o pracach Kongresu Stanów Zjednoczonych. Obejmuje ona transmisje z posiedzeń Izby Reprezentantów i Senatu, aktualne wiadomości, tweety członków Kongresu i inne informacje z mediów społecznościowych. Aplikacja ma być otwarta przez cały dzień, ponieważ rejestruje ona działania Kongresu.

Zacznij od WebSockets

Specyfikacja WebSockets zyskała sporo uwagi dzięki temu, co umożliwia: stabilny, dwukierunkowy gniazdo TCP między przeglądarką a serwerem. Na gnieździe TCP nie narzuca się formatu danych; programista może dowolnie zdefiniować protokół przesyłania komunikatów. W praktyce najwygodniej jest przekazywać obiekty JSON jako ciągi znaków. Kod JavaScript po stronie klienta, który nasłuchuje aktualizacji na żywo, jest przejrzysty i prosty:

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

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

Obsługa protokołu WebSockets w przeglądarce jest prosta, ale obsługa po stronie serwera jest jeszcze na etapie wstępnego rozwoju. Socket.IO w Node.js to jedno z najbardziej dopracowanych i wytrzymałych rozwiązań po stronie serwera. Serwer oparty na zdarzeniach, taki jak Node.js, jest odpowiedni dla WebSockets. W przypadku alternatywnych implementacji deweloperzy Pythona mogą używać TwistedTornado, a deweloperzy Ruby – EventMachine.

Przedstawiamy Cramp

Cramp to asynchroniczne środowisko internetowe Ruby, które działa na podstawie EventMachine. Jest ona autorstwa Pratika Naika, członka zespołu Ruby on Rails. Cramp to język do zastosowań w danej domenie (DSL) przeznaczony do tworzenia aplikacji internetowych działających w czasie rzeczywistym. Jest to idealny wybór dla deweloperów internetowych korzystających z języka Ruby. Osoby, które znają pisanie kontrolerów w Ruby on Rails, rozpoznają styl Cramp:

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

Ponieważ Cramp działa na podstawie nieblokującego modułu EventMachine, należy wziąć pod uwagę kilka kwestii:

  • Należy używać nieblokujących sterowników bazy danych, takich jak MySQLPlus i em-mongo.
  • Należy używać serwerów WWW opartych na zdarzeniach. Wbudowana obsługa Thin i Rainbows.
  • Aplikacja Cramp musi być uruchamiana oddzielnie od głównej aplikacji Rails, która obsługuje Stream Congress, a także musi być restartowana i monitorowana niezależnie.

Obecne ograniczenia

8 grudnia 2010 r. WebSockets odnotował spadek popularności, gdy ujawniono lukę w zabezpieczeniach. Zarówno Firefox, jak i Opera usunęli obsługę technologii WebSockets w przeglądarkach. Nie ma czystego kodu polyfill JavaScript, ale istnieje powszechnie stosowana kreacja zastępcza Flash. Używanie Flasha nie jest jednak idealnym rozwiązaniem. Choć Chrome i Safari nadal będą obsługiwać protokół WebSockets, stało się jasne, że aby obsługiwać wszystkie nowoczesne przeglądarki bez konieczności stosowania Flasha, trzeba będzie wymienić obsługę WebSockets.

Przywracanie sondowania AJAX

Podjęliśmy decyzję o porzuceniu WebSockets i powrocie do „starej szkoły” pollingu AJAX. Chociaż polling AJAX jest znacznie mniej wydajny z perspektywy wejścia/wyjścia dysku i sieci, uprościł techniczną implementację Stream Congress. Najważniejsze jest to, że nie trzeba już używać osobnej aplikacji Cramp. Punkt końcowy AJAX został udostępniony przez aplikację Rails. Kod po stronie klienta został zmodyfikowany pod kątem obsługi odpytywania jQuery w technologii 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

Ważną kwestią, o której należy pamiętać w przypadku parametru EventSource, jest to, że połączenia między domenami są niedozwolone. Oznacza to, że aplikacja Cramp musi być obsługiwana z tej samej domeny streamcongress.com, co główna aplikacja Rails. Można to osiągnąć za pomocą serwera proxy na serwerze WWW. Przy założeniu, że aplikacja Cramp jest obsługiwana przez technologię Thin 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.

Stabilna wersja substytutu

Jedną z największych zalet EventSource w porównaniu z WebSockets jest to, że tryb awaryjny jest całkowicie oparty na JavaScript i nie zależy od Flasha. Rozwiązanie polyfill autorstwa Remy’ego Sharpa osiąga to za pomocą implementacji długiego pollingu w przeglądarkach, które nie obsługują natywnie interfejsu EventSource. Obecnie EventSource działa we wszystkich nowoczesnych przeglądarkach z włączonym JavaScriptem.

Podsumowanie

HTML5 otwiera wiele nowych i ciekawych możliwości tworzenia stron internetowych. Dzięki WebSockets i EventSource deweloperzy mają teraz czyste, dobrze zdefiniowane standardy, które umożliwiają tworzenie aplikacji internetowych w czasie rzeczywistym. Jednak nie wszyscy użytkownicy korzystają z nowoczesnych przeglądarek. Przy wyborze tych technologii należy wziąć pod uwagę łagodne przejście na tryb offline. Narzędzia na serwerze do obsługi WebSockets i EventSource są jeszcze w fazie testów. Pamiętaj o tych czynnikach podczas tworzenia aplikacji HTML5 w czasie rzeczywistym.