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

Extend the top-level DSL with the modules provided.

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

Use the middleware for classic applications.

     # File lib/sinatra/base.rb
1977 def self.use(*args, &block)
1978   Delegator.target.use(*args, &block)
1979 end

Public Instance Methods

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

Sugar for redirect (example: redirect back)

    # File lib/sinatra/base.rb
579 def back
580   request.referer
581 end
bad_request?() click to toggle source

whether or not the status is set to 400

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

whether or not the status is set to 4xx

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

whether or not the status is set to 1xx

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

whether or not the status is set to 404

    # File lib/sinatra/base.rb
609 def not_found?
610   status == 404
611 end
redirect?() click to toggle source

whether or not the status is set to 3xx

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

whether or not the status is set to 5xx

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

whether or not the status is set to 2xx

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