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
462   def self.defer(*)    yield end
463 
464   def initialize(scheduler = self.class, keep_open = false, &back)
465     @back = back.to_proc
466     @scheduler = scheduler
467     @keep_open = keep_open
468     @callbacks = []
469     @closed = false
470   end
471 
472   def close
473     return if closed?
474 
475     @closed = true
476     @scheduler.schedule { @callbacks.each { |c| c.call } }
477   end
478 
479   def each(&front)
480     @front = front
481     @scheduler.defer do
482       begin
483         @back.call(self)
484       rescue Exception => e
485         @scheduler.schedule { raise e }
486       ensure
487         close unless @keep_open
488       end
489     end
490   end
491 
492   def <<(data)
493     @scheduler.schedule { @front.call(data.to_s) }
494     self
495   end
496 
497   def callback(&block)
498     return yield if closed?
499 
500     @callbacks << block
501   end
502 
503   alias errback callback
504 
505   def closed?
506     @closed
507   end
508 end
helpers(*extensions, &block) click to toggle source

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

     # File lib/sinatra/base.rb
2159 def self.helpers(*extensions, &block)
2160   Delegator.target.helpers(*extensions, &block)
2161 end
new(scheduler = self.class, keep_open = false, &back) click to toggle source
    # File lib/sinatra/base.rb
464 def initialize(scheduler = self.class, keep_open = false, &back)
465   @back = back.to_proc
466   @scheduler = scheduler
467   @keep_open = keep_open
468   @callbacks = []
469   @closed = false
470 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
2147 def self.new(base = Base, &block)
2148   base = Class.new(base)
2149   base.class_eval(&block) if block_given?
2150   base
2151 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
2164 def self.use(*args, &block)
2165   Delegator.target.use(*args, &block)
2166 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
492 def <<(data)
493   @scheduler.schedule { @front.call(data.to_s) }
494   self
495 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
653 def back
654   request.referer
655 end
bad_request?() click to toggle source

whether or not the status is set to 400

    # File lib/sinatra/base.rb
688 def bad_request?
689   status == 400
690 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
540 def cache_control(*values)
541   if values.last.is_a?(Hash)
542     hash = values.pop
543     hash.reject! { |_k, v| v == false }
544     hash.reject! { |k, v| values << k if v == true }
545   else
546     hash = {}
547   end
548 
549   values.map! { |value| value.to_s.tr('_', '-') }
550   hash.each do |key, value|
551     key = key.to_s.tr('_', '-')
552     value = value.to_i if %w[max-age s-maxage].include? key
553     values << "#{key}=#{value}"
554   end
555 
556   response['Cache-Control'] = values.join(', ') if values.any?
557 end
callback() { || ... } click to toggle source
    # File lib/sinatra/base.rb
497 def callback(&block)
498   return yield if closed?
499 
500   @callbacks << block
501 end
client_error?() click to toggle source

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
673 def client_error?
674   status.between? 400, 499
675 end
close() click to toggle source
    # File lib/sinatra/base.rb
472 def close
473   return if closed?
474 
475   @closed = true
476   @scheduler.schedule { @callbacks.each { |c| c.call } }
477 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
505 def closed?
506   @closed
507 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
479 def each(&front)
480   @front = front
481   @scheduler.defer do
482     begin
483       @back.call(self)
484     rescue Exception => e
485       @scheduler.schedule { raise e }
486     ensure
487       close unless @keep_open
488     end
489   end
490 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
623 def etag(value, options = {})
624   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
625   options      = { kind: options } unless Hash === options
626   kind         = options[:kind] || :strong
627   new_resource = options.fetch(:new_resource) { request.post? }
628 
629   unless ETAG_KINDS.include?(kind)
630     raise ArgumentError, ':strong or :weak expected'
631   end
632 
633   value = format('"%s"', value)
634   value = "W/#{value}" if kind == :weak
635   response['ETag'] = value
636 
637   return unless success? || status == 304
638 
639   if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
640     halt(request.safe? ? 304 : 412)
641   end
642 
643   if env['HTTP_IF_MATCH']
644     return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
645 
646     halt 412
647   end
648 
649   nil
650 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
711 def etag_matches?(list, new_resource = request.post?)
712   return !new_resource if list == '*'
713 
714   list.to_s.split(/\s*,\s*/).include? response['ETag']
715 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
568 def expires(amount, *values)
569   values << {} unless values.last.is_a?(Hash)
570 
571   if amount.is_a? Integer
572     time    = Time.now + amount.to_i
573     max_age = amount
574   else
575     time    = time_for amount
576     max_age = time - Time.now
577   end
578 
579   values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
580   cache_control(*values)
581 
582   response['Expires'] = time.httpdate
583 end
informational?() click to toggle source

whether or not the status is set to 1xx

    # File lib/sinatra/base.rb
658 def informational?
659   status.between? 100, 199
660 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
592 def last_modified(time)
593   return unless time
594 
595   time = time_for time
596   response['Last-Modified'] = time.httpdate
597   return if env['HTTP_IF_NONE_MATCH']
598 
599   if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
600     # compare based on seconds since epoch
601     since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
602     halt 304 if since >= time.to_i
603   end
604 
605   if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
606     # compare based on seconds since epoch
607     since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
608     halt 412 if since < time.to_i
609   end
610 rescue ArgumentError
611 end
not_found?() click to toggle source

whether or not the status is set to 404

    # File lib/sinatra/base.rb
683 def not_found?
684   status == 404
685 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
678 def server_error?
679   status.between? 500, 599
680 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.

    # File lib/sinatra/base.rb
515 def stream(keep_open = false)
516   scheduler = env['async.callback'] ? EventMachine : Stream
517   current   = @params.dup
518   stream = if scheduler == Stream  && keep_open
519     Stream.new(scheduler, false) do |out|
520       until out.closed?
521         with_params(current) { yield(out) }
522       end
523     end
524   else
525     Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
526   end
527   body stream
528 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
663 def success?
664   status.between? 200, 299
665 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
694 def time_for(value)
695   if value.is_a? Numeric
696     Time.at value
697   elsif value.respond_to? :to_s
698     Time.parse value.to_s
699   else
700     value.to_time
701   end
702 rescue ArgumentError => e
703   raise e
704 rescue Exception
705   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
706 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
717 def with_params(temp_params)
718   original = @params
719   @params = temp_params
720   yield
721 ensure
722   @params = original if original
723 end