Server Sent Event in Rails

Dạo đầu

Ngày ni tui học về cách nhận data từ server mà không cần reload web page

Một vài cách mà tui nghe giang hồ đồn:

  • HTTP long-polling: Client gửi 1 request đến server và chờ sau một khoảng nhất định hoặc đến khi nhận được phản hồi và đóng lại.
  • Webhooks: Khi nào server cập nhật thì nó sẽ request đến client, nhưng yêu cầu client phải chạy http server
  • Server-Sent Events: Sau request đầu tiền từ client đến server thì kết nối 1 chiều được duy trì, khi nào server có gì mới sẽ đẩy về cho client (Xem thêm ở đây)
  • WebSockets: Cũng tương tự như Server-Sent Events nhưng duy trì kết nối 2 chiều qua lại client <=> server

Tùy yêu cầu từng ứng dụng để chọn phương pháp hợp lí. Ví dụ làm web cập nhật thời tiết theo thời gian thực thì client chỉ cần nhận dữ liệu từ server trong trường hợp ni tui dùng SSE, còn web chat gì đó vừa gửi request, vừa nhận data thì tui dùng WebSockets

Vô bài!!!

Server

Controller

class CommentsController < ApplicationController
  include ActionController::Live

  def progress
    sse = SSE.new(response.stream)
    response.headers['Content-Type'] = 'text/event-stream'
    5.times do |i|
      sse.write({count: i}.to_json)
    end
  rescue IOError
    # code
  ensure
    sse.close
  end
end

Và Route

get 'comments/progress', to: 'comments#progress'

Client

Tui dùng javascript để xử lí data server gửi về

var div = document.getElementById('divID');
var sse = new EventSource('/comments/progress');
sse.addEventListener('message', function (e) {
  div.innerHTML += e.data;
});

Thành quả

Để rõ hơn, bật devtool lên, xong vô network tab > EventStream tui chộ có 1 kết nối tên là process hiển thị những data server gửi về

Dùng redis để cool ngầu hơn

Trong Create action tui lưu những comment vào channel có tên là comment:progress. Sau đó ở process action, bằng cách subcribe channel comment:progress để lấy ra khi có thằng comment vừa được lưu vào

class CommentsController < ApplicationController
  include ActionController::Live

  def progress
    redis = Redis.new
    sse   = SSE.new(response.stream)
    response.headers['Content-Type'] = 'text/event-stream'
    redis.subscribe('comment:progress') do |on|
      on.message { |e, data| sse.write(data) }
    end
  rescue IOError
      # code
  ensure
    redis.quit
    sse.close
  end
  
  def create
    redis = Redis.new
    redis.publish('comment:progress', {count: i}.to_json)
  end
end

Như rứa là tui đã setup xong SSE cơ bản rồi


All Rights Reserved