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: