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