Sinatra 1.3 introduced the
stream
helper. This addon improves the streaming API by making
the stream object immitate an IO object, turing it into a real Deferrable
and making the body play nicer with middleware unaware of streaming.
This is useful when passing the stream object to a library expecting an IO or StringIO object.
get '/' do stream do |out| out.puts "Hello World!", "How are you?" out.write "Written #{out.pos} bytes so far!\n" out.putc(65) unless out.closed? out.flush end end
Handy when using EventMachine.
list = [] get '/' do stream(false) do |out| list << out out.callback { list.delete out } out.errback do logger.warn "lost connection" list.delete out end end end
Blocks passed to map! or map will actually be applied while streaming (as you might suspect, map! applies modifications to the current body, map creates a new one):
class StupidMiddleware def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) body.map! { |e| e.upcase } [status, headers, body] end end use StupidMiddleware get '/' do stream do |out| out.puts "still" sleep 1 out.puts "streaming" end end
Even works if each is used to generate an Enumerator:
def call(env) status, headers, body = @app.call(env) body = body.each.map { |s| s.upcase } [status, headers, body] end
Note that both examples violate the Rack specification.
In a classic application:
require "sinatra" require "sinatra/streaming"
In a modular application:
require "sinatra/base" require "sinatra/streaming" class MyApp < Sinatra::Base helpers Streaming end
# File lib/sinatra/streaming.rb, line 97 def stream(*) stream = super stream.extend Stream stream.app = self env['async.close'].callback { stream.close } if env.key? 'async.close' stream end