Slide 1

Slide 1 text

Real-Time HTML5 and Ruby Luigi Montanez RubyNation 2012

Slide 2

Slide 2 text

Overview Part 1 - Real-time HTML5 Technologies Part 2 - EventMachine Part 3 - Ruby frameworks

Slide 3

Slide 3 text

tl;dw You can do this stu! in Ruby, you don’t need Node.js.

Slide 4

Slide 4 text

Part 1: Real-Time HTML5 Technologies

Slide 5

Slide 5 text

The Enemy: Page Refreshing

Slide 6

Slide 6 text

“Classic” Solutions AJAX AJAX Polling COMET (Long Polling)

Slide 7

Slide 7 text

“Classic” Solutions AJAX AJAX Polling COMET (Long Polling)

Slide 8

Slide 8 text

WebSockets TCP Socket between browser and server Bi-directional Remains open until explicitly closed

Slide 9

Slide 9 text

In the Browser var connection = new WebSocket('ws://example.com/echo'); connection.onopen = function () { connection.send('Ping'); }; connection.onerror = function (error) { // handle error }; connection.onmessage = function (e) { console.log('Server said: ' + e.data); }; connection.onclose = function () { // ensure you expected a close };

Slide 10

Slide 10 text

Just TCP A lower level than HTTP Developer de"nes protocol Support XMPP, IRC, AMQP, VNC Some proxy servers not compatible

Slide 11

Slide 11 text

No JS Poly!ll Flash-based fallback Socket.IO falls back using di!erent methods

Slide 12

Slide 12 text

Bi-directional communication usually overkill.

Slide 13

Slide 13 text

Server-Sent Events Forgotten little brother of WebSockets Downstream, server to browser push Just HTTP Browser handles reconnections Pure JS Poly"ll by Remy Sharp

Slide 14

Slide 14 text

In the Browser var source = new EventSource('/stream'); source.addEventListener('message', function(e) { console.log(e.data); }); source.addEventListener('open', function(e) { ... }); source.addEventListener('error', function(e) { ... });

Slide 15

Slide 15 text

Data data: first line\n data: second line\n\n --- data: {\n data: "msg": "hello world",\n data: "id": 12345\n data: }\n\n source.addEventListener('message', function(e) { var data = JSON.parse(e.data); console.log(data.id, data.msg); });

Slide 16

Slide 16 text

Data IDs and Events id: 12345\n data: AAPL\n data: 572.44\n\n --- data: {"msg": "First message"}\n\n event: userlogon\n data: {"username": "John123"}\n\n event: update\n data: {"username": "John123", "emotion": "happy"}\n\n source.addEventListener('userlogon', function(e) { ... source.addEventListener('update', function(e) { ...

Slide 17

Slide 17 text

Part 2: EventMachine

Slide 18

Slide 18 text

Why EM? Many concurrent, long-held connections Addresses the C10K Problem Can’t use Rails or Rack to support WebSockets and Server-Sent Events

Slide 19

Slide 19 text

Reactor Pattern Single-threaded event loop listens to many sources Dispatches synchronous work to handlers Handlers report when done

Slide 20

Slide 20 text

Timebomb require 'eventmachine' EM.run do EM.add_timer(5) do puts "BOOM" EM.stop_event_loop end EM.add_periodic_timer(1) do puts "Tick" end end $ ruby timer.rb Tick Tick Tick Tick BOOM

Slide 21

Slide 21 text

Other Constructs EM#next_tick EM#defer EM::Deferrable EM::Queue EM::Channel

Slide 22

Slide 22 text

Problem: Pyramid Code or Callback Spaghetti

Slide 23

Slide 23 text

Tip of the Pyramid EventMachine.run { page = EventMachine::HttpRequest.new('http://example.com/').get page.errback { p "Google is down! terminate?" } page.callback { about = EventMachine::HttpRequest.new('http://example2.com').get about.callback { # callback nesting, ad infinitum } about.errback { # error-handling code } } }

Slide 24

Slide 24 text

EM::Synchrony Ruby 1.9 Fibers Abstracts away callbacks and errbacks Code looks synchronous but is actually asynchronous

Slide 25

Slide 25 text

EM + Fiber def http_get(url) f = Fiber.current http = EM::HttpRequest.new(url).get # resume fiber once http call is done http.callback { f.resume(http) } http.errback { f.resume(http) } return Fiber.yield end EM.run do Fiber.new{ page = http_get('http://www.google.com/') puts "Fetched page: #{page.response_header.status}" if page page = http_get('http://www.google.com/search?q=eventmachine') puts "Fetched page 2: #{page.response_header.status}" end }.resume end

Slide 26

Slide 26 text

EM::Synchrony::Multi EventMachine.synchrony do multi = EventMachine::Synchrony::Multi.new multi.add :a, EventMachine::HttpRequest.new(uri1).aget multi.add :b, EventMachine::HttpRequest.new(uri2).apost multi.add :c, EventMachine::HttpRequest.new(uri3).aget res = multi.perform p "Look ma, no callbacks, and parallel HTTP requests!" p res EventMachine.stop end

Slide 27

Slide 27 text

Part 3: Ruby Frameworks

Slide 28

Slide 28 text

All built on top of EventMachine

Slide 29

Slide 29 text

Servers Thin Rainbows!

Slide 30

Slide 30 text

Cramp Built by Pratik Naik, “lifo” http://cramp.in Supports Fibers $ cramp new myapp

Slide 31

Slide 31 text

Server-Sent Events class TimeAction < Cramp::Action self.transport = :sse on_start :send_latest_time periodic_timer :send_latest_time, :every => 2 def send_latest_time data = {'time' => Time.now.to_i}.to_json render data end end

Slide 32

Slide 32 text

Goliath Built by Ilya Grigorik http://goliath.io Supports EM::Synchrony Both a server and a framework

Slide 33

Slide 33 text

Goliath App class Websocket < Goliath::API use Goliath::Rack::Favicon, ‘favicon.ico') map '/ws', WebsocketEndPoint end

Slide 34

Slide 34 text

WebSocket Endpoint class WebsocketEndPoint < Goliath::WebSocket def on_open(env) env.logger.info("WS OPEN") env['subscription'] = env.channel.subscribe { |m| env.stream_send(m) } end def on_message(env, msg) env.logger.info("WS MESSAGE: #{msg}") env.channel << msg end def on_close(env) env.logger.info("WS CLOSED") env.channel.unsubscribe(env['subscription']) end def on_error(env, error) env.logger.error error end end

Slide 35

Slide 35 text

Frameworks AsyncRack Cramp Faye::WebSocket Goliath

Slide 36

Slide 36 text

Caveats Separate app Ports vs. Paths Automated Testing

Slide 37

Slide 37 text

Credits HTML5 Rocks Ilya Grigorik Dan Sinclair Peepcode

Slide 38

Slide 38 text

Questions?