1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Support HTTP Range downloads from disk

Closes #32193.
This commit is contained in:
George Claghorn 2018-07-15 19:58:14 -04:00
parent 5a71a773ea
commit 390097531b
3 changed files with 30 additions and 20 deletions

View file

@ -5,23 +5,14 @@
# Always go through the BlobsController, or your own authenticated controller, rather than directly
# to the service url.
class ActiveStorage::DiskController < ActiveStorage::BaseController
include ActionController::Live
skip_forgery_protection
def show
if key = decode_verified_key
response.headers["Content-Type"] = params[:content_type] || DEFAULT_SEND_FILE_TYPE
response.headers["Content-Disposition"] = params[:disposition] || DEFAULT_SEND_FILE_DISPOSITION
disk_service.download key do |chunk|
response.stream.write chunk
end
serve_file disk_service.path_for(key), content_type: params[:content_type], disposition: params[:disposition]
else
head :not_found
end
ensure
response.stream.close
end
def update
@ -32,13 +23,9 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
else
head :unprocessable_entity
end
else
head :not_found
end
rescue ActiveStorage::IntegrityError
head :unprocessable_entity
ensure
response.stream.close
end
private
@ -51,6 +38,20 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
end
def serve_file(path, content_type:, disposition:)
Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
self.status = status
self.response_body = body
headers.each do |name, value|
response.headers[name] = value
end
response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
end
end
def decode_verified_token
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)

View file

@ -117,11 +117,11 @@ module ActiveStorage
{ "Content-Type" => content_type }
end
private
def path_for(key)
File.join root, folder_for(key), key
end
def path_for(key) #:nodoc:
File.join root, folder_for(key), key
end
private
def folder_for(key)
[ key[0..1], key[2..3] ].join("/")
end

View file

@ -6,8 +6,8 @@ require "database/setup"
class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "showing blob inline" do
blob = create_blob
get blob.service_url
assert_response :ok
assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
assert_equal "text/plain", response.headers["Content-Type"]
assert_equal "Hello world!", response.body
@ -15,13 +15,22 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "showing blob as attachment" do
blob = create_blob
get blob.service_url(disposition: :attachment)
assert_response :ok
assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
assert_equal "text/plain", response.headers["Content-Type"]
assert_equal "Hello world!", response.body
end
test "showing blob range inline" do
blob = create_blob
get blob.service_url, headers: { "Range" => "bytes=5-9" }
assert_response :partial_content
assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
assert_equal "text/plain", response.headers["Content-Type"]
assert_equal " worl", response.body
end
test "directly uploading blob with integrity" do
data = "Something else entirely!"