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