Class: Sinatra::Helpers::Stream

### Home Classes Methods

### In Files

- lib/sinatra/base.rb

### Parent

Object

### Namespace

- MODULE Sinatra::Helpers::Stream::Templates
- CLASS Sinatra::Helpers::Stream::Application
- CLASS Sinatra::Helpers::Stream::Base
- CLASS Sinatra::Helpers::Stream::Wrapper

### Methods

- ::defer
- ::new
- ::schedule
- #<<
- #back
- #cache_control
- #callback
- #client_error?
- #close
- #closed?
- #each
- #etag
- #etag_matches?
- #expires
- #informational?
- #last_modified
- #not_found?
- #redirect?
- #server_error?
- #stream
- #success?
- #time_for
- #with_params

### Files

- LICENSE

### Class/Module Index

Quicksearch

- Sinatra
- Sinatra::Application
- Sinatra::CommonLogger
- Sinatra::ExtendedRack
- Sinatra::Helpers
- Sinatra::Helpers::Stream
- Sinatra::Helpers::Stream::Application
- Sinatra::Helpers::Stream::Base
- Sinatra::Helpers::Stream::Templates
- Sinatra::Helpers::Stream::Templates::ContentTyped
- Sinatra::Helpers::Stream::Wrapper
- Sinatra::Request
- Sinatra::Request::AcceptEntry
- Sinatra::Response
- Sinatra::ShowExceptions
- Rack
- Rack::Builder

No matching classes.

# Sinatra::Helpers::Stream

Class of the response body in case you use stream.

Three things really matter: The front and back block (back being the block generating content, front the one sending it to the client) and the scheduler, integrating with whatever concurrency feature the Rack handler is using.

Scheduler has to respond to defer and schedule.

### Constants

ETAG_KINDS

### Public Class Methods

defer(*) click to toggle source

    # File lib/sinatra/base.rb, line 385
      def self.defer(*)    yield end

      def initialize(scheduler = self.class, keep_open = false, &back)
        @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
        @callbacks, @closed = [], false
      end

      def close
        return if @closed
        @closed = true
        @scheduler.schedule { @callbacks.each { |c| c.call }}
      end

      def each(&front)
        @front = front
        @scheduler.defer do
          begin
            @back.call(self)
          rescue Exception => e
            @scheduler.schedule { raise e }
          end
          close unless @keep_open
        end
      end

      def <<(data)
        @scheduler.schedule { @front.call(data.to_s) }
        self
      end

      def callback(&block)
        return yield if @closed
        @callbacks << block
      end

      alias errback callback

      def closed?
        @closed
      end
    end

new(scheduler = self.class, keep_open = false, &back) click to toggle source

    # File lib/sinatra/base.rb, line 387
    def initialize(scheduler = self.class, keep_open = false, &back)
      @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
      @callbacks, @closed = [], false
    end

