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