entity_tag response helper / conditional GET support

ResponseHelpers#entity_tag sets the ETag response header and
(potentially) halts execution if an If-None-Match request header
is present matches the value given.

More information on HTTP's ETag header and conditional requests
available in RFC 2616:

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
This commit is contained in:
Ryan Tomayko 2008-04-13 05:07:15 -04:00
parent 302a03cc37
commit ce4cf34e77
2 changed files with 65 additions and 0 deletions

View File

@ -459,6 +459,45 @@ module Sinatra
time time
end 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 +strength+ 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. For all other request
# methods, a '412 Precondition Failed' response is sent.
#
# Calling this method before perfoming heavy processing (e.g., lengthy
# database queries, template rendering, complex logic) can dramatically
# increase overall throughput with caching clients.
#
# === See Also
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19[RFC2616: ETag],
# ResponseHelpers#last_modified
def entity_tag(value, strength=:strong)
value =
case strength
when :strong then '"%s"' % value
when :weak then 'W/"%s"' % value
else raise TypeError, "strength must be one of :strong or :weak"
end
response.header['ETag'] = value
# Check for If-None-Match request header and halt if match is found.
etags = (request.env['HTTP_IF_NONE_MATCH'] || '').split(/\s*,\s*/)
if etags.include?(value) || etags.include?('*')
# GET/HEAD requests: send Not Modified response
throw :halt, 304 if request.get? || request.head?
# Other requests: send Precondition Failed response
throw :halt, 412
end
end
alias :etag :entity_tag
end end
module RenderingHelpers module RenderingHelpers

View File

@ -152,6 +152,32 @@ context "Sinatra" do
body.should.equal '' body.should.equal ''
end end
specify "supports conditional GETs with entity_tag" do
get '/strong' do
entity_tag 'FOO'
'foo response'
end
get_it '/strong'
should.be.ok
body.should.equal 'foo response'
get_it '/strong', {},
'HTTP_IF_NONE_MATCH' => '"BAR"'
should.be.ok
body.should.equal 'foo response'
get_it '/strong', {},
'HTTP_IF_NONE_MATCH' => '"FOO"'
status.should.equal 304
body.should.equal ''
get_it '/strong', {},
'HTTP_IF_NONE_MATCH' => '"BAR", *'
status.should.equal 304
body.should.equal ''
end
specify "delegates HEAD requests to GET handlers" do specify "delegates HEAD requests to GET handlers" do
get '/invisible' do get '/invisible' do
"I am invisible to the world" "I am invisible to the world"