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
# 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
# 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
# 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
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
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
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 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
# File lib/sinatra/base.rb 435 def <<(data) 436 @scheduler.schedule { @front.call(data.to_s) } 437 self 438 end
Sugar for redirect (example: redirect back)
# File lib/sinatra/base.rb 582 def back 583 request.referer 584 end
whether or not the status is set to 400
# File lib/sinatra/base.rb 617 def bad_request? 618 status == 400 619 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, :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
# File lib/sinatra/base.rb 440 def callback(&block) 441 return yield if closed? 442 @callbacks << block 443 end
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
# 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
# File lib/sinatra/base.rb 447 def closed? 448 @closed 449 end
# 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
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
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
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
whether or not the status is set to 1xx
# File lib/sinatra/base.rb 587 def informational? 588 status.between? 100, 199 589 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 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
whether or not the status is set to 404
# File lib/sinatra/base.rb 612 def not_found? 613 status == 404 614 end
whether or not the status is set to 3xx
# File lib/sinatra/base.rb 597 def redirect? 598 status.between? 300, 399 599 end
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
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
whether or not the status is set to 2xx
# File lib/sinatra/base.rb 592 def success? 593 status.between? 200, 299 594 end
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
# 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