schedule(*) click to toggle source

    # File lib/sinatra/base.rb, line 384
        def self.schedule(*) yield end
        def self.defer(*)    yield end

        def initialize(scheduler = self.class, keep_open = false, &back)
          @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
          @callbacks, @closed = [], false
        end

        def close
          return if @closed
          @closed = true
          @scheduler.schedule { @callbacks.each { |c| c.call }}
        end

        def each(&front)
          @front = front
          @scheduler.defer do
            begin
              @back.call(self)
            rescue Exception => e
              @scheduler.schedule { raise e }
            end
            close unless @keep_open
          end
        end

        def <<(data)
          @scheduler.schedule { @front.call(data.to_s) }
          self
        end

        def callback(&block)
          return yield if @closed
          @callbacks << block
        end

        alias errback callback

        def closed?
          @closed
        end
      end

      # Allows to start sending data to the client even though later parts of
      # the response body have not yet been generated.
      #
      # The close parameter specifies whether Stream#close should be called
      # after the block has been executed. This is only relevant for evented
      # servers like Thin or Rainbows.
      def stream(keep_open = false)
        scheduler = env['async.callback'] ? EventMachine : Stream
        current   = @params.dup
        body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
      end

      # Specify response freshness policy for HTTP caches (Cache-Control header).
      # Any number of non-value directives (:public, :private, :no_cache,
      # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
      # a Hash of value directives (:max_age, :min_stale, :s_max_age).
      #
      #   cache_control :public, :must_revalidate, :max_age => 60
      #   => Cache-Control: public, must-revalidate, max-age=60
      #
      # See RFC 2616 / 14.9 for more on standard cache control directives:
      # http://tools.ietf.org/html/rfc2616#section-14.9.1
      def cache_control(*values)
        if values.last.kind_of?(Hash)
          hash = values.pop
          hash.reject! { |k,v| v == false }
          hash.reject! { |k,v| values << k if v == true }
        else
          hash = {}
        end

        values.map! { |value| value.to_s.tr('_','-') }
        hash.each do |key, value|
          key = key.to_s.tr('_', '-')
          value = value.to_i if key == "max-age"
          values << "#{key}=#{value}"
        end

        response['Cache-Control'] = values.join(', ') if values.any?
      end

      # Set the Expires header and Cache-Control/max-age directive. Amount
      # can be an integer number of seconds in the future or a Time object
      # indicating when the response should be considered "stale". The remaining
      # "values" arguments are passed to the #cache_control helper:
      #
      #   expires 500, :public, :must_revalidate
      #   => Cache-Control: public, must-revalidate, max-age=60
      #   => Expires: Mon, 08 Jun 2009 08:50:17 GMT
      #
      def expires(amount, *values)
        values << {} unless values.last.kind_of?(Hash)

        if amount.is_a? Integer
          time    = Time.now + amount.to_i
          max_age = amount
        else
          time    = time_for amount
          max_age = time - Time.now
        end

        values.last.merge!(:max_age => max_age)
        cache_control(*values)

        response['Expires'] = time.httpdate
      end

      # Set the last modified time of the resource (HTTP 'Last-Modified' header)
      # and halt if conditional GET matches. The +time+ argument is a Time,
      # DateTime, or other object that responds to +to_time+.
      #
      # When the current request includes an 'If-Modified-Since' header that is
      # equal or later than the time specified, execution is immediately halted
      # with a '304 Not Modified' response.
      def last_modified(time)
        return unless time
        time = time_for time
        response['Last-Modified'] = time.httpdate
        return if env['HTTP_IF_NONE_MATCH']

        if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
          # compare based on seconds since epoch
          since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
          halt 304 if since >= time.to_i
        end

        if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
          # compare based on seconds since epoch
          since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
          halt 412 if since < time.to_i
        end
      rescue ArgumentError
      end

      ETAG_KINDS = [:strong, :weak]
      # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
      # GET matches. The +value+ argument is an identifier that uniquely
      # identifies the current version of the resource. The +kind+ argument
      # indicates whether the etag should be used as a :strong (default) or :weak
      # cache validator.
      #
      # When the current request includes an 'If-None-Match' header with a
      # matching etag, execution is immediately halted. If the request method is
      # GET or HEAD, a '304 Not Modified' response is sent.
      def etag(value, options = {})
        # Before touching this code, please double check RFC 2616 14.24 and 14.26.
        options      = {:kind => options} unless Hash === options
        kind         = options[:kind] || :strong
        new_resource = options.fetch(:new_resource) { request.post? }

        unless ETAG_KINDS.include?(kind)
          raise ArgumentError, ":strong or :weak expected"
        end

        value = '"%s"' % value
        value = "W/#{value}" if kind == :weak
        response['ETag'] = value

        if success? or status == 304
          if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
            halt(request.safe? ? 304 : 412)
          end

          if env['HTTP_IF_MATCH']
            halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
          end
        end
      end

      # Sugar for redirect (example:  redirect back)
      def back
        request.referer
      end

      # whether or not the status is set to 1xx
      def informational?
        status.between? 100, 199
      end

      # whether or not the status is set to 2xx
      def success?
        status.between? 200, 299
      end

      # whether or not the status is set to 3xx
      def redirect?
        status.between? 300, 399
      end

      # whether or not the status is set to 4xx
      def client_error?
        status.between? 400, 499
      end

      # whether or not the status is set to 5xx
      def server_error?
        status.between? 500, 599
      end

      # whether or not the status is set to 404
      def not_found?
        status == 404
      end

      # Generates a Time object from the given value.
      # Used by #expires and #last_modified.
      def time_for(value)
        if value.respond_to? :to_time
          value.to_time
        elsif value.is_a? Time
          value
        elsif value.respond_to? :new_offset
          # DateTime#to_time does the same on 1.9
          d = value.new_offset 0
          t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
          t.getlocal
        elsif value.respond_to? :mday
          # Date#to_time does the same on 1.9
          Time.local(value.year, value.mon, value.mday)
        elsif value.is_a? Numeric
          Time.at value
        else
          Time.parse value.to_s
        end
      rescue ArgumentError => boom
        raise boom
      rescue Exception
        raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
      end

      private

      # Helper method checking if a ETag value list includes the current ETag.
      def etag_matches?(list, new_resource = request.post?)
        return !new_resource if list == '*'
        list.to_s.split(/\s*,\s*/).include? response['ETag']
      end

      def with_params(temp_params)
        original, @params = @params, temp_params
        yield
      ensure
        @params = original if original
      end
    end

