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