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

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

     # File lib/sinatra/base.rb
2107 def self.helpers(*extensions, &block)
2108   Delegator.target.helpers(*extensions, &block)
2109 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 = back.to_proc
457   @scheduler = scheduler
458   @keep_open = keep_open
459   @callbacks = []
460   @closed = false
461 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
2095 def self.new(base = Base, &block)
2096   base = Class.new(base)
2097   base.class_eval(&block) if block_given?
2098   base
2099 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
2112 def self.use(*args, &block)
2113   Delegator.target.use(*args, &block)
2114 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
483 def <<(data)
484   @scheduler.schedule { @front.call(data.to_s) }
485   self
486 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
644 def back
645   request.referer
646 end
bad_request?() click to toggle source

whether or not the status is set to 400

    # File lib/sinatra/base.rb
679 def bad_request?
680   status == 400
681 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
531 def cache_control(*values)
532   if values.last.is_a?(Hash)
533     hash = values.pop
534     hash.reject! { |_k, v| v == false }
535     hash.reject! { |k, v| values << k if v == true }
536   else
537     hash = {}
538   end
539 
540   values.map! { |value| value.to_s.tr('_', '-') }
541   hash.each do |key, value|
542     key = key.to_s.tr('_', '-')
543     value = value.to_i if %w[max-age s-maxage].include? key
544     values << "#{key}=#{value}"
545   end
546 
547   response['Cache-Control'] = values.join(', ') if values.any?
548 end
callback() { || ... } click to toggle source
    # File lib/sinatra/base.rb
488 def callback(&block)
489   return yield if closed?
490 
491   @callbacks << block
492 end
client_error?() click to toggle source

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
664 def client_error?
665   status.between? 400, 499
666 end
close() click to toggle source
    # File lib/sinatra/base.rb
463 def close
464   return if closed?
465 
466   @closed = true
467   @scheduler.schedule { @callbacks.each { |c| c.call } }
468 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
496 def closed?
497   @closed
498 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
470 def each(&front)
471   @front = front
472   @scheduler.defer do
473     begin
474       @back.call(self)
475     rescue Exception => e
476       @scheduler.schedule { raise e }
477     ensure
478       close unless @keep_open
479     end
480   end
481 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
614 def etag(value, options = {})
615   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
616   options      = { kind: options } unless Hash === options
617   kind         = options[:kind] || :strong
618   new_resource = options.fetch(:new_resource) { request.post? }
619 
620   unless ETAG_KINDS.include?(kind)
621     raise ArgumentError, ':strong or :weak expected'
622   end
623 
624   value = format('"%s"', value)
625   value = "W/#{value}" if kind == :weak
626   response['ETag'] = value
627 
628   return unless success? || status == 304
629 
630   if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
631     halt(request.safe? ? 304 : 412)
632   end
633 
634   if env['HTTP_IF_MATCH']
635     return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
636 
637     halt 412
638   end
639 
640   nil
641 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
702 def etag_matches?(list, new_resource = request.post?)
703   return !new_resource if list == '*'
704 
705   list.to_s.split(/\s*,\s*/).include? response['ETag']
706 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
559 def expires(amount, *values)
560   values << {} unless values.last.is_a?(Hash)
561 
562   if amount.is_a? Integer
563     time    = Time.now + amount.to_i
564     max_age = amount
565   else
566     time    = time_for amount
567     max_age = time - Time.now
568   end
569 
570   values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
571   cache_control(*values)
572 
573   response['Expires'] = time.httpdate
574 end
informational?() click to toggle source

whether or not the status is set to 1xx

    # File lib/sinatra/base.rb
649 def informational?
650   status.between? 100, 199
651 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
583 def last_modified(time)
584   return unless time
585 
586   time = time_for time
587   response['Last-Modified'] = time.httpdate
588   return if env['HTTP_IF_NONE_MATCH']
589 
590   if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
591     # compare based on seconds since epoch
592     since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
593     halt 304 if since >= time.to_i
594   end
595 
596   if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
597     # compare based on seconds since epoch
598     since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
599     halt 412 if since < time.to_i
600   end
601 rescue ArgumentError
602 end
not_found?() click to toggle source

whether or not the status is set to 404

    # File lib/sinatra/base.rb
674 def not_found?
675   status == 404
676 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
669 def server_error?
670   status.between? 500, 599
671 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
506 def stream(keep_open = false)
507   scheduler = env['async.callback'] ? EventMachine : Stream
508   current   = @params.dup
509   stream = if scheduler == Stream  && keep_open
510     Stream.new(scheduler, false) do |out|
511       until out.closed?
512         with_params(current) { yield(out) }
513       end
514     end
515   else
516     Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
517   end
518   body stream
519 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
654 def success?
655   status.between? 200, 299
656 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
685 def time_for(value)
686   if value.is_a? Numeric
687     Time.at value
688   elsif value.respond_to? :to_s
689     Time.parse value.to_s
690   else
691     value.to_time
692   end
693 rescue ArgumentError => e
694   raise e
695 rescue Exception
696   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
697 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
708 def with_params(temp_params)
709   original = @params
710   @params = temp_params
711   yield
712 ensure
713   @params = original if original
714 end