Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Simple Chat Application using the Sinatra Streaming API
# coding: utf-8
require 'sinatra'
set server: 'thin', connections: []
get '/' do
halt erb(:login) unless params[:user]
erb :chat, locals: { user: params[:user].gsub(/\W/, '') }
end
get '/stream', provides: 'text/event-stream' do
stream :keep_open do |out|
settings.connections << out
out.callback { settings.connections.delete(out) }
end
end
post '/' do
settings.connections.each { |out| out << "data: #{params[:msg]}\n\n" }
204 # response without entity body
end
__END__
@@ layout
<html>
<head>
<title>Super Simple Chat with Sinatra</title>
<meta charset="utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
</head>
<body><%= yield %></body>
</html>
@@ login
<form action='/'>
<label for='user'>User Name:</label>
<input name='user' value='' />
<input type='submit' value="GO!" />
</form>
@@ chat
<pre id='chat'></pre>
<script>
// reading
var es = new EventSource('/stream');
es.onmessage = function(e) { $('#chat').append(e.data + "\n") };
// writing
$("form").live("submit", function(e) {
$.post('/', {msg: "<%= user %>: " + $('#msg').val()});
$('#msg').val(''); $('#msg').focus();
e.preventDefault();
});
</script>
<form>
<input id='msg' placeholder='type message here...' />
</form>
@rkh

This comment has been minimized.

Show comment Hide comment
@rkh

rkh Dec 14, 2011

Owner

rkh commented Dec 14, 2011

@pcreux

This comment has been minimized.

Show comment Hide comment
@pcreux

pcreux Dec 14, 2011

Awesome! :)

pcreux commented Dec 14, 2011

Awesome! :)

@lucianosousa

This comment has been minimized.

Show comment Hide comment
@lucianosousa

lucianosousa Dec 14, 2011

nice man! really good.

nice man! really good.

@PavelDemyanenko

This comment has been minimized.

Show comment Hide comment
@PavelDemyanenko

PavelDemyanenko Dec 14, 2011

nice!

nice!

@vredniy

This comment has been minimized.

Show comment Hide comment
@vredniy

vredniy Dec 14, 2011

It was interesing with images and JavaScripts )

vredniy commented Dec 14, 2011

It was interesing with images and JavaScripts )

@fractaloop

This comment has been minimized.

Show comment Hide comment
@fractaloop

fractaloop Dec 14, 2011

Very cool! Thanks for the simple and helpful snippet. =]

Very cool! Thanks for the simple and helpful snippet. =]

@andreif

This comment has been minimized.

Show comment Hide comment
@andreif

andreif Jan 1, 2012

(Note: I have deleted my previous comment, since that problem was either in the response body format, or in Sinatra version not supporting routing params in stream.)

I am trying to run the example and have some problems with :keep_open and #callback (I use Sinatra 1.3.2 and browse with Chrome 16):

  1. the connection is closed in 30-60 sec unless one sets a periodic timer as suggested by Konstantin elsewhere
  2. callback never fires (e.g. after page reload), and therefore the number of connections is growing

Can you replicate it? Is it possible to fix these issues somehow? WebSockets work seamlessly in this respect...

get '/stream', provides: 'text/event-stream' do
  stream :keep_open do |out|
    EventMachine::PeriodicTimer.new(20) { out << "data: \n\n" } # added
    settings.connections << out
    puts settings.connections.count # added
    out.callback { puts 'closed'; settings.connections.delete(out) } # modified
  end
end
  var es = new EventSource('/stream');
  es.onmessage = function(e) { if (e.data != '') $('#chat').append(e.data + "\n") }; // modified

Update:

This was a bug sinatra/sinatra#446

andreif commented Jan 1, 2012

(Note: I have deleted my previous comment, since that problem was either in the response body format, or in Sinatra version not supporting routing params in stream.)

I am trying to run the example and have some problems with :keep_open and #callback (I use Sinatra 1.3.2 and browse with Chrome 16):

  1. the connection is closed in 30-60 sec unless one sets a periodic timer as suggested by Konstantin elsewhere
  2. callback never fires (e.g. after page reload), and therefore the number of connections is growing

Can you replicate it? Is it possible to fix these issues somehow? WebSockets work seamlessly in this respect...

get '/stream', provides: 'text/event-stream' do
  stream :keep_open do |out|
    EventMachine::PeriodicTimer.new(20) { out << "data: \n\n" } # added
    settings.connections << out
    puts settings.connections.count # added
    out.callback { puts 'closed'; settings.connections.delete(out) } # modified
  end
end
  var es = new EventSource('/stream');
  es.onmessage = function(e) { if (e.data != '') $('#chat').append(e.data + "\n") }; // modified

Update:

This was a bug sinatra/sinatra#446

@newtriks

This comment has been minimized.

Show comment Hide comment
@newtriks

newtriks Feb 2, 2012

