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