Sinatra steam helper doesn't work - websocket

require 'sinatra/base'
class Counter < Sinatra::Base
set :server, 'thin' # 'webrick' doesn't work either
get '/' do
haml :index
end
get '/stream' do |out|
out << 'first sentence'
sleep 0.5
out << 'second sentence'
sleep 0.5
end
run!
end
I follow the guide in the README file, which suggests Thin server works. But it gives me a empty page whereas webrick serve the page with the two sentences altogether without the stream effect.
Could someone let me know where I did wrong?

Excerpt from Sinatra: The blog
get '/stream' do
stream do |out|
out << "It's gonna be legen -\n"
sleep 0.5
out << " (wait for it) \n"
sleep 1
out << "- dary!\n"
end
end
notice line: 2 stream do |out|

Related

ruby/sinatra: will code outside the routes run only once or each time site is pinged?

I have a slim Sinatra site.
If I include code outside the get routes, will it run in the background only once, or will it trigger each time the IP address is pinged.
For example will the function 'start' only run once on server creation / gitpush or will it run anew each site visit.
--
other-code.rb
$variable
$count = 0
def start
$variable = "hello world + #{$count}"
$count += 1
end
start
--
index.rb
require 'sinatra'
require 'json'
require 'other-code'
get '/' do
content_type :json
puts $variable
end
Require only loads the ruby code from the required file once.
Here is how you can tell:
#index.rb
require 'sinatra'
require 'json'
require_relative 'other_code'
get '/' do
content_type :json
puts $variable
end
# other_code.rb
$variable
def start
$variable = 'hello world'
end
puts 'other code called'
start
Now start your sinatra server
ruby index.rb
You will see this in the console:
other code called
== Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
Then hit your browser a few times and look at your console, you will only see other code called output 1 time. However each time you hit your get route, you should see output hello world!

Streaming on Ruby Thin server

I tried the following ruby code...
self.response.headers["Cache-Control"] ||= "no-cache"
self.response.headers["Transfer-Encoding"] = "chunked"
self.response.headers['Last-Modified'] = Time.now.ctime.to_s
self.response_body = Rack::Chunked::Body.new(Enumerator.new do |y|
10.times do
sleep 1
y << "Hello World\n"
end
end)
This works great in Unicron server but can't stream using Thin server. I tried 1.5.0 and 2.0.0.pre too, this is not working in thin.
I tried the following rack code,
class DeferredBody
def each(block)
#server_block = block
end
def send(data)
#server_block.call data
end
end
class RackStreamApp
def self.call(env)
Thread.new do
sleep 2 # simulate waiting for some event
body = DeferredBody.new
response = [200, {'Content-Type' => 'text/plain'}, body]
env['async.callback'].call response
body.send 'Hello, '
sleep 2
body.send 'World'
end
[-1, {}, []] # or throw :async
end
end
The above code streams "Hello, World" if we use Unicorn Server, but the code doesn't stream using Thin server 1.5.0 ( I tried 2.0.0-pre too)
Is there anything I can do to stream data using the thin server?

How to stop a background thread in Sinatra once the connection is closed

I'm trying to consume the twitter streaming API with Sinatra and give users real-time updates when they search for a keyword.
require 'sinatra'
require 'eventmachine'
require 'em-http'
require 'json'
STREAMING_URL = 'https://stream.twitter.com/1/statuses/sample.json'
get '/' do
stream(:keep_open) do |out|
http = EM::HttpRequest.new(STREAMING_URL).get :head => { 'Authorization' => [ 'USERNAME', 'PASS' ] }
buffer = ""
http.stream do |chunk|
puts "still chugging"
buffer += chunk
while line = buffer.slice!(/.+\r?\n/)
tweet = JSON.parse(line)
unless tweet.length == 0 or tweet['user'].nil?
out << "<p><b>#{tweet['user']['screen_name']}</b>: #{tweet['text']}</p>"
end
end
end
end
end
I want the processing of the em-http-request stream to stop if the user closes the connection. Does anyone know how to do this?
Eric's answer was close, but what it does is closing the response body (not the client connection, btw) once your twitter stream closes, which normally never happens. This should work:
require 'sinatra/streaming' # gem install sinatra-contrib
# ...
get '/' do
stream(:keep_open) do |out|
# ...
out.callback { http.conn.close_connection }
out.errback { http.conn.close_connection }
end
end
I'm not quite familiar with the Sinatra stream API yet, but did you try this?
http.callback { out.close }

stream multiple body using async sinatra

I would like start a long poll request from javascript which is fine and i expect my ruby prog to stream multiple body sections to the javascript. Why doesn the following (pseudo)code work?
require 'rubygems'
require 'sinatra/async'
require 'eventmachine'
require 'thin'
require 'json'
class Test < Sinatra:Base
register Sinatra::Async
aget '/process' do
for c in 1..10
body {
{ :data => [ "this is part #{c}" ] }.to_json
end
end
end
run!
end
Maybe i misunderstood what long polling and async is supposed to do, but my expectation is that i get multiple bodies sent back to the client ? Do i need to use eventmachine or something?
thanks
require 'rubygems'
require 'sinatra/async'
require 'thin'
require 'json'
class Test < Sinatra::Base
register Sinatra::Async
class JSONStream
include EventMachine::Deferrable
def stream(object)
#block.call object.to_json + "\n"
end
def each(&block)
#block = block
end
end
aget '/process' do
puts 'ok'
out = JSONStream.new
body out
EM.next_tick do
c = 0
timer = EM.add_periodic_timer(0.3) do
c += 1
out.stream :data => ["this is part #{c}"]
if c == 100
timer.cancel
out.succeed
end
end
end
end
run!
end
See also: http://confreaks.net/videos/564-scotlandruby2011-real-time-rack
It appears in the example below that you need an EventMachine event to trigger the sending of the multiple bodies. Also see this previous answer as well.
require 'sinatra/async'
class AsyncTest < Sinatra::Base
register Sinatra::Async
aget '/' do
body "hello async"
end
aget '/delay/:n' do |n|
EM.add_timer(n.to_i) { body { "delayed for #{n} seconds" } }
end
end

Is there a way to flush html to the wire in Sinatra

I have a Sinatra app with a long running process (a web scraper). I'd like the app flush the results of the crawler's progress as the crawler is running instead of at the end.
I've considered forking the request and doing something fancy with ajax but this is a really basic one-pager app that really just needs to output a log to a browser as it's happening. Any suggestions?
Update (2012-03-21)
As of Sinatra 1.3.0, you can use the new streaming API:
get '/' do
stream do |out|
out << "foo\n"
sleep 10
out << "bar\n"
end
end
Old Answer
Unfortunately you don't have a stream you can simply flush to (that would not work with Rack middleware). The result returned from a route block can simply respond to each. The Rack handler will then call each with a block and in that block flush the given part of the body to the client.
All rack responses have to always respond to each and always hand strings to the given block. Sinatra takes care of this for you, if you just return a string.
A simple streaming example would be:
require 'sinatra'
get '/' do
result = ["this", " takes", " some", " time"]
class << result
def each
super do |str|
yield str
sleep 0.3
end
end
end
result
end
Now you could simply place all your crawling in the each method:
require 'sinatra'
class Crawler
def initialize(url)
#url = url
end
def each
yield "opening url\n"
result = open #url
yield "seaching for foo\n"
if result.include? "foo"
yield "found it\n"
else
yield "not there, sorry\n"
end
end
end
get '/' do
Crawler.new 'http://mysite'
end

Resources