Lovely example, works a charm and nice and simple thanks!

newtriks commented Feb 2, 2012

Lovely example, works a charm and nice and simple thanks!

@phillipgore

This comment has been minimized.

Show comment Hide comment
@phillipgore

phillipgore Feb 4, 2012

I just started implementing Sinatra Streaming into my app. Going great so far. What I need to figure out is how to stream changes for an account only to users on that account and not everyone. Can anyone point me in the right direction or link to some sample code?

I just started implementing Sinatra Streaming into my app. Going great so far. What I need to figure out is how to stream changes for an account only to users on that account and not everyone. Can anyone point me in the right direction or link to some sample code?

@baronlinden

This comment has been minimized.

Show comment Hide comment
@baronlinden

baronlinden Feb 14, 2012

Great example.

How do I send messages from outside the Sinatra endpoints? I have an async event handler that listens for messages from another server, and I want to be able to forward a message on one of the Sinatra connections. I have stored the connection objects in a class Array and retrieve the connection and append the message on it (<<), but I assume I need to send the response by using something similar to "204", but how do I do that from outside the SSE endpoint (get {})?

Thanks!

Great example.

How do I send messages from outside the Sinatra endpoints? I have an async event handler that listens for messages from another server, and I want to be able to forward a message on one of the Sinatra connections. I have stored the connection objects in a class Array and retrieve the connection and append the message on it (<<), but I assume I need to send the response by using something similar to "204", but how do I do that from outside the SSE endpoint (get {})?

Thanks!

@baronlinden

This comment has been minimized.

Show comment Hide comment
@baronlinden

baronlinden Feb 15, 2012

It seems like it has to do with the fact that I have mounted the Sinatra app inside a Rails app. I get it to work when running the Sinatra app by itself, but when running it inside the Rails app the connection seems to be dropped because the browser is reconnecting continuously and therefore I won't be able to stream messages from my message handler.

Is there anything I need to configure in Rails for this to work?

It seems like it has to do with the fact that I have mounted the Sinatra app inside a Rails app. I get it to work when running the Sinatra app by itself, but when running it inside the Rails app the connection seems to be dropped because the browser is reconnecting continuously and therefore I won't be able to stream messages from my message handler.

Is there anything I need to configure in Rails for this to work?

@rkh

This comment has been minimized.

Show comment Hide comment
@rkh

rkh Feb 15, 2012

The Rails issue is well known, dunno what's breaking here, I think they wrap the body object somewhere and the errback/callback methods get lost on the way. Solution for now is to mount it in the config.ru rather than your routes.rb.

Owner

rkh commented Feb 15, 2012

The Rails issue is well known, dunno what's breaking here, I think they wrap the body object somewhere and the errback/callback methods get lost on the way. Solution for now is to mount it in the config.ru rather than your routes.rb.

@baronlinden

This comment has been minimized.

Show comment Hide comment
@baronlinden

baronlinden Feb 15, 2012

Yeah, I tried adding the Sinatra app as a Rails middleware and it worked. Cheers.

Yeah, I tried adding the Sinatra app as a Rails middleware and it worked. Cheers.

@patrickpang

This comment has been minimized.

Show comment Hide comment
@patrickpang

patrickpang Jul 3, 2012

pretty good.
a nice alternative with websockets, with javascript polyfills

pretty good.
a nice alternative with websockets, with javascript polyfills

@celeryclub

This comment has been minimized.

Show comment Hide comment
@celeryclub

celeryclub Aug 26, 2012

Just what I needed! Thanks a lot.

Just what I needed! Thanks a lot.

@mig-hub

This comment has been minimized.

Show comment Hide comment
@mig-hub

mig-hub May 23, 2013

Nice,

Since no specific version of jQuery is requested, it doesn't work anymore on the Heroku demo.
Because "live" was removed.
It works with this revision

You probably knew this as it was a year ago, but just in case...

mig-hub commented May 23, 2013

Nice,

Since no specific version of jQuery is requested, it doesn't work anymore on the Heroku demo.
Because "live" was removed.
It works with this revision

You probably knew this as it was a year ago, but just in case...

@akemrir

This comment has been minimized.

Show comment Hide comment
@akemrir

akemrir Sep 27, 2013

rkj, do you have padrino version?

akemrir commented Sep 27, 2013

rkj, do you have padrino version?

@mcfiredrill

This comment has been minimized.

Show comment Hide comment
@mcfiredrill

mcfiredrill Mar 31, 2014

How many clients would this reasonable support, using say, thin?

How many clients would this reasonable support, using say, thin?

@multiholle

This comment has been minimized.

Show comment Hide comment
@multiholle

multiholle Apr 18, 2017

Change line 29 to <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script> if you have trouble getting it to run. There was an api change in JQuery 1.9.

Change line 29 to <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script> if you have trouble getting it to run. There was an api change in JQuery 1.9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment