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

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

     # File lib/sinatra/base.rb
2025 def self.helpers(*extensions, &block)
2026   Delegator.target.helpers(*extensions, &block)
2027 end
new(scheduler = self.class, keep_open = false, &back) click to toggle source
    # File lib/sinatra/base.rb
440 def initialize(scheduler = self.class, keep_open = false, &back)
441   @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
442   @callbacks, @closed = [], false
443 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
2013 def self.new(base = Base, &block)
2014   base = Class.new(base)
2015   base.class_eval(&block) if block_given?
2016   base
2017 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
2030 def self.use(*args, &block)
2031   Delegator.target.use(*args, &block)
2032 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
463 def <<(data)
464   @scheduler.schedule { @front.call(data.to_s) }
465   self
466 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
610 def back
611   request.referer
612 end
bad_request?() click to toggle source

whether or not the status is set to 400

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

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
630 def client_error?
631   status.between? 400, 499
632 end
close() click to toggle source
    # File lib/sinatra/base.rb
445 def close
446   return if closed?
447   @closed = true
448   @scheduler.schedule { @callbacks.each { |c| c.call } }
449 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
475 def closed?
476   @closed
477 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
451 def each(&front)
452   @front = front
453   @scheduler.defer do
454     begin
455       @back.call(self)
456     rescue Exception => e
457       @scheduler.schedule { raise e }
458     end
459     close unless @keep_open
460   end
461 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
584 def etag(value, options = {})
585   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
586   options      = {:kind => options} unless Hash === options
587   kind         = options[:kind] || :strong
588   new_resource = options.fetch(:new_resource) { request.post? }
589 
590   unless ETAG_KINDS.include?(kind)
591     raise ArgumentError, ":strong or :weak expected"
592   end
593 
594   value = '"%s"' % value
595   value = "W/#{value}" if kind == :weak
596   response['ETag'] = value
597 
598   if success? or status == 304
599     if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
600       halt(request.safe? ? 304 : 412)
601     end
602 
603     if env['HTTP_IF_MATCH']
604       halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
605     end
606   end
607 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
668 def etag_matches?(list, new_resource = request.post?)
669   return !new_resource if list == '*'
670   list.to_s.split(/\s*,\s*/).include? response['ETag']
671 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
530 def expires(amount, *values)
531   values << {} unless values.last.kind_of?(Hash)
532 
533   if amount.is_a? Integer
534     time    = Time.now + amount.to_i
535     max_age = amount
536   else
537     time    = time_for amount
538     max_age = time - Time.now
539   end
540 
541   values.last.merge!(:max_age => max_age)
542   cache_control(*values)
543 
544   response['Expires'] = time.httpdate
545 end
informational?() click to toggle source

whether or not the status is set to 1xx

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

whether or not the status is set to 404

    # File lib/sinatra/base.rb
640 def not_found?
641   status == 404
642 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
635 def server_error?
636   status.between? 500, 599
637 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
486 def stream(keep_open = false)
487   scheduler = env['async.callback'] ? EventMachine : Stream
488   current   = @params.dup
489   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
490 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
620 def success?
621   status.between? 200, 299
622 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
651 def time_for(value)
652   if value.is_a? Numeric
653     Time.at value
654   elsif value.respond_to? :to_s
655     Time.parse value.to_s
656   else
657     value.to_time
658   end
659 rescue ArgumentError => boom
660   raise boom
661 rescue Exception
662   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
663 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
673 def with_params(temp_params)
674   original, @params = @params, temp_params
675   yield
676 ensure
677   @params = original if original
678 end