### Public Instance Methods

<<(data) click to toggle source

    # File lib/sinatra/base.rb, line 410
    def <<(data)
      @scheduler.schedule { @front.call(data.to_s) }
      self
    end

back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb, line 557
    def back
      request.referer
    end

cache_control(*values) click to toggle source

Specify response freshness policy for HTTP caches (Cache-Control header). Any number of non-value directives (:public, :private, :no_cache, :no_store, :must_revalidate, :proxy_revalidate) may be passed along with a Hash of value directives (:max_age, :min_stale, :s_max_age).

    cache_control :public, :must_revalidate, :max_age => 60
    => Cache-Control: public, must-revalidate, max-age=60

See RFC 2616 / 14.9 for more on standard cache control directives: tools.ietf.org/html/rfc2616#section-14.9.1

    # File lib/sinatra/base.rb, line 449
    def cache_control(*values)
      if values.last.kind_of?(Hash)
        hash = values.pop
        hash.reject! { |k,v| v == false }
        hash.reject! { |k,v| values << k if v == true }
      else
        hash = {}
      end

      values.map! { |value| value.to_s.tr('_','-') }
      hash.each do |key, value|
        key = key.to_s.tr('_', '-')
        value = value.to_i if key == "max-age"
        values << "#{key}=#{value}"
      end

      response['Cache-Control'] = values.join(', ') if values.any?
    end

callback(&block) click to toggle source

    # File lib/sinatra/base.rb, line 415
    def callback(&block)
      return yield if @closed
      @callbacks << block
    end

client_error?() click to toggle source

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb, line 577
    def client_error?
      status.between? 400, 499
    end

close() click to toggle source

    # File lib/sinatra/base.rb, line 392
    def close
      return if @closed
      @closed = true
      @scheduler.schedule { @callbacks.each { |c| c.call }}
    end

closed?() click to toggle source

    # File lib/sinatra/base.rb, line 422
    def closed?
      @closed
    end

each(&front) click to toggle source

    # File lib/sinatra/base.rb, line 398
    def each(&front)
      @front = front
      @scheduler.defer do
        begin
          @back.call(self)
        rescue Exception => e
          @scheduler.schedule { raise e }
        end
        close unless @keep_open
      end
    end

etag(value, options = {}) click to toggle source

Set the response entity tag (HTTP ‘ETag’ header) and halt if conditional GET matches. The `value` argument is an identifier that uniquely identifies the current version of the resource. The `kind` argument indicates whether the etag should be used as a :strong (default) or :weak cache validator.

When the current request includes an ‘If-None-Match’ header with a matching etag, execution is immediately halted. If the request method is GET or HEAD, a ‘304 Not Modified’ response is sent.

    # File lib/sinatra/base.rb, line 531
    def etag(value, options = {})
      # Before touching this code, please double check RFC 2616 14.24 and 14.26.
      options      = {:kind => options} unless Hash === options
      kind         = options[:kind] || :strong
      new_resource = options.fetch(:new_resource) { request.post? }

      unless ETAG_KINDS.include?(kind)
        raise ArgumentError, ":strong or :weak expected"
      end

      value = '"%s"' % value
      value = "W/#{value}" if kind == :weak
      response['ETag'] = value

      if success? or status == 304
        if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
          halt(request.safe? ? 304 : 412)
        end

        if env['HTTP_IF_MATCH']
          halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
        end
      end
    end

