forked from rack/rack
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ConditionalGet middleware (Last-Modified/Etag)
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
1 parent
e4ad5c7
commit 3b31e21
Showing
3 changed files
with
84 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |