class 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(*) { |end| ... } click to toggle source
    # File lib/sinatra/base.rb
434   def self.defer(*)    yield end
435 
436   def initialize(scheduler = self.class, keep_open = false, &back)
437     @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
438     @callbacks, @closed = [], false
439   end
440 
441   def close
442     return if closed?
443     @closed = true
444     @scheduler.schedule { @callbacks.each { |c| c.call } }
445   end
446 
447   def each(&front)
448     @front = front
449     @scheduler.defer do
450       begin
451         @back.call(self)
452       rescue Exception => e
453         @scheduler.schedule { raise e }
454       end
455       close unless @keep_open
456     end
457   end
458 
459   def <<(data)
460     @scheduler.schedule { @front.call(data.to_s) }
461     self
462   end
463 
464   def callback(&block)
465     return yield if closed?
466     @callbacks << block
467   end
468 
469   alias errback callback
470 
471   def closed?
472     @closed
473   end
474 end
helpers(*extensions, &block) click to toggle source

Include the helper modules provided in Sinatra’s request context.

     # File lib/sinatra/base.rb
2019 def self.helpers(*extensions, &block)
2020   Delegator.target.helpers(*extensions, &block)
2021 end
new(scheduler = self.class, keep_open = false, &back) click to toggle source
    # File lib/sinatra/base.rb
436 def initialize(scheduler = self.class, keep_open = false, &back)
437   @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
438   @callbacks, @closed = [], false
439 end
new(base = Base, &block) click to toggle source

Create a new Sinatra application; the block is evaluated in the class scope.

     # File lib/sinatra/base.rb
2007 def self.new(base = Base, &block)
2008   base = Class.new(base)
2009   base.class_eval(&block) if block_given?
2010   base
2011 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

     # File lib/sinatra/base.rb
2014 def self.register(*extensions, &block)
2015   Delegator.target.register(*extensions, &block)
2016 end
schedule(*) { |end| ... } click to toggle source
    # File lib/sinatra/base.rb
