Hegwin.Me

无边落木萧萧下,不尽长江滚滚来。

在Rails中实现HTTP Long Polling

Implement HTTP Long Polling in Rails with Action Controller Live

关于ActionController::Live

通过使用 ActionController::Live,我们可以实现实时的Push通信。官方文档在这里,但总觉得学习起来,这个文档太简略。

这里面主要是利用了这几个技术:

  • 当HTTP/1.1 的Header指定为 Transfer-Encoding: chunked 时,数据将会被切分为多块、然后持续地被返回

  • ActionController::Live将Response设定为 Transfer-Encoding: chunked 返回数据

  • SSE(Server-Sent Events) 即浏览器向服务器发送一个HTTP请求,然后服务器不断单向地向浏览器推送“信息”(message)。这种信息在格式上很简单,就是 “信息”加上前缀“data: ”,然后以“\n\n”结尾

实现一个Push通知

我们来构造一个PusherController:

class PusherController < ApplicationController
  # 引入模块
  include ActionController::Live

  def stream
    # 设定Responst的Content-Type
    response.headers['Content-Type'] = 'text/event-stream'
    10.times do |i|
      # 写入数据,数据的写法是基于上述SSE的格式要求
      response.stream.write "event: message\n"
      response.stream.write "data: Hello world ##{i+1}\n\n"
      sleep 1
    end

    response.stream.write "event: done\n"
    response.stream.write "data: Done\n\n"
  rescue IOError
    # client disconneted
  ensure
    # 最后,切断client的连接
    response.stream.close
  end
end

配置路由:

  get 'stream' => 'pusher#stream'

此外,由于Rails在开发环境下默认的WEBrick,其并非多线程的Server,我们需要使用一个支持多线程的app server,在Gemfile中加入Puma,然后 bundle install

gem 'puma'

rails s启动app之后,在命令行使用curl就能看到数据被一行行传过来了:

curl -i http://localhost:3000/stream

服务端这边已经没有问题了,那前台应该如何使用呢?我们来做一个简单的例子

利用EventSource来控制View显示

我们在PusherController里添加一个名为index的action:

class PusherController < ApplicationController
  include ActionController::Live

  def index
  end

  def stream
    response.headers['Content-Type'] = 'text/event-stream'
    10.times do |i|
      response.stream.write "event: message\n"
      response.stream.write "data: Hello world ##{i+1}\n\n"
      sleep 1
    end

    response.stream.write "event: done\n"
    response.stream.write "data: Done\n\n"
  ensure
    response.stream.close
  end
end

再配置路由:

  get 'stream' => 'pusher#stream'
  get 'pusher' => 'pusher#index'

新建一个view: app/views/pusher/index.html.erb

<h1>Pusher#index</h1>

<div id="result">
</div>

<%= javascript_tag do %>
  $(function() {
    var eventSource = new EventSource("/stream");

    eventSource.addEventListener("message", function(event) {
      $("#result").text(event.data);
    });
    eventSource.addEventListener("done", function(event) {
      this.close();
      $("#result").text(event.data);
    });
  });
<% end %>

注:这里的message, done是在controller中写入stream时,自己定义的类型。

此时,用浏览器访问 http://localhost:3000/pusher 就能看到从服务端推送过来的数据啦,效果如图:

Refs:

  1. SSE:服务器发送事件
  2. ActionController::LiveとServer-Sent Events で地図上にじわじわ表示する
< Back