Örnek Olay - Akış Kongresi'nde Gerçek Zamanlı Güncellemeler

Luigi Montanez
Luigi Montanez

Giriş

HTML5, WebSockets ve EventSource aracılığıyla geliştiricilerin bir sunucuyla gerçek zamanlı olarak iletişim kuran web uygulamaları oluşturmasını sağlar. Stream Congress (Chrome Web Mağazası'nda mevcuttur), ABD Kongresi'nin işleyişiyle ilgili canlı güncellemeler sağlar. Hem Temsilciler Meclisi hem de Senato'dan gelen güncellemeler, alakalı haberler, Kongre üyelerinin tweet'leri ve diğer sosyal medya güncellemeleri bu kanalda yayınlanır. Uygulama, Kongre'nin çalışmalarını yakaladığı için tüm gün açık bırakılmalıdır.

WebSocket'lerle başlama

WebSockets spesifikasyonu, sunduğu olanak nedeniyle oldukça dikkat çekmiştir: tarayıcı ile sunucu arasında kararlı, iki yönlü bir TCP soketi. TCP soketine zorunlu kılınan bir veri biçimi yoktur. Geliştirici, mesajlaşma protokolünü tanımlamakta özgürdür. Uygulamada, JSON nesnelerini dize olarak iletmek en uygun yöntemdir. Canlı güncellemeleri dinlemek için istemci tarafı JavaScript kodu temiz ve basittir:

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

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

WebSocket'ler için tarayıcı desteği basit olsa da sunucu tarafı desteği henüz oluşum aşamasındadır. Node.js'deki Socket.IO, en gelişmiş ve sağlam sunucu tarafı çözümlerinden birini sunar. Node.js gibi etkinliğe dayalı bir sunucu, WebSocket'ler için idealdir. Alternatif uygulamalar için Python geliştiricileri Twisted ve Tornado'yu kullanabilirken Ruby geliştiricileri EventMachine'i kullanabilir.

Cramp ile tanışın

Cramp, EventMachine'in üzerinde çalışan eşzamansız bir Ruby web çerçevesidir. Bu makale, Ruby on Rails çekirdek ekibinin bir üyesi olan Pratik Naik tarafından yazılmıştır. Gerçek zamanlı web uygulamaları için alana özgü bir dil (DSL) sunan Cramp, Ruby web geliştiricileri için ideal bir seçimdir. Ruby on Rails'de kontrolör yazma konusunda bilgi sahibi olanlar Cramp'ın tarzını tanıyacaktır:

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, engellenmeyen EventMachine'in üzerinde yer aldığından dikkate alınması gereken birkaç nokta vardır:

  • MySQLPlus ve em-mongo gibi engellenmeyen veritabanı sürücüleri kullanılmalıdır.
  • Etkinliğe dayalı web sunucuları kullanılmalıdır. İnce ve Yağmur Damlaları için yerleşik destek mevcuttur.
  • Cramp uygulaması, Stream Congress'i destekleyen ana Rails uygulamasından ayrı olarak çalıştırılmalı, bağımsız olarak yeniden başlatılmalı ve izlenmelidir.

Mevcut Sınırlılıklar

WebSockets, 8 Aralık 2010'da bir güvenlik açığının kamuoyuna açıklanmasıyla gerileme yaşadı. Hem Firefox hem de Opera, WebSocket'ler için tarayıcı desteğini kaldırdı. Saf JavaScript polyfill'i bulunmasa da yaygın olarak kullanılan bir Flash yedeği vardır. Ancak Flash'a güvenmek ideal olmaktan uzaktır. Chrome ve Safari, WebSocket'leri desteklemeye devam etse de tüm modern tarayıcıları Flash'a gerek kalmadan desteklemek için WebSocket'lerin değiştirilmesi gerektiği anlaşıldı.

AJAX anketine geri dönme

WebSocket'lerden "eski usul" AJAX anketine geçme kararı alındı. Disk ve ağ G/Ç açısından çok daha az verimli olsa da AJAX anketi, Stream Congress'in teknik uygulamasını basitleştirdi. En önemlisi, ayrı bir Cramp uygulamasına ihtiyaç kalmadı. Bunun yerine AJAX uç noktası Rails uygulaması tarafından sağlandı. İstemci tarafı kod, jQuery AJAX anketini desteklemek için değiştirildi:

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 ile ilgili göz önünde bulundurulması gereken önemli bir nokta, alan adı arası bağlantılara izin verilmemesidir. Bu, Cramp uygulamasının ana Rails uygulamasıyla aynı streamcongress.com alanından sunulması gerektiği anlamına gelir. Bu, web sunucusunda proxy ile yapılabilir. Cramp uygulamasının Thin tarafından desteklendiğini ve 8000 numaralı bağlantı noktasında çalıştığını varsayarsak Apache yapılandırması şu şekilde görünür:

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>

Bu yapılandırma, streamcongress.com/live adresinde bir EventSource uç noktası belirler.

Kararlı Çoklu Dolgular

EventSource'ın WebSocket'lere kıyasla en önemli avantajlarından biri, yedek çözümün tamamen JavaScript tabanlı olması ve Flash'a bağımlı olmamasıdır. Remy Sharp'ın polyfill'i, EventSource'ı doğal olarak desteklemeyen tarayıcılarda uzun süreli sorgu uygulayarak bunu gerçekleştirir. Bu nedenle EventSource, JavaScript'in etkin olduğu tüm modern tarayıcılarda çalışır.

Sonuç

HTML5, birçok yeni ve heyecan verici web geliştirme olanağının kapısını aralıyor. WebSockets ve EventSource ile web geliştiricileri artık gerçek zamanlı web uygulamalarını etkinleştirmek için net ve iyi tanımlanmış standartlara sahip. Ancak tüm kullanıcılar modern tarayıcılar kullanmaz. Bu teknolojileri uygulamayı seçerken sorunsuz düşüş dikkate alınmalıdır. WebSocket ve EventSource için sunucu tarafındaki araçlar henüz ilk aşamalardadır. Gerçek zamanlı HTML5 uygulamaları geliştirirken bu faktörleri göz önünde bulundurmanız önemlidir.