Real-Time Rails
Brian Cardarella
Who?
Owner of @DockYard
Co-Organizer for Boston OpenHack
Co-Organizer for Boston Ember.js
Wicked Good Ruby
@wickedgoodruby
Oct 12 - 13
What's the point?
Real-time is just one of the areas where Node.js has outpaced Rails
Rails was the innovator and is now playing catch-up
Can Rails Scale?
Can Rails Scale?
(its features)
Sockets

TCP Sockets
by Jesse Storimer
TCP Sockets
Basics
- Data is broken into frames
- Frames are broken into packets
- Packets travel through the tubes
- Packets are constructed into frames
- Data is read from frames
Concurrency
- Web Servers
- Threads
- Pub/Sub
- Thread safety
Web Servers



Rails
Rails 4 is now fully concurrent, there is no full-stack lock on a request. If you use a concurrent server like Puma you can handle many requests at a time with a single process.
- Nick Gauthier
Threads
- You will have to learn how
to write threaded code in Ruby - Being aware thread safety issues in
the code you write
Threads - 101
thread = Thread.new do
100.times do
puts 'Here!'
end
end
puts 'Over here too!'
Simple race condition example
More Complex
- Several "rooms"
- Each room can only hold 2 visitors
- Visitors are assigned to rooms as they join
- When a room fills the next visitor
is assigned to the next available room
Queues
- Mutex#synchronize
- External queue (Redis lists)
- Push to the queue
- Worker pops and run job
- Ensures execution order
Pub/Sub
Will be your best friend
- Postgres Notify/Listen
- Redis Publish/Subscribe
- ZeroMQ
Rails
-
Event Machine / Celluloid
- Faye / Pusher.com
Rails
Long polling
poll = function() {
$.ajax({
url: '/chat.json',
data: { last_time: getLastTime() }
}).done(function(data) {
// handle data
setTimeout(poll, 1000);
});
}
Rails 4
ActionController::Live
- Proxy object for the socket
- Stream response
Rails 4
Long Polling
class ChatController < ActionController::Base
def index
render json: { chat: Chat.history_from(params[:last_time]) }
end
end
Rails 4
ActionController::Live
class ChatController < ActionController::Base
include ActionController::Live
def index
Thread.new do
ChatStreamer.run(response.stream)
end
end
end
Rails 4
The stream object is meant to quack like an IO object
class MyController < ActionController::Base
include ActionController::Live
def index
100.times {
response.stream.write "hello world\n"
}
response.stream.close
end
end
- Aaron Patterson
Server Sent Events
- One way streaming communication
- Part of HTML5 Spec
def index
100.times {
response.headers['Content-Type'] = 'text/event-stream'
response.stream.write "event: time\n"
data = JSON.dump({ time: Time.now })
response.stream.write "#data: {data}\n\n"
sleep 1
}
response.stream.close
end
var source = new EventSource('/browser');
source.addEventListener('time', function(event) {
// handle event
});
Rack.Hijack
New with Rack 1.5
env['rack.hijack?']
=> true
env['rack.hijack'].call
=> <TCPSocket:XXXX>
env['rack.hijack_io']
=> <TCPSocket:XXXX>
Rack.Hijack
Full Hijack
Partial Hijack
Rack.Hijack
Full Hijack
Partial Hijack
Rack.Hijack
- Request is made to the server
- TCP Socket is hijacked
- Socket is put into a thread
- Request completes as normal
- Thread is still alive and able to
stream back to the client
- Up to the web server to allocate
new sockets or watch hijacked
socket and wait until free
Websockets
- Perfect for rack.hijack
- Two way communication
- Websocket gem
gem 'websocket', github: 'imanel/websocket-ruby'
gem 'websocket-native'
Websockets
class ChatController < ActionController::Base
def index
socket = env['rack.hijack'].call
# Sending the header
handshake = WebSocket::Handshake::Server.new
handshake.from_rack env
socket.write handshake.to_s
# Reading a frame
buffer = WebSocket::Frame::Incoming::Server.new({
version: handshake.version})
raw_data = socket.recvfrom(2000).first
buffer << raw_data
while frame = buffer.next do
// handle frame.data
end
# Writing a frame
msg = "Hello"
frame = WebSocket::Frame::Outgoing::Server.new({
version: handshake.version, type: 'text', data: msg})
socket.write frame.to_s
end
end
WEbsockets
- Construct the header
- Write the header to the socket
- Write data to the socket
- Read data from the socket
- Close the socket
Websockets
ws = new WebSocket('ws://server:port/chat')
ws.onopen = function() {
// do something upon connection
};
ws.onmessage = function(event) {
// handle event.data
};
ws.send(data);
ws.onclose = function() {
// teardown
}
Demos
Click on 'Chat'
A Wild Pattern Appears!
- Client captures events
- Event data is sent to the server
- Event data is normalized
- Event data is broadcast to all connections
TubeSock
Websockets + rack.hijack
hijack do |tubesock|
tubesock.onopen do
tubesock.send_data message: "Hello, friend"
end
tubesock.onmessage do |data|
tubesock.send_data message: "You said: #{data[:message]}"
end
end
Challenges
- No Heroku support
- Nginx fights websockets
The future?
- Integrate rack.hijack into ActionController::Live
- Rails is (hopefully) moving towards the client
- Frameworks like Ember (and others) are
growing and need a strong backend
solution
Thank you!
Work with DockYard
Hire DockYard
contact@dockyard.com
real time rails
By bcardarella
real time rails
- 10,632