Server Send Event
As for web application grew over the past few years, the need for real time data update has been increased. Web apps we use every day rely on real-time features—the sort of features that let you see new posts magically appearing at the top of your feeds without having to lift a finger. Polling ...
As for web application grew over the past few years, the need for real time data update has been increased. Web apps we use every day rely on real-time features—the sort of features that let you see new posts magically appearing at the top of your feeds without having to lift a finger.
Polling is a traditional technique used by the vast majority of AJAX applications. The basic idea is that the application repeatedly polls a server for data. If you're familiar with the HTTP protocol, you know that fetching data revolves around a request/response format. The client makes a request and waits for the server to respond with data. If none is available, an empty response is returned. So what's the big deal with polling? Extra polling creates greater HTTP overhead. WebSockets is another technology, it built on top of TCP protocol. It hold the connection to the server open so that the server can send information to the client, even in the absence of a request from the client. WebSockets allow for bi-directional, "full-duplex" communication between the client and the server by creating a persistent connection between the two. Having a two-way channel is more attractive for things like games, messaging apps, and for cases where you need near real-time updates in both directions. However, in some scenarios data doesn't need to be sent from the client. You simply need updates from some server action that is where Server Send Event comes in. SSEs are sent over traditional HTTP. That means they do not require a special protocol or server implementation to get working.
We are going to implement a simple chat application using SSEs which make use of Rails ActionController::Live module to stream data and Redis PubSub to publish data when user send new message.
Server Side
class ChatsController < ApplicationController include ActionController::Live def index end def new response.headers['Content-Type'] = 'text/event-stream' client = Redis.new sse = SSE.new(response.stream) client.subscribe 'chat' do |on| on.message do |channel, message| sse.write(message, event: 'message') end end rescue IOError Rails.logger.info('Connection close') ensure sse.close client.quit end def create client = Redis.new client.publish('chat', { user: params[:user], message: params[:message]}.to_json) client.quit render nothing: true, status: 200 end end
To make this controller streamable we need to include ActionController::Live and set response header to content type to text/event-stream. We can now use stream object to send out data to client. In order for client to receive updated message we subcribe each client to chat channel and when the new message comes in we publish the message to this channel. SSE is just a wrap around stream object here is its source code
class SSE def initialize(io) @io = io end def write(message, options = {}) options.each do |k, v| @io.write("#{k}: #{v} ") end @io.write("data: #{message} ") end def close @io.close end end
Messages are delimited by two newlines. The data field is the event’s payload. Next, update your routes.rb to point at the new controller
Rails.application.routes.draw do resources :chats end
Client Side
$(document).on 'ready page:load', -> name = ' chatbox = $('#chatbox') source = new EventSource('/chats/new') source.addEventListener 'message', (e) -> appendMessage(e.data) $('#chat').hide(); $('#btn-register').on 'click', (e) -> e.preventDefault() name = $('#name').val() $('#register').hide() $('#chat').show(); $('#btn').on 'click', (e) -> e.preventDefault() textBox = $('#message') message = textBox.val() textBox.val(') $.ajax({ url: '/chats', method: 'POST', data: { user: name, message: message } }) appendMessage = (data) -> object = JSON.parse(data) node = document.createElement('div') node.className = 'chat-message' node.innerHTML = "#{object.user}: #{object.message}" chatbox.append(node)
The point to note here is we create a new EventSource object and passed it a url to ChatsController#new, this result in subscribe client to receive data from server. Next we listen to message event and append new message into DOM. We make use of AJAX to post data to ChatsController#create which in turn publish through redis to chat channel and as a result push new message to all clients that subscribe to that channel.
Webservers
By default, rails server uses WEBrick. The Rack adapter for WEBrick buffers all output in a way we cannot bypass, so developing this example with rails server will not work. We will use Puma an opensource concurrent webserver for this project. Drop in the puma gem and start server with command rails s puma
gem 'puma'
Now open your browser and visit http://localhost:3000/chats.