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

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

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

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

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

Public Instance Methods

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

Sugar for redirect (example: redirect back)

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

whether or not the status is set to 400

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

whether or not the status is set to 4xx

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

whether or not the status is set to 1xx

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

whether or not the status is set to 404

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

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

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

whether or not the status is set to 2xx

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