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

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

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

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

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

Public Instance Methods

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

Sugar for redirect (example: redirect back)

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

whether or not the status is set to 400

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

whether or not the status is set to 4xx

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

whether or not the status is set to 1xx

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

whether or not the status is set to 404

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

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

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

whether or not the status is set to 2xx

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