433     def self.schedule(*) yield end
434     def self.defer(*)    yield end
435 
436     def initialize(scheduler = self.class, keep_open = false, &back)
437       @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
438       @callbacks, @closed = [], false
439     end
440 
441     def close
442       return if closed?
443       @closed = true
444       @scheduler.schedule { @callbacks.each { |c| c.call } }
445     end
446 
447     def each(&front)
448       @front = front
449       @scheduler.defer do
450         begin
451           @back.call(self)
452         rescue Exception => e
453           @scheduler.schedule { raise e }
454         end
455         close unless @keep_open
456       end
457     end
458 
459     def <<(data)
460       @scheduler.schedule { @front.call(data.to_s) }
461       self
462     end
463 
464     def callback(&block)
465       return yield if closed?
466       @callbacks << block
467     end
468 
469     alias errback callback
470 
471     def closed?
472       @closed
473     end
474   end
475 
476   # Allows to start sending data to the client even though later parts of
477   # the response body have not yet been generated.
478   #
479   # The close parameter specifies whether Stream#close should be called
480   # after the block has been executed. This is only relevant for evented
481   # servers like Rainbows.
482   def stream(keep_open = false)
483     scheduler = env['async.callback'] ? EventMachine : Stream
484     current   = @params.dup
485     body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
486   end
487 
488   # Specify response freshness policy for HTTP caches (Cache-Control header).
489   # Any number of non-value directives (:public, :private, :no_cache,
490   # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
491   # a Hash of value directives (:max_age, :s_maxage).
492   #
493   #   cache_control :public, :must_revalidate, :max_age => 60
494   #   => Cache-Control: public, must-revalidate, max-age=60
495   #
496   # See RFC 2616 / 14.9 for more on standard cache control directives:
497   # http://tools.ietf.org/html/rfc2616#section-14.9.1
498   def cache_control(*values)
499     if values.last.kind_of?(Hash)
500       hash = values.pop
501       hash.reject! { |k, v| v == false }
502       hash.reject! { |k, v| values << k if v == true }
503     else
504       hash = {}
505     end
506 
507     values.map! { |value| value.to_s.tr('_','-') }
508     hash.each do |key, value|
509       key = key.to_s.tr('_', '-')
510       value = value.to_i if ['max-age', 's-maxage'].include? key
511       values << "#{key}=#{value}"
512     end
513 
514     response['Cache-Control'] = values.join(', ') if values.any?
515   end
516 
517   # Set the Expires header and Cache-Control/max-age directive. Amount
518   # can be an integer number of seconds in the future or a Time object
519   # indicating when the response should be considered "stale". The remaining
520   # "values" arguments are passed to the #cache_control helper:
521   #
522   #   expires 500, :public, :must_revalidate
523   #   => Cache-Control: public, must-revalidate, max-age=500
524   #   => Expires: Mon, 08 Jun 2009 08:50:17 GMT
525   #
526   def expires(amount, *values)
527     values << {} unless values.last.kind_of?(Hash)
528 
529     if amount.is_a? Integer
530       time    = Time.now + amount.to_i
531       max_age = amount
532     else
533       time    = time_for amount
534       max_age = time - Time.now
535     end
536 
537     values.last.merge!(:max_age => max_age)
538     cache_control(*values)
539 
540     response['Expires'] = time.httpdate
541   end
542 
543   # Set the last modified time of the resource (HTTP 'Last-Modified' header)
544   # and halt if conditional GET matches. The +time+ argument is a Time,
545   # DateTime, or other object that responds to +to_time+.
546   #
547   # When the current request includes an 'If-Modified-Since' header that is
548   # equal or later than the time specified, execution is immediately halted
549   # with a '304 Not Modified' response.
550   def last_modified(time)
551     return unless time
552     time = time_for time
553     response['Last-Modified'] = time.httpdate
554     return if env['HTTP_IF_NONE_MATCH']
555 
556     if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
557       # compare based on seconds since epoch
558       since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
559       halt 304 if since >= time.to_i
560     end
561 
562     if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
563       # compare based on seconds since epoch
564       since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
565       halt 412 if since < time.to_i
566     end
567   rescue ArgumentError
568   end
569 
570   ETAG_KINDS = [:strong, :weak]
571   # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
572   # GET matches. The +value+ argument is an identifier that uniquely
573   # identifies the current version of the resource. The +kind+ argument
574   # indicates whether the etag should be used as a :strong (default) or :weak
575   # cache validator.
576   #
577   # When the current request includes an 'If-None-Match' header with a
578   # matching etag, execution is immediately halted. If the request method is
579   # GET or HEAD, a '304 Not Modified' response is sent.
580   def etag(value, options = {})
581     # Before touching this code, please double check RFC 2616 14.24 and 14.26.
582     options      = {:kind => options} unless Hash === options
583     kind         = options[:kind] || :strong
584     new_resource = options.fetch(:new_resource) { request.post? }
585 
586     unless ETAG_KINDS.include?(kind)
587       raise ArgumentError, ":strong or :weak expected"
588     end
589 
590     value = '"%s"' % value
591     value = "W/#{value}" if kind == :weak
592     response['ETag'] = value
593 
594     if success? or status == 304
595       if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
596         halt(request.safe? ? 304 : 412)
597       end
598 
599       if env['HTTP_IF_MATCH']
600         halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
601       end
602     end
603   end
604 
605   # Sugar for redirect (example:  redirect back)
606   def back
607     request.referer
608   end
609 
610   # whether or not the status is set to 1xx
611   def informational?
612     status.between? 100, 199
613   end
614 
615   # whether or not the status is set to 2xx
616   def success?
617     status.between? 200, 299
618   end
619 
620   # whether or not the status is set to 3xx
621   def redirect?
622     status.between? 300, 399
623   end
624 
625   # whether or not the status is set to 4xx
626   def client_error?
627     status.between? 400, 499
628   end
629 
630   # whether or not the status is set to 5xx
631   def server_error?
632     status.between? 500, 599
633   end
634 
635   # whether or not the status is set to 404
636   def not_found?
637     status == 404
638   end
639 
640   # whether or not the status is set to 400
641   def bad_request?
642     status == 400
643   end
644 
645   # Generates a Time object from the given value.
646   # Used by #expires and #last_modified.
647   def time_for(value)
648     if value.is_a? Numeric
649       Time.at value
650     elsif value.respond_to? :to_s
651       Time.parse value.to_s
652     else
653       value.to_time
654     end
655   rescue ArgumentError => boom
656     raise boom
657   rescue Exception
658     raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
659   end
660 
661   private
662 
663   # Helper method checking if a ETag value list includes the current ETag.
664   def etag_matches?(list, new_resource = request.post?)
665     return !new_resource if list == '*'
666     list.to_s.split(/\s*,\s*/).include? response['ETag']
667   end
668 
669   def with_params(temp_params)
670     original, @params = @params, temp_params
671     yield
672   ensure
673     @params = original if original
674   end
675 end
use(*args, &block) click to toggle source

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
2024 def self.use(*args, &block)
2025   Delegator.target.use(*args, &block)
2026 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
459 def <<(data)
460   @scheduler.schedule { @front.call(data.to_s) }
461   self
462 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
606 def back
607   request.referer
608 end
bad_request?() click to toggle source

