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