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
453   def self.defer(*)    yield end
454 
455   def initialize(scheduler = self.class, keep_open = false, &back)
456     @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
457     @callbacks, @closed = [], false
458   end
459 
460   def close
461     return if closed?
462     @closed = true
463     @scheduler.schedule { @callbacks.each { |c| c.call } }
464   end
465 
466   def each(&front)
467     @front = front
468     @scheduler.defer do
469       begin
470         @back.call(self)
471       rescue Exception => e
472         @scheduler.schedule { raise e }
473       end
474       close unless @keep_open
475     end
476   end
477 
478   def <<(data)
479     @scheduler.schedule { @front.call(data.to_s) }
480     self
481   end
482 
483   def callback(&block)
484     return yield if closed?
485     @callbacks << block
486   end
487 
488   alias errback callback
489 
490   def closed?
491     @closed
492   end
493 end
helpers(*extensions, &block) click to toggle source

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

     # File lib/sinatra/base.rb
2041 def self.helpers(*extensions, &block)
2042   Delegator.target.helpers(*extensions, &block)
2043 end
new(scheduler = self.class, keep_open = false, &back) click to toggle source
    # File lib/sinatra/base.rb
455 def initialize(scheduler = self.class, keep_open = false, &back)
456   @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
457   @callbacks, @closed = [], false
458 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
2029 def self.new(base = Base, &block)
2030   base = Class.new(base)
2031   base.class_eval(&block) if block_given?
2032   base
2033 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
2046 def self.use(*args, &block)
2047   Delegator.target.use(*args, &block)
2048 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
478 def <<(data)
479   @scheduler.schedule { @front.call(data.to_s) }
480   self
481 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
625 def back
626   request.referer
627 end
bad_request?() click to toggle source

whether or not the status is set to 400

    # File lib/sinatra/base.rb
660 def bad_request?
661   status == 400
662 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
517 def cache_control(*values)
518   if values.last.kind_of?(Hash)
519     hash = values.pop
520     hash.reject! { |k, v| v == false }
521     hash.reject! { |k, v| values << k if v == true }
522   else
523     hash = {}
524   end
525 
526   values.map! { |value| value.to_s.tr('_','-') }
527   hash.each do |key, value|
528     key = key.to_s.tr('_', '-')
529     value = value.to_i if ['max-age', 's-maxage'].include? key
530     values << "#{key}=#{value}"
531   end
532 
533   response['Cache-Control'] = values.join(', ') if values.any?
534 end
callback() { || ... } click to toggle source
    # File lib/sinatra/base.rb
483 def callback(&block)
484   return yield if closed?
485   @callbacks << block
486 end
client_error?() click to toggle source

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
645 def client_error?
646   status.between? 400, 499
647 end
close() click to toggle source
    # File lib/sinatra/base.rb
460 def close
461   return if closed?
462   @closed = true
463   @scheduler.schedule { @callbacks.each { |c| c.call } }
464 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
490 def closed?
491   @closed
492 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
466 def each(&front)
467   @front = front
468   @scheduler.defer do
469     begin
470       @back.call(self)
471     rescue Exception => e
472       @scheduler.schedule { raise e }
473     end
474     close unless @keep_open
475   end
476 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
599 def etag(value, options = {})
600   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
601   options      = {:kind => options} unless Hash === options
602   kind         = options[:kind] || :strong
603   new_resource = options.fetch(:new_resource) { request.post? }
604 
605   unless ETAG_KINDS.include?(kind)
606     raise ArgumentError, ":strong or :weak expected"
607   end
608 
609   value = '"%s"' % value
610   value = "W/#{value}" if kind == :weak
611   response['ETag'] = value
612 
613   if success? or status == 304
614     if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
615       halt(request.safe? ? 304 : 412)
616     end
617 
618     if env['HTTP_IF_MATCH']
619       halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
620     end
621   end
622 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
683 def etag_matches?(list, new_resource = request.post?)
684   return !new_resource if list == '*'
685   list.to_s.split(/\s*,\s*/).include? response['ETag']
686 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
545 def expires(amount, *values)
546   values << {} unless values.last.kind_of?(Hash)
547 
548   if amount.is_a? Integer
549     time    = Time.now + amount.to_i
550     max_age = amount
551   else
552     time    = time_for amount
553     max_age = time - Time.now
554   end
555 
556   values.last.merge!(:max_age => max_age)
557   cache_control(*values)
558 
559   response['Expires'] = time.httpdate
560 end
informational?() click to toggle source

whether or not the status is set to 1xx

    # File lib/sinatra/base.rb
630 def informational?
631   status.between? 100, 199
632 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
569 def last_modified(time)
570   return unless time
571   time = time_for time
572   response['Last-Modified'] = time.httpdate
573   return if env['HTTP_IF_NONE_MATCH']
574 
575   if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
576     # compare based on seconds since epoch
577     since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
578     halt 304 if since >= time.to_i
579   end
580 
581   if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
582     # compare based on seconds since epoch
583     since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
584     halt 412 if since < time.to_i
585   end
586 rescue ArgumentError
587 end
not_found?() click to toggle source

whether or not the status is set to 404

    # File lib/sinatra/base.rb
655 def not_found?
656   status == 404
657 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
650 def server_error?
651   status.between? 500, 599
652 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
501 def stream(keep_open = false)
502   scheduler = env['async.callback'] ? EventMachine : Stream
503   current   = @params.dup
504   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
505 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
635 def success?
636   status.between? 200, 299
637 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
666 def time_for(value)
667   if value.is_a? Numeric
668     Time.at value
669   elsif value.respond_to? :to_s
670     Time.parse value.to_s
671   else
672     value.to_time
673   end
674 rescue ArgumentError => boom
675   raise boom
676 rescue Exception
677   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
678 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
688 def with_params(temp_params)
689   original, @params = @params, temp_params
690   yield
691 ensure
692   @params = original if original
693 end