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