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

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
1988 def self.use(*args, &block)
1989   Delegator.target.use(*args, &block)
1990 end

Public Instance Methods

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

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
589 def back
590   request.referer
591 end
bad_request?() click to toggle source

whether or not the status is set to 400

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

whether or not the status is set to 4xx

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

whether or not the status is set to 1xx

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

whether or not the status is set to 404

    # File lib/sinatra/base.rb
619 def not_found?
620   status == 404
621 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
614 def server_error?
615   status.between? 500, 599
616 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
465 def stream(keep_open = false)
466   scheduler = env['async.callback'] ? EventMachine : Stream
467   current   = @params.dup
468   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
469 end
success?() click to toggle source

whether or not the status is set to 2xx

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