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 xây dựng các ứng dụng web giao tiếp theo thời gian thực với máy chủ. Stream Congress (có trong 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ỳ. Kênh này phát trực tuyến thông tin cập nhật về hoạt động trên sàn của cả Hạ viện và Thượng viện, tin tức cập nhật có liên quan, tweet của các thành viên Quốc hội và thông tin cập nhật khác trên mạng xã hội. Ứng dụng này sẽ được để mở cả ngày vì ứng dụng ghi lại hoạt động kinh doanh của Quốc hội.

Bắt đầu với WebSocket

Thông số kỹ thuật WebSockets đã nhận được khá nhiều sự chú ý nhờ những gì nó mang lại: một ổ 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 được áp dụng cho ổ cắm TCP; nhà phát triển có thể tự do xác định giao thức nhắn tin. 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 các bản cập nhật trực tiếp rất rõ rà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ù tính năng hỗ trợ trình duyệt cho WebSockets rất đơn giản, nhưng tính năng hỗ trợ phía máy chủ vẫn đang trong giai đoạn hình thành. 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ủ do sự kiện điều khiển như Node.js là phù hợp với WebSocket. Đối với các phương thức triển khai 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 về Cramp

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

Vì Cramp nằm trên EventMachine không chặn, nên bạn cần lưu ý một số điều 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.
  • Phải sử dụng máy chủ web do sự kiện điều khiển. Hỗ trợ tích hợp sẵn cho MỏngCầu vồng.
  • Ứng dụng Cramp phải chạy riêng biệt với ứng dụng Rails chính cung cấp năng lượng cho Stream Congress, khởi động lại và theo dõi độc lập.

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

WebSockets gặp phải một 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 bố. Cả Firefox và Opera đều đã xoá tính năng hỗ trợ trình duyệt cho WebSocket. Mặc dù không có đoạn mã polyfill JavaScript thuần tuý nào, nhưng có một tính năng dự phòng Flash được sử dụng rộng rãi. Tuy nhiên, việc dựa vào Flash là điều không lý tưởng. Mặc dù Chrome và Safari tiếp tục hỗ trợ WebSocket, nhưng rõ ràng là để hỗ trợ tất cả trình duyệt hiện đại mà không cần dựa vào Flash, WebSocket sẽ cần được thay thế.

Quay lại tính năng thăm dò ý kiến AJAX

Chúng tôi đã quyết định chuyển từ WebSocket sang phương thức thăm dò ý kiến AJAX "cũ". Mặc dù kém hiệu quả hơn nhiều từ góc độ I/O của ổ đĩa và mạng, nhưng tính năng thăm dò ý kiến AJAX đã đơn giản hoá việc triển khai kỹ thuật của tính năng Hội nghị phát trực tuyến. Đáng kể nhất là nhu cầu cần một ứng dụng Cramp riêng biệt đã bị loại bỏ. Thay vào đó, điểm cuối AJAX do ứng dụng Rails cung cấp. Mã phía máy khách đã được sửa đổi để hỗ trợ thăm dò AJAX của jQuery:

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. Điều này có nghĩa 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 sử dụng proxy tại máy chủ web. Giả sử ứng dụng Cramp được cung cấp bởi Thin 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 lợi thế đáng kể nhất của EventSource so với WebSocket là phương thức 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 thực hiện việc này bằng cách triển khai tính năng thăm dò ý kiến dài hạn trong các trình duyệt không hỗ trợ EventSource. Do đó, EventSource hiện hoạt động trên tất cả trình duyệt hiện đại có bật JavaScript.

Kết luận

HTML5 mở ra nhiều khả năng mới và thú vị cho việc phát triển web. 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, rõ ràng để hỗ trợ các ứng dụng web theo thời gian thực. Tuy nhiên, không phải người dùng nào cũng 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ụ phía máy chủ cho WebSockets và EventSource vẫn đang ở giai đoạn đầu. Bạn cần lưu ý những yếu tố này khi phát triển ứng dụng HTML5 theo thời gian thực.