mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Squashed commit of the following:
commit 1dca99ad4f8082d8daaa17c6600f3036c25f8e50
Author: Tom Prats <tprats108@gmail.com>
Date: Thu Aug 5 18:07:00 2021 -0400
Moved header
commit 62201870ff5c4124d90912989745819b05d94516
Merge: 5fa3ecae74 c91a8135c7
Author: Tom Prats <tprats108@gmail.com>
Date: Thu Aug 5 14:41:22 2021 -0400
Merge branch 'main' into active-storage-byte-range
commit 5fa3ecae745b4f7c67a6b6b1b7ec420877c96fb8
Author: Tom Prats <tprats108@gmail.com>
Date: Thu Aug 5 14:39:53 2021 -0400
Apply suggestions from code review
Syntax updates
Co-authored-by: Rafael França <rafael@franca.dev>
commit b9553e3698a7af5105171f9d63bd7b89cbb7e2c3
Author: Tom Prats <tprats108@gmail.com>
Date: Wed Jun 23 17:36:26 2021 -0400
Added Active Storage support for byte ranges
This commit is contained in:
parent
7f5ebc4b43
commit
fcc46228c6
5 changed files with 105 additions and 2 deletions
|
@ -1,3 +1,7 @@
|
|||
* Add support for byte range requests
|
||||
|
||||
*Tom Prats*
|
||||
|
||||
* Attachments can be deleted after their association is no longer defined.
|
||||
|
||||
Fixes #42514
|
||||
|
|
|
@ -10,8 +10,15 @@ class ActiveStorage::Blobs::ProxyController < ActiveStorage::BaseController
|
|||
include ActiveStorage::SetBlob
|
||||
|
||||
def show
|
||||
http_cache_forever public: true do
|
||||
send_blob_stream @blob
|
||||
if request.headers["Range"].present?
|
||||
send_blob_byte_range_data @blob, request.headers["Range"]
|
||||
else
|
||||
http_cache_forever public: true do
|
||||
response.headers["Accept-Ranges"] = "bytes"
|
||||
response.headers["Content-Length"] = @blob.byte_size.to_s
|
||||
|
||||
send_blob_stream @blob
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "securerandom"
|
||||
|
||||
module ActiveStorage::Streaming
|
||||
DEFAULT_BLOB_STREAMING_DISPOSITION = "inline"
|
||||
|
||||
include ActionController::DataStreaming
|
||||
include ActionController::Live
|
||||
|
||||
private
|
||||
# Stream the blob in byte ranges specified through the header
|
||||
def send_blob_byte_range_data(blob, range_header, disposition: nil) #:doc:
|
||||
ranges = Rack::Utils.get_byte_ranges(range_header, blob.byte_size)
|
||||
|
||||
return head(:range_not_satisfiable) if ranges.blank? || ranges.all?(&:blank?)
|
||||
|
||||
if ranges.length == 1
|
||||
range = ranges.first
|
||||
content_type = blob.content_type_for_serving
|
||||
data = blob.download_chunk(range)
|
||||
|
||||
response.headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{blob.byte_size}"
|
||||
else
|
||||
boundary = SecureRandom.hex
|
||||
content_type = "multipart/byteranges; boundary=#{boundary}"
|
||||
data = +""
|
||||
|
||||
ranges.compact.each do |range|
|
||||
chunk = blob.download_chunk(range)
|
||||
|
||||
data << "\r\n--#{boundary}\r\n"
|
||||
data << "Content-Type: #{blob.content_type_for_serving}\r\n"
|
||||
data << "Content-Range: bytes #{range.begin}-#{range.end}/#{blob.byte_size}\r\n\r\n"
|
||||
data << chunk
|
||||
end
|
||||
|
||||
data << "\r\n--#{boundary}--\r\n"
|
||||
end
|
||||
|
||||
response.headers["Accept-Ranges"] = "bytes"
|
||||
response.headers["Content-Length"] = data.length.to_s
|
||||
|
||||
send_data(
|
||||
data,
|
||||
disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
|
||||
filename: blob.filename.sanitized,
|
||||
status: :partial_content,
|
||||
type: content_type
|
||||
)
|
||||
end
|
||||
|
||||
# Stream the blob from storage directly to the response. The disposition can be controlled by setting +disposition+.
|
||||
# The content type and filename is set directly from the +blob+.
|
||||
def send_blob_stream(blob, disposition: nil) #:doc:
|
||||
|
|
|
@ -253,6 +253,11 @@ class ActiveStorage::Blob < ActiveStorage::Record
|
|||
service.download key, &block
|
||||
end
|
||||
|
||||
# Downloads a part of the file associated with this blob.
|
||||
def download_chunk(range)
|
||||
service.download_chunk key, range
|
||||
end
|
||||
|
||||
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
||||
#
|
||||
# The tempfile's name is prefixed with +ActiveStorage-+ and the blob's ID. Its extension matches that of the blob.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require "test_helper"
|
||||
require "database/setup"
|
||||
require "minitest/mock"
|
||||
|
||||
class ActiveStorage::Blobs::ProxyControllerTest < ActionDispatch::IntegrationTest
|
||||
test "invalid signed ID" do
|
||||
|
@ -36,6 +37,48 @@ class ActiveStorage::Blobs::ProxyControllerTest < ActionDispatch::IntegrationTes
|
|||
get url
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "single Byte Range" do
|
||||
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg")), headers: { "Range" => "bytes=5-9" }
|
||||
assert_response :partial_content
|
||||
assert_equal "5", response.headers["Content-Length"]
|
||||
assert_equal "bytes 5-9/1124062", response.headers["Content-Range"]
|
||||
assert_equal "image/jpeg", response.headers["Content-Type"]
|
||||
assert_equal " Exif", response.body
|
||||
end
|
||||
|
||||
test "invalid Byte Range" do
|
||||
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg")), headers: { "Range" => "bytes=*/1234" }
|
||||
assert_response :range_not_satisfiable
|
||||
end
|
||||
|
||||
test "multiple Byte Ranges" do
|
||||
boundary = SecureRandom.hex
|
||||
SecureRandom.stub :hex, boundary do
|
||||
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg")), headers: { "Range" => "bytes=5-9,13-17" }
|
||||
assert_response :partial_content
|
||||
assert_equal "252", response.headers["Content-Length"]
|
||||
assert_equal "multipart/byteranges; boundary=#{boundary}", response.headers["Content-Type"]
|
||||
assert_equal(
|
||||
[
|
||||
"",
|
||||
"--#{boundary}",
|
||||
"Content-Type: image/jpeg",
|
||||
"Content-Range: bytes 5-9/1124062",
|
||||
"",
|
||||
" Exif",
|
||||
"--#{boundary}",
|
||||
"Content-Type: image/jpeg",
|
||||
"Content-Range: bytes 13-17/1124062",
|
||||
"",
|
||||
"I*\u0000\b\u0000",
|
||||
"--#{boundary}--",
|
||||
""
|
||||
].join("\r\n"),
|
||||
response.body
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ActiveStorage::Blobs::ExpiringProxyControllerTest < ActionDispatch::IntegrationTest
|
||||
|
|
Loading…
Reference in a new issue