diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index c45f063a..16faf9e9 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -171,6 +171,57 @@ module Sinatra end 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, :min_stale, :s_max_age). + # + # 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: + # http://tools.ietf.org/html/rfc2616#section-14.9.1 + def cache_control(*values) + if values.last.kind_of?(Hash) + hash = values.pop + hash.reject! { |k,v| v == false } + hash.reject! { |k,v| values << k if v == true } + else + hash = {} + end + + values = values.map { |value| value.to_s.tr('_','-') } + hash.each { |k,v| values << [k.to_s.tr('_', '-'), v].join('=') } + + response['Cache-Control'] = values.join(', ') if values.any? + 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=60 + # => Expires: Mon, 08 Jun 2009 08:50:17 GMT + # + def expires(amount, *values) + values << {} unless values.last.kind_of?(Hash) + + if amount.respond_to?(:to_time) + max_age = amount.to_time - Time.now + time = amount.to_time + else + max_age = amount + time = Time.now + amount + end + + values.last.merge!(:max_age => max_age) + cache_control(*values) + + response['Expires'] = time.httpdate + 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+. diff --git a/test/helpers_test.rb b/test/helpers_test.rb index 673aff1d..9fde81b6 100644 --- a/test/helpers_test.rb +++ b/test/helpers_test.rb @@ -352,6 +352,43 @@ class HelpersTest < Test::Unit::TestCase end end + describe 'cache_control' do + setup do + mock_app { + get '/' do + cache_control :public, :no_cache, :max_age => 60 + 'Hello World' + end + } + end + + it 'sets the Cache-Control header' do + get '/' + assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') + end + end + + describe 'expires' do + setup do + mock_app { + get '/' do + expires 60, :public, :no_cache + 'Hello World' + end + } + end + + it 'sets the Cache-Control header' do + get '/' + assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') + end + + it 'sets the Expires header' do + get '/' + assert_not_nil response['Expires'] + end + end + describe 'last_modified' do setup do now = Time.now