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

Private Class Methods

helpers(*extensions, &block) click to toggle source

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

     # File lib/sinatra/base.rb
1980 def self.helpers(*extensions, &block)
1981   Delegator.target.helpers(*extensions, &block)
1982 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
1968 def self.new(base = Base, &block)
1969   base = Class.new(base)
1970   base.class_eval(&block) if block_given?
1971   base
1972 end
register(*extensions, &block) click to toggle source

Extend the top-level DSL with the modules provided.

     # File lib/sinatra/base.rb
1975 def self.register(*extensions, &block)
1976   Delegator.target.register(*extensions, &block)
1977 end
use(*args, &block) click to toggle source

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
1985 def self.use(*args, &block)
1986   Delegator.target.use(*args, &block)
1987 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
439 def <<(data)
440   @scheduler.schedule { @front.call(data.to_s) }
441   self
442 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
586 def back
587   request.referer
588 end
bad_request?() click to toggle source

whether or not the status is set to 400

    # File lib/sinatra/base.rb
621 def bad_request?
622   status == 400
623 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
478 def cache_control(*values)
479   if values.last.kind_of?(Hash)
480     hash = values.pop
481     hash.reject! { |k, v| v == false }
482     hash.reject! { |k, v| values << k if v == true }
483   else
484     hash = {}
485   end
486 
487   values.map! { |value| value.to_s.tr('_','-') }
488   hash.each do |key, value|
489     key = key.to_s.tr('_', '-')
490     value = value.to_i if ['max-age', 's-maxage'].include? key
491     values << "#{key}=#{value}"
492   end
493 
494   response['Cache-Control'] = values.join(', ') if values.any?
495 end
callback() { || ... } click to toggle source
    # File lib/sinatra/base.rb
444 def callback(&block)
445   return yield if closed?
446   @callbacks << block
447 end
client_error?() click to toggle source

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
606 def client_error?
607   status.between? 400, 499
608 end
close() click to toggle source
    # File lib/sinatra/base.rb
421 def close
422   return if closed?
423   @closed = true
424   @scheduler.schedule { @callbacks.each { |c| c.call } }
425 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
451 def closed?
452   @closed
453 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
427 def each(&front)
428   @front = front
429   @scheduler.defer do
430     begin
431       @back.call(self)
432     rescue Exception => e
433       @scheduler.schedule { raise e }
434     end
435     close unless @keep_open
436   end
437 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
560 def etag(value, options = {})
561   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
562   options      = {:kind => options} unless Hash === options
563   kind         = options[:kind] || :strong
564   new_resource = options.fetch(:new_resource) { request.post? }
565 
566   unless ETAG_KINDS.include?(kind)
567     raise ArgumentError, ":strong or :weak expected"
568   end
569 
570   value = '"%s"' % value
571   value = "W/#{value}" if kind == :weak
572   response['ETag'] = value
573 
574   if success? or status == 304
575     if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
576       halt(request.safe? ? 304 : 412)
577     end
578 
579     if env['HTTP_IF_MATCH']
580       halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
581     end
582   end
583 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
644 def etag_matches?(list, new_resource = request.post?)
645   return !new_resource if list == '*'
646   list.to_s.split(/\s*,\s*/).include? response['ETag']
647 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
506 def expires(amount, *values)
507   values << {} unless values.last.kind_of?(Hash)
508 
509   if amount.is_a? Integer
510     time    = Time.now + amount.to_i
511     max_age = amount
512   else
513     time    = time_for amount
514     max_age = time - Time.now
515   end
516 
517   values.last.merge!(:max_age => max_age)
518   cache_control(*values)
519 
520   response['Expires'] = time.httpdate
521 end
informational?() click to toggle source

whether or not the status is set to 1xx

    # File lib/sinatra/base.rb
591 def informational?
592   status.between? 100, 199
593 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
530 def last_modified(time)
531   return unless time
532   time = time_for time
533   response['Last-Modified'] = time.httpdate
534   return if env['HTTP_IF_NONE_MATCH']
535 
536   if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
537     # compare based on seconds since epoch
538     since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
539     halt 304 if since >= time.to_i
540   end
541 
542   if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
543     # compare based on seconds since epoch
544     since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
545     halt 412 if since < time.to_i
546   end
547 rescue ArgumentError
548 end
not_found?() click to toggle source

whether or not the status is set to 404

    # File lib/sinatra/base.rb
616 def not_found?
617   status == 404
618 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
611 def server_error?
612   status.between? 500, 599
613 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 Thin or Rainbows.

    # File lib/sinatra/base.rb
462 def stream(keep_open = false)
463   scheduler = env['async.callback'] ? EventMachine : Stream
464   current   = @params.dup
465   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
466 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
596 def success?
597   status.between? 200, 299
598 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
627 def time_for(value)
628   if value.is_a? Numeric
629     Time.at value
630   elsif value.respond_to? :to_s
631     Time.parse value.to_s
632   else
633     value.to_time
634   end
635 rescue ArgumentError => boom
636   raise boom
637 rescue Exception
638   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
639 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
649 def with_params(temp_params)
650   original, @params = @params, temp_params
651   yield
652 ensure
653   @params = original if original
654 end