Nghiên cứu điển hình – Thông tin cập nhật theo thời gian thực trong Stream lễ hội

Luigi Montanez
Luigi Montanez

Giới thiệu

Thông qua WebSocketsEventSource, HTML5 cho phép nhà phát triển tạo các ứng dụng web giao tiếp theo thời gian thực với máy chủ. Phát trực tuyến sự kiện (có trên Cửa hàng Chrome trực tuyến) cung cấp thông tin cập nhật trực tiếp về hoạt động của Quốc hội Hoa Kỳ. Ứng dụng này phát trực tuyến tin cập nhật từ cả Hạ viện và Thượng viện, tin tức cập nhật liên quan, bài đăng của các thành viên Quốc hội và các nội dung cập nhật khác trên mạng xã hội. Ứng dụng này dự định để mở cả ngày vì nó ghi lại hoạt động kinh doanh của Quốc hội.

Bắt đầu bằng WebSockets

Thông số kỹ thuật WebSockets nhận được khá nhiều sự chú ý về những gì nó cho phép: ổ cắm TCP ổn định, hai chiều giữa trình duyệt và máy chủ. Không có định dạng dữ liệu nào áp dụng cho cổng TCP; nhà phát triển có thể tuỳ ý xác định giao thức thông báo. Trong thực tế, việc truyền các đối tượng JSON xung quanh dưới dạng chuỗi là thuận tiện nhất. Mã JavaScript phía máy khách để theo dõi thông tin cập nhật trực tiếp rất gọn gàng và đơn giản:

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

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

Mặc dù hỗ trợ trình duyệt cho WebSockets rất đơn giản, hỗ trợ phía máy chủ vẫn đang ở giai đoạn định dạng. Socket.IO trên Node.js cung cấp một trong những giải pháp phía máy chủ hoàn thiện và mạnh mẽ nhất. Máy chủ dựa trên sự kiện như Node.js là lựa chọn phù hợp cho WebSockets. Để triển khai các giải pháp thay thế, nhà phát triển Python có thể sử dụng TwistedTornado, còn nhà phát triển Ruby có EventMachine.

Giới thiệu Cramp

Cramp là một khung web Ruby không đồng bộ chạy trên EventMachine. Tác phẩm của Pratik Naik, một thành viên của nhóm cốt lõi Ruby on Rails. Cung cấp một ngôn ngữ đặc thù của miền (DSL) cho các ứng dụng web theo thời gian thực, Cramp là một lựa chọn lý tưởng cho các nhà phát triển web của Ruby. Những người quen với bộ điều khiển viết trong Ruby on Rails sẽ nhận ra phong cách của 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

Do Cramp nằm trên EventMachine không tuần tự, nên bạn cần lưu ý một số điểm sau:

  • Bạn phải sử dụng trình điều khiển cơ sở dữ liệu không chặn, chẳng hạn như MySQLPlusem-mongo.
  • Bạn phải sử dụng máy chủ web hướng sự kiện. Tính năng hỗ trợ được tích hợp sẵn cho Thincầu vồng.
  • Ứng dụng Cramp phải được chạy riêng biệt với ứng dụng Rails chính chạy hệ điều hành này. Ứng dụng này phải được khởi động lại và giám sát độc lập.

Các hạn chế hiện tại

WebSockets gặp phải sự cố vào ngày 8 tháng 12 năm 2010 khi một lỗ hổng bảo mật được công khai. Cả Firefox và Opera đều không hỗ trợ trình duyệt cho WebSockets. Mặc dù không có polyfill JavaScript thuần túy nào tồn tại, nhưng có một bản dự phòng Flash được sử dụng rộng rãi. Tuy nhiên, việc dựa vào Flash là không lý tưởng. Mặc dù Chrome và Safari tiếp tục hỗ trợ WebSockets, nhưng rõ ràng là cần phải thay thế WebSocket để hỗ trợ tất cả các trình duyệt hiện đại mà không cần dựa vào Flash.

Quay lại cuộc thăm dò ý kiến AJAX

Quyết định này được đưa ra khi ngừng sử dụng WebSockets và quay lại cuộc thăm dò ý kiến AJAX "kiểu cũ". Mặc dù ít hiệu quả hơn nhiều từ góc nhìn của I/O ổ đĩa và mạng, nhưng cuộc thăm dò ý kiến AJAX đã đơn giản hoá việc triển khai kỹ thuật cho Stream lễ hội. Quan trọng nhất là bạn không còn cần đến một ứng dụng Cramp riêng biệt. Thay vào đó, ứng dụng Rails cung cấp điểm cuối AJAX. Mã phía máy khách đã được sửa đổi để hỗ trợ thăm dò 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

Một vấn đề quan trọng cần lưu ý với EventSource là kết nối nhiều miền không được cho phép. Tức là ứng dụng Cramp phải được phân phát từ cùng một miền streamcongress.com với ứng dụng Rails chính. Bạn có thể thực hiện việc này bằng cách thực hiện việc proxy trên máy chủ web. Giả sử ứng dụng Cramp được Thin cung cấp và chạy trên cổng 8000, cấu hình Apache sẽ có dạng như sau:

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>

Cấu hình này đặt điểm cuối EventSource tại streamcongress.com/live.

Polyfill ổn định

Một trong những ưu điểm quan trọng nhất của EventSource so với WebSockets là dự phòng hoàn toàn dựa trên JavaScript, không phụ thuộc vào Flash. polyfill của Remy Sharp hoàn thành việc này bằng cách triển khai cuộc thăm dò dài trong các trình duyệt không hỗ trợ EventSource vốn có. Do đó, hiện nay EventSource hoạt động trên tất cả các trình duyệt hiện đại có bật JavaScript.

Kết luận

HTML5 mở ra cơ hội cho nhiều khả năng phát triển web mới và thú vị. Với WebSockets và EventSource, các nhà phát triển web hiện có các tiêu chuẩn rõ ràng và được xác định rõ ràng để hỗ trợ các ứng dụng web theo thời gian thực. Tuy nhiên, không phải tất cả người dùng đều sử dụng trình duyệt hiện đại. xuống cấp nhẹ phải được xem xét khi chọn triển khai các công nghệ này. Ngoài ra, công cụ về phía máy chủ cho WebSockets và EventSource vẫn đang ở giai đoạn đầu. Điều quan trọng là phải ghi nhớ những yếu tố này khi phát triển ứng dụng HTML5 theo thời gian thực.