This extension is part of the Sinatra::Contrib project. Run gem install sinatra-contrib to have it available.

Sinatra::Streaming

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.

IO-like behavior

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

Proper Deferrable

Handy when using EventMachine.

list = []

get '/' do
  stream(:keep_open) do |out|
    list << out
    out.callback { list.delete out }
    out.errback do
      logger.warn "lost connection"
      list.delete out
    end
  end
end

Better Middleware Handling

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.

Setup

In a classic application:

require "sinatra"
require "sinatra/streaming"

In a modular application:

require "sinatra/base"
require "sinatra/streaming"

class MyApp < Sinatra::Base
  helpers Sinatra::Streaming
end
Fork me on GitHub