whether or not the status is set to 400

    # File lib/sinatra/base.rb
641 def bad_request?
642   status == 400
643 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, :s_maxage).

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
498 def cache_control(*values)
499   if values.last.kind_of?(Hash)
500     hash = values.pop
501     hash.reject! { |k, v| v == false }
502     hash.reject! { |k, v| values << k if v == true }
503   else
504     hash = {}
505   end
506 
507   values.map! { |value| value.to_s.tr('_','-') }
508   hash.each do |key, value|
509     key = key.to_s.tr('_', '-')
510     value = value.to_i if ['max-age', 's-maxage'].include? key
511     values << "#{key}=#{value}"
512   end
513 
514   response['Cache-Control'] = values.join(', ') if values.any?
515 end
callback() { || ... } click to toggle source
    # File lib/sinatra/base.rb
464 def callback(&block)
465   return yield if closed?
466   @callbacks << block
467 end
client_error?() click to toggle source

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
626 def client_error?
627   status.between? 400, 499
628 end
close() click to toggle source
    # File lib/sinatra/base.rb
441 def close
442   return if closed?
443   @closed = true
444   @scheduler.schedule { @callbacks.each { |c| c.call } }
445 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
471 def closed?
472   @closed
473 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
447 def each(&front)
448   @front = front
449   @scheduler.defer do
450     begin
451       @back.call(self)
452     rescue Exception => e
453       @scheduler.schedule { raise e }
454     end
455     close unless @keep_open
456   end
457 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
580 def etag(value, options = {})
581   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
582   options      = {:kind => options} unless Hash === options
583   kind         = options[:kind] || :strong
584   new_resource = options.fetch(:new_resource) { request.post? }
585 
586   unless ETAG_KINDS.include?(kind)
587     raise ArgumentError, ":strong or :weak expected"
588   end
589 
590   value = '"%s"' % value
591   value = "W/#{value}" if kind == :weak
592   response['ETag'] = value
593 
594   if success? or status == 304
595     if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
596       halt(request.safe? ? 304 : 412)
597     end
598 
599     if env['HTTP_IF_MATCH']
600       halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
601     end
602   end
603 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
664 def etag_matches?(list, new_resource = request.post?)
665   return !new_resource if list == '*'
666   list.to_s.split(/\s*,\s*/).include? response['ETag']
667 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=500
=> Expires: Mon, 08 Jun 2009 08:50:17 GMT
    # File lib/sinatra/base.rb
526 def expires(amount, *values)
527   values << {} unless values.last.kind_of?(Hash)
528 
529   if amount.is_a? Integer
530     time    = Time.now + amount.to_i
531     max_age = amount
532   else
533     time    = time_for amount
534     max_age = time - Time.now
535   end
536 
537   values.last.merge!(:max_age => max_age)
538   cache_control(*values)
539 
540   response['Expires'] = time.httpdate
541 end
informational?() click to toggle source

whether or not the status is set to 1xx

    # File lib/sinatra/base.rb
611 def informational?
612   status.between? 100, 199
613 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
550 def last_modified(time)
551   return unless time
552   time = time_for time
553   response['Last-Modified'] = time.httpdate
554   return if env['HTTP_IF_NONE_MATCH']
555 
556   if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
557     # compare based on seconds since epoch
558     since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
559     halt 304 if since >= time.to_i
560   end
561 
562   if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
563     # compare based on seconds since epoch
564     since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
565     halt 412 if since < time.to_i
566   end
567 rescue ArgumentError
568 end
not_found?() click to toggle source

whether or not the status is set to 404

    # File lib/sinatra/base.rb
636 def not_found?
637   status == 404
638 end
redirect?() click to toggle source

whether or not the status is set to 3xx

    # File lib/sinatra/base.rb
621 def redirect?
622   status.between? 300, 399
623 end
server_error?() click to toggle source

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
631 def server_error?
632   status.between? 500, 599
633 end
stream(keep_open = false) { |out| ... } 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 Rainbows.

    # File lib/sinatra/base.rb
482 def stream(keep_open = false)
483   scheduler = env['async.callback'] ? EventMachine : Stream
484   current   = @params.dup
485   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
486 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
616 def success?
617   status.between? 200, 299
618 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
647 def time_for(value)
648   if value.is_a? Numeric
649     Time.at value
650   elsif value.respond_to? :to_s
651     Time.parse value.to_s
652   else
653     value.to_time
654   end
655 rescue ArgumentError => boom
656   raise boom
657 rescue Exception
658   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
659 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
669 def with_params(temp_params)
670   original, @params = @params, temp_params
671   yield
672 ensure
673   @params = original if original
674 end