Class of the response body in case you use stream.
Three things really matter: The front and back block (back being the blog 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.
# File lib/sinatra/base.rb, line 247 def self.defer(*) yield end def initialize(scheduler = self.class, keep_open = false, &back) @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open @callbacks, @closed = [], false end def close return if @closed @closed = true @scheduler.schedule { @callbacks.each { |c| c.call }} end def each(&front) @front = front @scheduler.defer do begin @back.call(self) rescue Exception => e @scheduler.schedule { raise e } end close unless @keep_open end end def <<(data) @scheduler.schedule { @front.call(data.to_s) } self end def callback(&block) @callbacks << block end alias errback callback end
# File lib/sinatra/base.rb, line 249 def initialize(scheduler = self.class, keep_open = false, &back) @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open @callbacks, @closed = [], false end
# File lib/sinatra/base.rb, line 246 def self.schedule(*) yield end def self.defer(*) yield end def initialize(scheduler = self.class, keep_open = false, &back) @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open @callbacks, @closed = [], false end def close return if @closed @closed = true @scheduler.schedule { @callbacks.each { |c| c.call }} end def each(&front) @front = front @scheduler.defer do begin @back.call(self) rescue Exception => e @scheduler.schedule { raise e } end close unless @keep_open end end def <<(data) @scheduler.schedule { @front.call(data.to_s) } self end def callback(&block) @callbacks << block end alias errback callback end # 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. def stream(keep_open = false) scheduler = env['async.callback'] ? EventMachine : Stream current = @params.dup block = proc do |out| begin original, @params = @params, current yield(out) ensure @params = original if original end end body Stream.new(scheduler, keep_open, &block) end # 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, :min_stale, :s_max_age). # # 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: # http://tools.ietf.org/html/rfc2616#section-14.9.1 def cache_control(*values) if values.last.kind_of?(Hash) hash = values.pop hash.reject! { |k,v| v == false } hash.reject! { |k,v| values << k if v == true } else hash = {} end values.map! { |value| value.to_s.tr('_','-') } hash.each do |key, value| key = key.to_s.tr('_', '-') value = value.to_i if key == "max-age" values << [key, value].join('=') end response['Cache-Control'] = values.join(', ') if values.any? end # 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=60 # => Expires: Mon, 08 Jun 2009 08:50:17 GMT # def expires(amount, *values) values << {} unless values.last.kind_of?(Hash) if amount.is_a? Integer time = Time.now + amount.to_i max_age = amount else time = time_for amount max_age = time - Time.now end values.last.merge!(:max_age => max_age) cache_control(*values) response['Expires'] = time.httpdate end # 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. def last_modified(time) return unless time time = time_for time response['Last-Modified'] = time.httpdate return if env['HTTP_IF_NONE_MATCH'] if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i halt 304 if since >= time.to_i end if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i halt 412 if since < time.to_i end rescue ArgumentError end # 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. def etag(value, options = {}) # Before touching this code, please double check RFC 2616 14.24 and 14.26. options = {:kind => options} unless Hash === options kind = options[:kind] || :strong new_resource = options.fetch(:new_resource) { request.post? } unless [:strong, :weak].include?(kind) raise ArgumentError, ":strong or :weak expected" end value = '"%s"' % value value = 'W/' + value if kind == :weak response['ETag'] = value if success? or status == 304 if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource halt(request.safe? ? 304 : 412) end if env['HTTP_IF_MATCH'] halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource end end end # Sugar for redirect (example: redirect back) def back request.referer end # whether or not the status is set to 1xx def informational? status.between? 100, 199 end # whether or not the status is set to 2xx def success? status.between? 200, 299 end # whether or not the status is set to 3xx def redirect? status.between? 300, 399 end # whether or not the status is set to 4xx def client_error? status.between? 400, 499 end # whether or not the status is set to 5xx def server_error? status.between? 500, 599 end # whether or not the status is set to 404 def not_found? status == 404 end # Generates a Time object from the given value. # Used by #expires and #last_modified. def time_for(value) if value.respond_to? :to_time value.to_time elsif value.is_a? Time value elsif value.respond_to? :new_offset # DateTime#to_time does the same on 1.9 d = value.new_offset 0 t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction t.getlocal elsif value.respond_to? :mday # Date#to_time does the same on 1.9 Time.local(value.year, value.mon, value.mday) elsif value.is_a? Numeric Time.at value else Time.parse value.to_s end rescue ArgumentError => boom raise boom rescue Exception raise ArgumentError, "unable to convert #{value.inspect} to a Time object" end private # Helper method checking if a ETag value list includes the current ETag. def etag_matches?(list, new_resource = request.post?) return !new_resource if list == '*' list.to_s.split(%r\s*,\s*/).include? response['ETag'] end end
# File lib/sinatra/base.rb, line 272 def <<(data) @scheduler.schedule { @front.call(data.to_s) } self end
Sugar for redirect (example: redirect back)
# File lib/sinatra/base.rb, line 422 def back request.referer end
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, :min_stale, :s_max_age).
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, line 315 def cache_control(*values) if values.last.kind_of?(Hash) hash = values.pop hash.reject! { |k,v| v == false } hash.reject! { |k,v| values << k if v == true } else hash = {} end values.map! { |value| value.to_s.tr('_','-') } hash.each do |key, value| key = key.to_s.tr('_', '-') value = value.to_i if key == "max-age" values << [key, value].join('=') end response['Cache-Control'] = values.join(', ') if values.any? end
# File lib/sinatra/base.rb, line 277 def callback(&block) @callbacks << block end
whether or not the status is set to 4xx
# File lib/sinatra/base.rb, line 442 def client_error? status.between? 400, 499 end
# File lib/sinatra/base.rb, line 254 def close return if @closed @closed = true @scheduler.schedule { @callbacks.each { |c| c.call }} end
# File lib/sinatra/base.rb, line 260 def each(&front) @front = front @scheduler.defer do begin @back.call(self) rescue Exception => e @scheduler.schedule { raise e } end close unless @keep_open end end
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, line 396 def etag(value, options = {}) # Before touching this code, please double check RFC 2616 14.24 and 14.26. options = {:kind => options} unless Hash === options kind = options[:kind] || :strong new_resource = options.fetch(:new_resource) { request.post? } unless [:strong, :weak].include?(kind) raise ArgumentError, ":strong or :weak expected" end value = '"%s"' % value value = 'W/' + value if kind == :weak response['ETag'] = value if success? or status == 304 if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource halt(request.safe? ? 304 : 412) end if env['HTTP_IF_MATCH'] halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource end end end
Helper method checking if a ETag value list includes the current ETag.
# File lib/sinatra/base.rb, line 485 def etag_matches?(list, new_resource = request.post?) return !new_resource if list == '*' list.to_s.split(%r\s*,\s*/).include? response['ETag'] end
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=60 => Expires: Mon, 08 Jun 2009 08:50:17 GMT
# File lib/sinatra/base.rb, line 343 def expires(amount, *values) values << {} unless values.last.kind_of?(Hash) if amount.is_a? Integer time = Time.now + amount.to_i max_age = amount else time = time_for amount max_age = time - Time.now end values.last.merge!(:max_age => max_age) cache_control(*values) response['Expires'] = time.httpdate end
whether or not the status is set to 1xx
# File lib/sinatra/base.rb, line 427 def informational? status.between? 100, 199 end
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, line 367 def last_modified(time) return unless time time = time_for time response['Last-Modified'] = time.httpdate return if env['HTTP_IF_NONE_MATCH'] if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i halt 304 if since >= time.to_i end if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i halt 412 if since < time.to_i end rescue ArgumentError end
whether or not the status is set to 404
# File lib/sinatra/base.rb, line 452 def not_found? status == 404 end
whether or not the status is set to 3xx
# File lib/sinatra/base.rb, line 437 def redirect? status.between? 300, 399 end
whether or not the status is set to 5xx
# File lib/sinatra/base.rb, line 447 def server_error? status.between? 500, 599 end
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 #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, line 290 def stream(keep_open = false) scheduler = env['async.callback'] ? EventMachine : Stream current = @params.dup block = proc do |out| begin original, @params = @params, current yield(out) ensure @params = original if original end end body Stream.new(scheduler, keep_open, &block) end
whether or not the status is set to 2xx
# File lib/sinatra/base.rb, line 432 def success? status.between? 200, 299 end
Generates a Time object from the given value. Used by expires and last_modified.
# File lib/sinatra/base.rb, line 458 def time_for(value) if value.respond_to? :to_time value.to_time elsif value.is_a? Time value elsif value.respond_to? :new_offset # DateTime#to_time does the same on 1.9 d = value.new_offset 0 t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction t.getlocal elsif value.respond_to? :mday # Date#to_time does the same on 1.9 Time.local(value.year, value.mon, value.mday) elsif value.is_a? Numeric Time.at value else Time.parse value.to_s end rescue ArgumentError => boom raise boom rescue Exception raise ArgumentError, "unable to convert #{value.inspect} to a Time object" end