From ce4cf34e7731b754b1d6b3eade7110a4352324f1 Mon Sep 17 00:00:00 2001 From: Ryan Tomayko Date: Sun, 13 Apr 2008 05:07:15 -0400 Subject: [PATCH] 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 --- lib/sinatra.rb | 39 +++++++++++++++++++++++++++++++++++++++ test/app_test.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/sinatra.rb b/lib/sinatra.rb index 9e966550..92ab17e8 100644 --- a/lib/sinatra.rb +++ b/lib/sinatra.rb @@ -459,6 +459,45 @@ module Sinatra time 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 module RenderingHelpers diff --git a/test/app_test.rb b/test/app_test.rb index be89945d..90c7abc2 100644 --- a/test/app_test.rb +++ b/test/app_test.rb @@ -152,6 +152,32 @@ context "Sinatra" do body.should.equal '' 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 get '/invisible' do "I am invisible to the world"