Implement HTTP Long Polling in Rails with Action Controller Live
在Rails中实现HTTP Long Polling
About ActionController::Live
By using ActionController::Live
, we can implement real-time Push communication. The official documentation is here, but I always feel that this documentation is too abbreviated for learning.
The main techniques utilized here are:
When the HTTP/1.1 Header is specified as Transfer-Encoding: chunked, the data will be chunked and returned continuously
ActionController::Live sets the Response to Transfer-Encoding: chunked to return data
SSE (Server-Sent Events) means that the browser sends an HTTP request to the server, and then the server continuously pushes a "message" to the browser in one direction. The format of this message is simple: * "message" is prefixed with "data:" and ends with "\n\n" * .
Implement a Push notification
Let's construct a PusherController.
class PusherController < ApplicationController
# Introduce the module
include ActionController::Live
def stream
# Set the Content-Type of the response
response.headers['Content-Type'] = 'text/event-stream'
10.times do |i|
# Write the data, which is based on the formatting requirements of the SSE above
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
# Finally, disconnect the client
response.stream.close
end
end
Configure routing:
get 'stream' => 'pusher#stream'
In addition, since Rails has a default WEBrick in the development environment, which is not a multi-threaded Server, we need to use an app server that supports multi-threading, add Puma to the Gemfile, and bundle install
gem 'puma'
After starting the app with rails s
, use curl
on the command line to see the data being passed in line by line: ```bash
curl -i http://localhost:3000/stream
The server side is done, so how should the frontend be used? Let's do a simple example
Use EventSource to control the View display
Let's add an action named index
in PusherController:
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
Let's configure the routes:
get 'stream' => 'pusher#stream'
get 'pusher' => 'pusher#index'
Create a new 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 %>
Note: Here message
, done
are the types that you define when you write the stream in the controller.
At this point, if you visit http://localhost:3000/pusher
with your browser, you can see the data pushed from the server, as shown here:
Refs: