module Sinatra::Streaming

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(false) 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 Streaming
end

Public Instance Methods

stream(*) click to toggle source
# 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