Практический пример: обновления в режиме реального времени в Stream Congress

Введение

Благодаря WebSockets и EventSource HTML5 позволяет разработчикам создавать веб-приложения, которые взаимодействуют с сервером в режиме реального времени. Stream Congress (доступен в Интернет-магазине Chrome ) предоставляет актуальные обновления о работе Конгресса США. Он транслирует обновления как Палаты представителей, так и Сената, актуальные новости, твиты членов Конгресса и другие обновления социальных сетей. Предполагается, что приложение будет оставаться открытым весь день, поскольку оно фиксирует дела Конгресса.

Начинаем с вебсокетов

Спецификация WebSockets привлекла немало внимания благодаря тому, что она обеспечивает: стабильный двунаправленный TCP-сокет между браузером и сервером. Для сокета TCP не существует какого-либо формата данных; разработчик может определить протокол обмена сообщениями. На практике передача объектов JSON в виде строк наиболее удобна. Клиентский код JavaScript для прослушивания текущих обновлений понятен и прост:

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

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

Хотя поддержка WebSockets браузерами проста, поддержка на стороне сервера все еще находится на стадии формирования. Socket.IO на Node.js предоставляет одно из наиболее зрелых и надежных серверных решений. Сервер, управляемый событиями, такой как Node.js, идеально подходит для WebSockets. Для альтернативных реализаций разработчики Python могут использовать Twisted и Tornado , а разработчики Ruby — EventMachine .

Представляем судорогу

Cramp — это асинхронная веб-инфраструктура Ruby, работающая поверх EventMachine. Его написал Пратик Найк , член основной команды Ruby on Rails. Cramp, предоставляющий доменно-ориентированный язык (DSL) для веб-приложений реального времени, является идеальным выбором для веб-разработчиков Ruby. Те, кто знаком с написанием контроллеров на Ruby on Rails, узнают стиль Крэмпа:

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 располагается поверх неблокирующего EventMachine, следует учитывать несколько моментов:

  • Необходимо использовать неблокирующие драйверы базы данных, такие как MySQLPlus и em-mongo .
  • Необходимо использовать веб-серверы, управляемые событиями. Встроена поддержка Thin и Rainbows .
  • Приложение Cramp необходимо запускать отдельно от основного приложения Rails, которое обеспечивает работу Stream Congress, перезапускать и контролировать независимо.

Текущие ограничения

WebSockets потерпел неудачу 8 декабря 2010 года, когда была опубликована информация об уязвимости безопасности. И Firefox, и Opera удалили поддержку WebSockets в браузерах. Хотя полифилов на чистом JavaScript не существует, существует запасной вариант Flash , который получил широкое распространение. Однако полагаться на Flash далеко не идеально. Несмотря на то, что Chrome и Safari продолжают поддерживать WebSockets, стало ясно, что для поддержки всех современных браузеров без использования Flash необходимо заменить WebSockets.

Откат к опросу AJAX

Было принято решение отказаться от WebSockets и вернуться к опросам AJAX «старой школы». Хотя опрос AJAX гораздо менее эффективен с точки зрения дискового и сетевого ввода-вывода, он упростил техническую реализацию Stream Congress. Самое главное, что отпала необходимость в отдельном приложении Cramp. Вместо этого конечная точка AJAX была предоставлена ​​приложением Rails. Код на стороне клиента был изменен для поддержки опроса 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

Важная проблема, которую следует учитывать при использовании EventSource, заключается в том, что междоменные соединения не допускаются. Это означает, что приложение Cramp должно обслуживаться из того же доменаstreamcongress.com, что и основное приложение Rails. Это можно сделать с помощью проксирования на веб-сервере. Если предположить, что приложение Cramp работает на Thin и работает на порту 8000, конфигурация Apache выглядит следующим образом:

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>

Эта конфигурация устанавливает конечную точку EventSource по streamcongress.com/live .

Стабильный полифилл

Одним из наиболее значительных преимуществ EventSource перед WebSockets является то, что резервное копирование полностью основано на JavaScript и не зависит от Flash. Полифил Реми Шарпа достигает этого, реализуя длинный опрос в браузерах, которые не поддерживают EventSource изначально. Таким образом, EventSource сегодня работает во всех современных браузерах с включенным JavaScript.

Заключение

HTML5 открывает двери для многих новых и интересных возможностей веб-разработки. Благодаря WebSockets и EventSource веб-разработчики теперь имеют чистые, четко определенные стандарты, позволяющие создавать веб-приложения в реальном времени. Но не все пользователи используют современные браузеры. При выборе внедрения этих технологий необходимо учитывать плавную деградацию. А инструменты на стороне сервера для WebSockets и EventSource все еще находятся на ранних стадиях. Важно учитывать эти факторы при разработке приложений HTML5, работающих в режиме реального времени.