在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: