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 imitate an IO object, turning 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

Better Middleware Handling

Blocks passed to #map! or #map will actually be applied when streaming takes place (as you might have suspected, #map! applies modifications to the current body, while #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