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

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

     # File lib/sinatra/base.rb
2014 def self.helpers(*extensions, &block)
2015   Delegator.target.helpers(*extensions, &block)
2016 end
new(scheduler = self.class, keep_open = false, &back) click to toggle source
    # File lib/sinatra/base.rb
433 def initialize(scheduler = self.class, keep_open = false, &back)
434   @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
435   @callbacks, @closed = [], false
436 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
2002 def self.new(base = Base, &block)
2003   base = Class.new(base)
2004   base.class_eval(&block) if block_given?
2005   base
2006 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
2019 def self.use(*args, &block)
2020   Delegator.target.use(*args, &block)
2021 end

Public Instance Methods

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

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
603 def back
604   request.referer
605 end
bad_request?() click to toggle source

whether or not the status is set to 400

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

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
623 def client_error?
624   status.between? 400, 499
625 end
close() click to toggle source
    # File lib/sinatra/base.rb
438 def close
439   return if closed?
440   @closed = true
441   @scheduler.schedule { @callbacks.each { |c| c.call } }
442 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
468 def closed?
469   @closed
470 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
444 def each(&front)
445   @front = front
446   @scheduler.defer do
447     begin
448       @back.call(self)
449     rescue Exception => e
450       @scheduler.schedule { raise e }
451     end
452     close unless @keep_open
453   end
454 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
577 def etag(value, options = {})
578   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
579   options      = {:kind => options} unless Hash === options
580   kind         = options[:kind] || :strong
581   new_resource = options.fetch(:new_resource) { request.post? }
582 
583   unless ETAG_KINDS.include?(kind)
584     raise ArgumentError, ":strong or :weak expected"
585   end
586 
587   value = '"%s"' % value
588   value = "W/#{value}" if kind == :weak
589   response['ETag'] = value
590 
591   if success? or status == 304
592     if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
593       halt(request.safe? ? 304 : 412)
594     end
595 
596     if env['HTTP_IF_MATCH']
597       halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
598     end
599   end
600 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
661 def etag_matches?(list, new_resource = request.post?)
662   return !new_resource if list == '*'
663   list.to_s.split(/\s*,\s*/).include? response['ETag']
664 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
523 def expires(amount, *values)
524   values << {} unless values.last.kind_of?(Hash)
525 
526   if amount.is_a? Integer
527     time    = Time.now + amount.to_i
528     max_age = amount
529   else
530     time    = time_for amount
531     max_age = time - Time.now
532   end
533 
534   values.last.merge!(:max_age => max_age)
535   cache_control(*values)
536 
537   response['Expires'] = time.httpdate
538 end
informational?() click to toggle source

whether or not the status is set to 1xx

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

whether or not the status is set to 404

    # File lib/sinatra/base.rb
633 def not_found?
634   status == 404
635 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
628 def server_error?
629   status.between? 500, 599
630 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
479 def stream(keep_open = false)
480   scheduler = env['async.callback'] ? EventMachine : Stream
481   current   = @params.dup
482   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
483 end
success?() click to toggle source

whether or not the status is set to 2xx

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