etag_matches?(list, new_resource = request.post?) click to toggle source

Helper method checking if a ETag value list includes the current ETag.

    # File lib/sinatra/base.rb, line 620
    def etag_matches?(list, new_resource = request.post?)
      return !new_resource if list == '*'
      list.to_s.split(/\s*,\s*/).include? response['ETag']
    end

expires(amount, *values) click to toggle source

Set the Expires header and Cache-Control/max-age directive. Amount can be an integer number of seconds in the future or a Time object indicating when the response should be considered “stale”. The remaining “values” arguments are passed to the cache_control helper:

    expires 500, :public, :must_revalidate
    => Cache-Control: public, must-revalidate, max-age=60
    => Expires: Mon, 08 Jun 2009 08:50:17 GMT

    # File lib/sinatra/base.rb, line 477
    def expires(amount, *values)
      values << {} unless values.last.kind_of?(Hash)

      if amount.is_a? Integer
        time    = Time.now + amount.to_i
        max_age = amount
      else
        time    = time_for amount
        max_age = time - Time.now
      end

      values.last.merge!(:max_age => max_age)
      cache_control(*values)

      response['Expires'] = time.httpdate
    end

informational?() click to toggle source

whether or not the status is set to 1xx

    # File lib/sinatra/base.rb, line 562
    def informational?
      status.between? 100, 199
    end

last_modified(time) click to toggle source

Set the last modified time of the resource (HTTP ‘Last-Modified’ header) and halt if conditional GET matches. The `time` argument is a Time, DateTime, or other object that responds to `to_time`.

When the current request includes an ‘If-Modified-Since’ header that is equal or later than the time specified, execution is immediately halted with a ‘304 Not Modified’ response.

    # File lib/sinatra/base.rb, line 501
    def last_modified(time)
      return unless time
      time = time_for time
      response['Last-Modified'] = time.httpdate
      return if env['HTTP_IF_NONE_MATCH']

      if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
        # compare based on seconds since epoch
        since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
        halt 304 if since >= time.to_i
      end

      if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
        # compare based on seconds since epoch
        since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
        halt 412 if since < time.to_i
      end
    rescue ArgumentError
    end

not_found?() click to toggle source

whether or not the status is set to 404

    # File lib/sinatra/base.rb, line 587
    def not_found?
      status == 404
    end

redirect?() click to toggle source

whether or not the status is set to 3xx

    # File lib/sinatra/base.rb, line 572
    def redirect?
      status.between? 300, 399
    end

server_error?() click to toggle source

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb, line 582
    def server_error?
      status.between? 500, 599
    end

stream(keep_open = false) click to toggle source

Allows to start sending data to the client even though later parts of the response body have not yet been generated.

The close parameter specifies whether Stream#close should be called after the block has been executed. This is only relevant for evented servers like Thin or Rainbows.

    # File lib/sinatra/base.rb, line 433
    def stream(keep_open = false)
      scheduler = env['async.callback'] ? EventMachine : Stream
      current   = @params.dup
      body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
    end

success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb, line 567
    def success?
      status.between? 200, 299
    end

time_for(value) click to toggle source

Generates a Time object from the given value. Used by expires and last_modified.

    # File lib/sinatra/base.rb, line 593
    def time_for(value)
      if value.respond_to? :to_time
        value.to_time
      elsif value.is_a? Time
        value
      elsif value.respond_to? :new_offset
        # DateTime#to_time does the same on 1.9
        d = value.new_offset 0
        t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
        t.getlocal
      elsif value.respond_to? :mday
        # Date#to_time does the same on 1.9
        Time.local(value.year, value.mon, value.mday)
      elsif value.is_a? Numeric
        Time.at value
      else
        Time.parse value.to_s
      end
    rescue ArgumentError => boom
      raise boom
    rescue Exception
      raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
    end

with_params(temp_params) click to toggle source

    # File lib/sinatra/base.rb, line 625
    def with_params(temp_params)
      original, @params = @params, temp_params
      yield
    ensure
      @params = original if original
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.