Module: Sinatra::Streaming

Defined in:
lib/sinatra/streaming.rb

Overview

Sinatra::Streaming

Sinatra 1.3 introduced the stream helper. This addon improves the streaming API by making the stream object immitate 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

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 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

Defined Under Namespace

Modules: Stream

Instance Method Summary collapse

Instance Method Details

#streamObject



97
98
99
100
101
102
103
# 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