Skip to content
Browse files

Introduce send_file :x_sendfile => true to send an X-Sendfile respons…

…e header.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8628 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent ebfd03b commit 9889d86a6ce1c2206460276e63d7c0317b41a72f @jeremy jeremy committed Jan 11, 2008
Showing with 46 additions and 25 deletions.
  1. +2 −0 actionpack/CHANGELOG
  2. +31 −23 actionpack/lib/action_controller/streaming.rb
  3. +13 −2 actionpack/test/controller/send_file_test.rb
View
2 actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Introduce send_file :x_sendfile => true to send an X-Sendfile response header. [Jeremy Kemper]
+
* Fixed ActionView::Helpers::ActiveRecordHelper::form for when protect_from_forgery is used #10739 [jeremyevans]
* Provide nicer access to HTTP Headers. Instead of request.env["HTTP_REFERRER"] you can now use request.headers["Referrer"]. [Koz]
View
54 actionpack/lib/action_controller/streaming.rb
@@ -4,10 +4,13 @@ module Streaming
DEFAULT_SEND_FILE_OPTIONS = {
:type => 'application/octet-stream'.freeze,
:disposition => 'attachment'.freeze,
- :stream => true,
- :buffer_size => 4096
+ :stream => true,
+ :buffer_size => 4096,
+ :x_sendfile => false
}.freeze
+ X_SENDFILE_HEADER = 'X-Sendfile'.freeze
+
protected
# Sends the file by streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes
@@ -22,15 +25,15 @@ module Streaming
# Defaults to File.basename(path).
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
# or to read the entire file before sending (false). Defaults to true.
# * <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>:url_based_filename</tt> - set to true if you want the browser guess the filename from
- # the URL, which is necessary for i18n filenames on certain browsers
+ # * <tt>:url_based_filename</tt> - set to true if you want the browser guess the filename from
+ # the URL, which is necessary for i18n filenames on certain browsers
# (setting :filename overrides this option).
#
# The default Content-Type and Content-Disposition headers are
@@ -67,19 +70,24 @@ def send_file(path, options = {}) #:doc:
@performed_render = false
- if options[:stream]
- render :status => options[:status], :text => Proc.new { |response, output|
- logger.info "Streaming file #{path}" unless logger.nil?
- len = options[:buffer_size] || 4096
- File.open(path, 'rb') do |file|
- while buf = file.read(len)
- output.write(buf)
- end
- end
- }
+ if options[:x_sendfile]
+ logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger
+ head options[:status], X_SENDFILE_HEADER => path
else
- logger.info "Sending file #{path}" unless logger.nil?
- File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
+ if options[:stream]
+ render :status => options[:status], :text => Proc.new { |response, output|
+ logger.info "Streaming file #{path}" unless logger.nil?
+ len = options[:buffer_size] || 4096
+ File.open(path, 'rb') do |file|
+ while buf = file.read(len)
+ output.write(buf)
+ end
+ end
+ }
+ else
+ logger.info "Sending file #{path}" unless logger.nil?
+ File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
+ end
end
end
@@ -90,7 +98,7 @@ def send_file(path, options = {}) #:doc:
# * <tt>:filename</tt> - Suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # * <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'.
#
@@ -105,7 +113,7 @@ def send_file(path, options = {}) #:doc:
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
- logger.info "Sending data #{options[:filename]}" unless logger.nil?
+ logger.info "Sending data #{options[:filename]}" if logger
send_file_headers! options.merge(:length => data.size)
@performed_render = false
render :status => options[:status], :text => data
@@ -130,10 +138,10 @@ def send_file_headers!(options)
)
# Fix a problem with IE 6.0 on opening downloaded files:
- # If Cache-Control: no-cache is set (which Rails does by default),
- # IE removes the file it just downloaded from its cache immediately
- # after it displays the "open/save" dialog, which means that if you
- # hit "open" the file isn't there anymore when the application that
+ # If Cache-Control: no-cache is set (which Rails does by default),
+ # IE removes the file it just downloaded from its cache immediately
+ # after it displays the "open/save" dialog, which means that if you
+ # hit "open" the file isn't there anymore when the application that
# is called for handling the download is run, so let's workaround that
headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
end
View
15 actionpack/test/controller/send_file_test.rb
@@ -27,7 +27,7 @@ class SendFileTest < Test::Unit::TestCase
include TestFileUtils
Mime::Type.register "image/png", :png unless defined? Mime::PNG
-
+
def setup
@controller = SendFileController.new
@request = ActionController::TestRequest.new
@@ -55,7 +55,7 @@ def test_file_stream
assert_nothing_raised { response.body.call(response, output) }
assert_equal file_data, output.string
end
-
+
def test_file_url_based_filename
@controller.options = { :url_based_filename => true }
response = nil
@@ -64,6 +64,17 @@ def test_file_url_based_filename
assert_equal "attachment", response.headers["Content-Disposition"]
end
+ def test_x_sendfile_header
+ @controller.options = { :x_sendfile => true }
+
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+
+ assert_equal @controller.file_path, response.headers['X-Sendfile']
+ assert response.body.blank?
+ end
+
def test_data
response = nil
assert_nothing_raised { response = process('data') }

0 comments on commit 9889d86

Please sign in to comment.
Something went wrong with that request. Please try again.