Hegwin.Me

Thoughts grows up by feeding itself with its own words.

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:

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