Reputation: 6108
I have a Sinatra web app that I'd very much like to enhance with streaming updates for certain functions. Right now, though, I'm just trying to learn my way around using streaming data, which I've never done before. I have the following simple test code:
In Sinatra:
get '/foo' do
stream do |out|
10.times do
out.puts "foo"
out.flush
sleep 1
end
end
end
get '/bar' do
erb :bar
end
In bar.erb
:
<body>
<div class="stream">
nothing.
</div>
</body>
<script type="text/javascript" charset="utf-8">
$(document).ready( function() {
$.get('/foo', function(html) {
$(".stream").html(html);
});
});
</script>
I'm not surprised that this doesn't do what I want, which is to pick up each 'foo' when it's written and update the page dynamically. Instead, nothing happens for ~10 seconds and then I get foo foo foo foo foo foo foo foo foo foo foo
.
My question is, how in an ERB template (using Ruby, jQuery, or other means) can I pull streamed data as it is provided, instead of blocking until it's all collected and spitting it all out at once?
Upvotes: 4
Views: 880
Reputation: 42192
If you use stream in Sinatra the normal templates are skipped, you get a blank page with only the html you are streaming. You could circumvent this by manually creating and streaming your layout along with the text. Here an example.
require 'sinatra'
require "sinatra/streaming"
set server: 'thin', connections: []
get '/' do
stream do |out|
settings.connections << out
@out = out
erb :stream
out.callback { settings.connections.delete(out) }
end
end
__END__
@@pre
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<h1>This is the body</h1>
@@after
</body>
</html>
@@stream
<%
@out.puts erb(:pre)
@out.puts "<h2>test</h2>"
(1..10).each do |i|
@out.puts "#{i}<br>"
sleep 2
end
@out.puts erb(:after)
@out.flush
%>
Upvotes: 0
Reputation: 302
Sinatra actions wrap an entire HTTP response cycle - that means it waits until the action finishes before closing the request, at which point the browser considers the data "complete" and "good" for use. All you have created in your code above is a very, very slow Sinatra action.
The technology you are looking for is Websockets, which are supported by most modern browsers and provide a two-way communications channel between each client and the server. The websocket channel is created by "upgrading" a regular HTTP request. Where clients don't support web sockets, they may be emulated using techniques such as HTTP Long Polling (wherein a request is left open, without a response, until there is data available - at that point the data is shunted down the response channel, the response channel is closed, and the client is expected to open a new request to get any further data).
You can get this set up in your Ruby app using EventMachine and EM-Websocket. An alternative is Socky which I believe provides the javascript client as well as the Ruby server.
Upvotes: 5