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