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