Skip to content

Commit

Permalink
ConditionalGet middleware (Last-Modified/Etag)
Browse files Browse the repository at this point in the history
Adapted from Michael Klishin's implementation for Merb:
http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb

Implemented by Ryan Tomayko.
  • Loading branch information
leahneukirchen committed Sep 9, 2008
1 parent e4ad5c7 commit 3b31e21
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/rack.rb
Expand Up @@ -29,6 +29,7 @@ def self.release
autoload :Builder, "rack/builder"
autoload :Cascade, "rack/cascade"
autoload :CommonLogger, "rack/commonlogger"
autoload :ConditionalGet, "rack/conditionalget"
autoload :File, "rack/file"
autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory"
Expand Down
42 changes: 42 additions & 0 deletions lib/rack/conditionalget.rb
@@ -0,0 +1,42 @@
module Rack

# Middleware that enables conditional GET using If-None-Match and
# If-Modified-Since. The application should set either or both of the
# Last-Modified or Etag response headers according to RFC 2616. When
# either of the conditions is met, the response body is set to be zero
# length and the response status is set to 304 Not Modified.
#
# Applications that defer response body generation until the body's each
# message is received will avoid response body generation completely when
# a conditional GET matches.
#
# Adapted from Michael Klishin's Merb implementation:
# http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
class ConditionalGet
def initialize(app)
@app = app
end

def call(env)
return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD'])

status, headers, body = @app.call(env)
if etag_matches?(env, headers) || modified_since?(env, headers)
status = 304
body = []
end
[status, headers, body]
end

private
def etag_matches?(env, headers)
etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH']
end

def modified_since?(env, headers)
last_modified = headers['Last-Modified'] and
last_modified == env['HTTP_IF_MODIFIED_SINCE']
end
end

end
41 changes: 41 additions & 0 deletions test/spec_rack_conditionalget.rb
@@ -0,0 +1,41 @@
require 'test/spec'
require 'time'

require 'rack/mock'
require 'rack/conditionalget'

context "Rack::ConditionalGet" do
specify "should set a 304 status and truncate body when If-Modified-Since hits" do
timestamp = Time.now.httpdate
app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Last-Modified'=>timestamp}, 'TEST'] })

response = Rack::MockRequest.new(app).
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)

response.status.should.be == 304
response.body.should.be.empty
end

specify "should set a 304 status and truncate body when If-None-Match hits" do
app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Etag'=>'1234'}, 'TEST'] })

response = Rack::MockRequest.new(app).
get("/", 'HTTP_IF_NONE_MATCH' => '1234')

response.status.should.be == 304
response.body.should.be.empty
end

specify "should not affect non-GET/HEAD requests" do
app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Etag'=>'1234'}, 'TEST'] })

response = Rack::MockRequest.new(app).
post("/", 'HTTP_IF_NONE_MATCH' => '1234')

response.status.should.be == 200
response.body.should.be == 'TEST'
end
end

0 comments on commit 3b31e21

Please sign in to comment.