Class | Sinatra::Helpers::Stream |
In: |
lib/sinatra/base.rb
|
Parent: | Object |
# File lib/sinatra/base.rb, line 247 247: def self.defer(*) yield end 248: 249: def initialize(scheduler = self.class, keep_open = false, &back) 250: @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open 251: @callbacks, @closed = [], false 252: end 253: 254: def close 255: return if @closed 256: @closed = true 257: @scheduler.schedule { @callbacks.each { |c| c.call }} 258: end 259: 260: def each(&front) 261: @front = front 262: @scheduler.defer do 263: begin 264: @back.call(self) 265: rescue Exception => e 266: @scheduler.schedule { raise e } 267: end 268: close unless @keep_open 269: end 270: end 271: 272: def <<(data) 273: @scheduler.schedule { @front.call(data.to_s) } 274: self 275: end 276: 277: def callback(&block) 278: @callbacks << block 279: end 280: 281: alias errback callback 282: end
# File lib/sinatra/base.rb, line 249 249: def initialize(scheduler = self.class, keep_open = false, &back) 250: @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open 251: @callbacks, @closed = [], false 252: end
# File lib/sinatra/base.rb, line 246 246: def self.schedule(*) yield end 247: def self.defer(*) yield end 248: 249: def initialize(scheduler = self.class, keep_open = false, &back) 250: @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open 251: @callbacks, @closed = [], false 252: end 253: 254: def close 255: return if @closed 256: @closed = true 257: @scheduler.schedule { @callbacks.each { |c| c.call }} 258: end 259: 260: def each(&front) 261: @front = front 262: @scheduler.defer do 263: begin 264: @back.call(self) 265: rescue Exception => e 266: @scheduler.schedule { raise e } 267: end 268: close unless @keep_open 269: end 270: end 271: 272: def <<(data) 273: @scheduler.schedule { @front.call(data.to_s) } 274: self 275: end 276: 277: def callback(&block) 278: @callbacks << block 279: end 280: 281: alias errback callback 282: end 283: 284: # Allows to start sending data to the client even though later parts of 285: # the response body have not yet been generated. 286: # 287: # The close parameter specifies whether Stream#close should be called 288: # after the block has been executed. This is only relevant for evented 289: # servers like Thin or Rainbows. 290: def stream(keep_open = false, &block) 291: scheduler = env['async.callback'] ? EventMachine : Stream 292: body Stream.new(scheduler, keep_open, &block) 293: end 294: 295: # Specify response freshness policy for HTTP caches (Cache-Control header). 296: # Any number of non-value directives (:public, :private, :no_cache, 297: # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with 298: # a Hash of value directives (:max_age, :min_stale, :s_max_age). 299: # 300: # cache_control :public, :must_revalidate, :max_age => 60 301: # => Cache-Control: public, must-revalidate, max-age=60 302: # 303: # See RFC 2616 / 14.9 for more on standard cache control directives: 304: # http://tools.ietf.org/html/rfc2616#section-14.9.1 305: def cache_control(*values) 306: if values.last.kind_of?(Hash) 307: hash = values.pop 308: hash.reject! { |k,v| v == false } 309: hash.reject! { |k,v| values << k if v == true } 310: else 311: hash = {} 312: end 313: 314: values = values.map { |value| value.to_s.tr('_','-') } 315: hash.each do |key, value| 316: key = key.to_s.tr('_', '-') 317: value = value.to_i if key == "max-age" 318: values << [key, value].join('=') 319: end 320: 321: response['Cache-Control'] = values.join(', ') if values.any? 322: end 323: 324: # Set the Expires header and Cache-Control/max-age directive. Amount 325: # can be an integer number of seconds in the future or a Time object 326: # indicating when the response should be considered "stale". The remaining 327: # "values" arguments are passed to the #cache_control helper: 328: # 329: # expires 500, :public, :must_revalidate 330: # => Cache-Control: public, must-revalidate, max-age=60 331: # => Expires: Mon, 08 Jun 2009 08:50:17 GMT 332: # 333: def expires(amount, *values) 334: values << {} unless values.last.kind_of?(Hash) 335: 336: if amount.is_a? Integer 337: time = Time.now + amount.to_i 338: max_age = amount 339: else 340: time = time_for amount 341: max_age = time - Time.now 342: end 343: 344: values.last.merge!(:max_age => max_age) 345: cache_control(*values) 346: 347: response['Expires'] = time.httpdate 348: end 349: 350: # Set the last modified time of the resource (HTTP 'Last-Modified' header) 351: # and halt if conditional GET matches. The +time+ argument is a Time, 352: # DateTime, or other object that responds to +to_time+. 353: # 354: # When the current request includes an 'If-Modified-Since' header that is 355: # equal or later than the time specified, execution is immediately halted 356: # with a '304 Not Modified' response. 357: def last_modified(time) 358: return unless time 359: time = time_for time 360: response['Last-Modified'] = time.httpdate 361: return if env['HTTP_IF_NONE_MATCH'] 362: 363: if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] 364: # compare based on seconds since epoch 365: since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i 366: halt 304 if since >= time.to_i 367: end 368: 369: if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] 370: # compare based on seconds since epoch 371: since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i 372: halt 412 if since < time.to_i 373: end 374: rescue ArgumentError 375: end 376: 377: # Set the response entity tag (HTTP 'ETag' header) and halt if conditional 378: # GET matches. The +value+ argument is an identifier that uniquely 379: # identifies the current version of the resource. The +kind+ argument 380: # indicates whether the etag should be used as a :strong (default) or :weak 381: # cache validator. 382: # 383: # When the current request includes an 'If-None-Match' header with a 384: # matching etag, execution is immediately halted. If the request method is 385: # GET or HEAD, a '304 Not Modified' response is sent. 386: def etag(value, options = {}) 387: # Before touching this code, please double check RFC 2616 14.24 and 14.26. 388: options = {:kind => options} unless Hash === options 389: kind = options[:kind] || :strong 390: new_resource = options.fetch(:new_resource) { request.post? } 391: 392: unless [:strong, :weak].include?(kind) 393: raise ArgumentError, ":strong or :weak expected" 394: end 395: 396: value = '"%s"' % value 397: value = 'W/' + value if kind == :weak 398: response['ETag'] = value 399: 400: if success? or status == 304 401: if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource 402: halt(request.safe? ? 304 : 412) 403: end 404: 405: if env['HTTP_IF_MATCH'] 406: halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource 407: end 408: end 409: end 410: 411: # Sugar for redirect (example: redirect back) 412: def back 413: request.referer 414: end 415: 416: # whether or not the status is set to 1xx 417: def informational? 418: status.between? 100, 199 419: end 420: 421: # whether or not the status is set to 2xx 422: def success? 423: status.between? 200, 299 424: end 425: 426: # whether or not the status is set to 3xx 427: def redirect? 428: status.between? 300, 399 429: end 430: 431: # whether or not the status is set to 4xx 432: def client_error? 433: status.between? 400, 499 434: end 435: 436: # whether or not the status is set to 5xx 437: def server_error? 438: status.between? 500, 599 439: end 440: 441: # whether or not the status is set to 404 442: def not_found? 443: status == 404 444: end 445: 446: # Generates a Time object from the given value. 447: # Used by #expires and #last_modified. 448: def time_for(value) 449: if value.respond_to? :to_time 450: value.to_time 451: elsif value.is_a? Time 452: value 453: elsif value.respond_to? :new_offset 454: # DateTime#to_time does the same on 1.9 455: d = value.new_offset 0 456: t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction 457: t.getlocal 458: elsif value.respond_to? :mday 459: # Date#to_time does the same on 1.9 460: Time.local(value.year, value.mon, value.mday) 461: elsif value.is_a? Numeric 462: Time.at value 463: else 464: Time.parse value.to_s 465: end 466: rescue ArgumentError => boom 467: raise boom 468: rescue Exception 469: raise ArgumentError, "unable to convert #{value.inspect} to a Time object" 470: end 471: 472: private 473: 474: # Helper method checking if a ETag value list includes the current ETag. 475: def etag_matches?(list, new_resource = request.post?) 476: return !new_resource if list == '*' 477: list.to_s.split(/\s*,\s*/).include? response['ETag'] 478: end 479: end
# File lib/sinatra/base.rb, line 272 272: def <<(data) 273: @scheduler.schedule { @front.call(data.to_s) } 274: self 275: 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 305 305: def cache_control(*values) 306: if values.last.kind_of?(Hash) 307: hash = values.pop 308: hash.reject! { |k,v| v == false } 309: hash.reject! { |k,v| values << k if v == true } 310: else 311: hash = {} 312: end 313: 314: values = values.map { |value| value.to_s.tr('_','-') } 315: hash.each do |key, value| 316: key = key.to_s.tr('_', '-') 317: value = value.to_i if key == "max-age" 318: values << [key, value].join('=') 319: end 320: 321: response['Cache-Control'] = values.join(', ') if values.any? 322: end
whether or not the status is set to 4xx
# File lib/sinatra/base.rb, line 432 432: def client_error? 433: status.between? 400, 499 434: end
# File lib/sinatra/base.rb, line 254 254: def close 255: return if @closed 256: @closed = true 257: @scheduler.schedule { @callbacks.each { |c| c.call }} 258: end
# File lib/sinatra/base.rb, line 260 260: def each(&front) 261: @front = front 262: @scheduler.defer do 263: begin 264: @back.call(self) 265: rescue Exception => e 266: @scheduler.schedule { raise e } 267: end 268: close unless @keep_open 269: end 270: 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 386 386: def etag(value, options = {}) 387: # Before touching this code, please double check RFC 2616 14.24 and 14.26. 388: options = {:kind => options} unless Hash === options 389: kind = options[:kind] || :strong 390: new_resource = options.fetch(:new_resource) { request.post? } 391: 392: unless [:strong, :weak].include?(kind) 393: raise ArgumentError, ":strong or :weak expected" 394: end 395: 396: value = '"%s"' % value 397: value = 'W/' + value if kind == :weak 398: response['ETag'] = value 399: 400: if success? or status == 304 401: if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource 402: halt(request.safe? ? 304 : 412) 403: end 404: 405: if env['HTTP_IF_MATCH'] 406: halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource 407: end 408: end 409: end
Helper method checking if a ETag value list includes the current ETag.
# File lib/sinatra/base.rb, line 475 475: def etag_matches?(list, new_resource = request.post?) 476: return !new_resource if list == '*' 477: list.to_s.split(/\s*,\s*/).include? response['ETag'] 478: 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 333 333: def expires(amount, *values) 334: values << {} unless values.last.kind_of?(Hash) 335: 336: if amount.is_a? Integer 337: time = Time.now + amount.to_i 338: max_age = amount 339: else 340: time = time_for amount 341: max_age = time - Time.now 342: end 343: 344: values.last.merge!(:max_age => max_age) 345: cache_control(*values) 346: 347: response['Expires'] = time.httpdate 348: end
whether or not the status is set to 1xx
# File lib/sinatra/base.rb, line 417 417: def informational? 418: status.between? 100, 199 419: 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 357 357: def last_modified(time) 358: return unless time 359: time = time_for time 360: response['Last-Modified'] = time.httpdate 361: return if env['HTTP_IF_NONE_MATCH'] 362: 363: if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] 364: # compare based on seconds since epoch 365: since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i 366: halt 304 if since >= time.to_i 367: end 368: 369: if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] 370: # compare based on seconds since epoch 371: since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i 372: halt 412 if since < time.to_i 373: end 374: rescue ArgumentError 375: end
whether or not the status is set to 404
# File lib/sinatra/base.rb, line 442 442: def not_found? 443: status == 404 444: end
whether or not the status is set to 3xx
# File lib/sinatra/base.rb, line 427 427: def redirect? 428: status.between? 300, 399 429: end
whether or not the status is set to 5xx
# File lib/sinatra/base.rb, line 437 437: def server_error? 438: status.between? 500, 599 439: 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.
# File lib/sinatra/base.rb, line 290 290: def stream(keep_open = false, &block) 291: scheduler = env['async.callback'] ? EventMachine : Stream 292: body Stream.new(scheduler, keep_open, &block) 293: end
whether or not the status is set to 2xx
# File lib/sinatra/base.rb, line 422 422: def success? 423: status.between? 200, 299 424: end
Generates a Time object from the given value. Used by expires and last_modified.
# File lib/sinatra/base.rb, line 448 448: def time_for(value) 449: if value.respond_to? :to_time 450: value.to_time 451: elsif value.is_a? Time 452: value 453: elsif value.respond_to? :new_offset 454: # DateTime#to_time does the same on 1.9 455: d = value.new_offset 0 456: t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction 457: t.getlocal 458: elsif value.respond_to? :mday 459: # Date#to_time does the same on 1.9 460: Time.local(value.year, value.mon, value.mday) 461: elsif value.is_a? Numeric 462: Time.at value 463: else 464: Time.parse value.to_s 465: end 466: rescue ArgumentError => boom 467: raise boom 468: rescue Exception 469: raise ArgumentError, "unable to convert #{value.inspect} to a Time object" 470: end