If-Modified-Since support for Static / Streaming

Adds support for RFC 2616 conditional requests based on the resources
last modification time. If the request includes an If-Modified-Since
header and the file's last modification time matches the value exactly,
a "304 Not Modified" response is sent (with an empty response body)
instead of the default 200 response with entire response body.

The meat of the implementation is in Streaming#send_file_headers! so
Static and anything else that results in a call to send_file_headers!
gets automatic if-modified-since support.
This commit is contained in:
Ryan Tomayko 2008-03-26 10:51:08 -04:00
parent 1776a80d2c
commit 9a5d69a87f
2 changed files with 34 additions and 0 deletions

View File

@ -2,6 +2,7 @@ require 'rubygems'
require 'metaid'
require 'uri'
require 'thread'
require 'time'
if ENV['SWIFT']
require 'swiftcore/swiftiplied_mongrel'
@ -251,6 +252,11 @@ module Sinatra
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
# Defaults to 4096.
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
# indicating the last modified time of the file. If the request includes an
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
# is sent instead of the file. Defaults to the file's last modified
# time.
#
# The default Content-Type and Content-Disposition headers are
# set to download arbitrary binary files in as many browsers as
@ -283,6 +289,7 @@ module Sinatra
options[:length] ||= File.size(path)
options[:filename] ||= File.basename(path)
options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
options[:last_modified] ||= File.mtime(path).httpdate
send_file_headers! options
if options[:stream]
@ -302,6 +309,10 @@ module Sinatra
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
# indicating the last modified time of the response entity. If the request includes an
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
# is sent instead of the data.
#
# Generic data download:
# send_data buffer
@ -325,6 +336,13 @@ module Sinatra
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
end
# Send a "304 Not Modified" if the last_modified option is provided and matches
# the If-Modified-Since request header value.
if last_modified = options[:last_modified]
header 'Last-Modified' => last_modified
throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
end
disposition = options[:disposition].dup || 'attachment'
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]

View File

@ -54,6 +54,22 @@ context "Static files (by default)" do
body.should.equal "<foo></foo>\n"
end
specify "include a Last-Modified header" do
last_modified = File.mtime(Sinatra.application.options.public + '/foo.xml')
get_it('/foo.xml')
should.be.ok
body.should.not.be.empty
headers['Last-Modified'].should.equal last_modified.httpdate
end
specify "are not served when If-Modified-Since matches" do
last_modified = File.mtime(Sinatra.application.options.public + '/foo.xml')
@request = Rack::MockRequest.new(Sinatra.application)
@response = @request.get('/foo.xml', 'HTTP_IF_MODIFIED_SINCE' => last_modified.httpdate)
status.should.equal 304
body.should.be.empty
end
end
context "SendData" do