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

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
1981 def self.use(*args, &block)
1982   Delegator.target.use(*args, &block)
1983 end

Public Instance Methods

<<(data) click to toggle source
    # File lib/sinatra/base.rb
435 def <<(data)
436   @scheduler.schedule { @front.call(data.to_s) }
437   self
438 end
back() click to toggle source

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
582 def back
583   request.referer
584 end
bad_request?() click to toggle source

whether or not the status is set to 400

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

whether or not the status is set to 4xx

    # File lib/sinatra/base.rb
602 def client_error?
603   status.between? 400, 499
604 end
close() click to toggle source
    # File lib/sinatra/base.rb
417 def close
418   return if closed?
419   @closed = true
420   @scheduler.schedule { @callbacks.each { |c| c.call } }
421 end
closed?() click to toggle source
    # File lib/sinatra/base.rb
447 def closed?
448   @closed
449 end
each(&front) click to toggle source
    # File lib/sinatra/base.rb
423 def each(&front)
424   @front = front
425   @scheduler.defer do
426     begin
427       @back.call(self)
428     rescue Exception => e
429       @scheduler.schedule { raise e }
430     end
431     close unless @keep_open
432   end
433 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
556 def etag(value, options = {})
557   # Before touching this code, please double check RFC 2616 14.24 and 14.26.
558   options      = {:kind => options} unless Hash === options
559   kind         = options[:kind] || :strong
560   new_resource = options.fetch(:new_resource) { request.post? }
561 
562   unless ETAG_KINDS.include?(kind)
563     raise ArgumentError, ":strong or :weak expected"
564   end
565 
566   value = '"%s"' % value
567   value = "W/#{value}" if kind == :weak
568   response['ETag'] = value
569 
570   if success? or status == 304
571     if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
572       halt(request.safe? ? 304 : 412)
573     end
574 
575     if env['HTTP_IF_MATCH']
576       halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
577     end
578   end
579 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
640 def etag_matches?(list, new_resource = request.post?)
641   return !new_resource if list == '*'
642   list.to_s.split(/\s*,\s*/).include? response['ETag']
643 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
502 def expires(amount, *values)
503   values << {} unless values.last.kind_of?(Hash)
504 
505   if amount.is_a? Integer
506     time    = Time.now + amount.to_i
507     max_age = amount
508   else
509     time    = time_for amount
510     max_age = time - Time.now
511   end
512 
513   values.last.merge!(:max_age => max_age)
514   cache_control(*values)
515 
516   response['Expires'] = time.httpdate
517 end
informational?() click to toggle source

whether or not the status is set to 1xx

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

whether or not the status is set to 404

    # File lib/sinatra/base.rb
612 def not_found?
613   status == 404
614 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

    # File lib/sinatra/base.rb
607 def server_error?
608   status.between? 500, 599
609 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
458 def stream(keep_open = false)
459   scheduler = env['async.callback'] ? EventMachine : Stream
460   current   = @params.dup
461   body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
462 end
success?() click to toggle source

whether or not the status is set to 2xx

    # File lib/sinatra/base.rb
592 def success?
593   status.between? 200, 299
594 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
623 def time_for(value)
624   if value.is_a? Numeric
625     Time.at value
626   elsif value.respond_to? :to_s
627     Time.parse value.to_s
628   else
629     value.to_time
630   end
631 rescue ArgumentError => boom
632   raise boom
633 rescue Exception
634   raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
635 end
with_params(temp_params) { || ... } click to toggle source
    # File lib/sinatra/base.rb
645 def with_params(temp_params)
646   original, @params = @params, temp_params
647   yield
648 ensure
649   @params = original if original
650 end