Skip to content

Commit f7ecde8

Browse files
committed
Expire caching when a download fail while proxying in ActiveStorage
Fix rails#51284 Both Proxy controllers in Active Storage set the caching headers early before streaming. In some instances (see rails#51284), it is possible for the file download (from the service to server) to fail before we send the first byte to the client (response not yet committed). In those instances, this change would invalidate the cache and return a better response status before closing the stream.
1 parent 3342f13 commit f7ecde8

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

activestorage/app/controllers/concerns/active_storage/streaming.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ def send_blob_stream(blob, disposition: nil) # :doc:
6161
blob.download do |chunk|
6262
stream.write chunk
6363
end
64+
rescue ActiveStorage::FileNotFoundError
65+
expires_now
66+
head :not_found
67+
rescue
68+
# Status and caching headers are already set, but not commited.
69+
# Change the status to 500 manually.
70+
expires_now
71+
head :internal_server_error
72+
raise
6473
end
6574
end
6675
end

activestorage/test/controllers/blobs/proxy_controller_test.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,32 @@ class ActiveStorage::Blobs::ProxyControllerTest < ActionDispatch::IntegrationTes
1616
assert_equal "max-age=3155695200, public", response.headers["Cache-Control"]
1717
end
1818

19+
test "invalidates cache and returns a 404 if the file is not found on download" do
20+
blob = create_file_blob(filename: "racecar.jpg")
21+
mock_download = lambda do |_|
22+
raise ActiveStorage::FileNotFoundError.new "File still uploading!"
23+
end
24+
blob.service.stub(:download, mock_download) do
25+
get rails_storage_proxy_url(blob)
26+
end
27+
assert_response :not_found
28+
assert_equal "no-cache", response.headers["Cache-Control"]
29+
end
30+
31+
32+
test "invalidates cache and returns a 500 if an error is raised on download" do
33+
blob = create_file_blob(filename: "racecar.jpg")
34+
mock_download = lambda do |_|
35+
raise StandardError.new "Something is not cool!"
36+
end
37+
blob.service.stub(:download, mock_download) do
38+
get rails_storage_proxy_url(blob)
39+
end
40+
assert_response :internal_server_error
41+
assert_equal "no-cache", response.headers["Cache-Control"]
42+
end
43+
44+
1945
test "forcing Content-Type to binary" do
2046
get rails_storage_proxy_url(create_blob(content_type: "text/html"))
2147
assert_equal "application/octet-stream", response.headers["Content-Type"]

activestorage/test/controllers/representations/proxy_controller_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "test_helper"
44
require "database/setup"
5+
require "minitest/mock"
56

67
class ActiveStorage::Representations::ProxyControllerWithVariantsTest < ActionDispatch::IntegrationTest
78
setup do
@@ -84,6 +85,39 @@ class ActiveStorage::Representations::ProxyControllerWithVariantsWithStrictLoadi
8485
assert_match(/^inline/, response.headers["Content-Disposition"])
8586
assert_equal @blob.variant(@transformations).download, response.body
8687
end
88+
89+
90+
test "invalidates cache and returns a 404 if the file is not found on download" do
91+
# This mock requires a pre-processed variant as processing the variant will call to download
92+
mock_download = lambda do |_|
93+
raise ActiveStorage::FileNotFoundError.new "File still uploading!"
94+
end
95+
96+
@blob.service.stub(:download, mock_download) do
97+
get rails_blob_representation_proxy_url(
98+
filename: @blob.filename,
99+
signed_blob_id: @blob.signed_id,
100+
variation_key: ActiveStorage::Variation.encode(@transformations))
101+
end
102+
assert_response :not_found
103+
assert_equal "no-cache", response.headers["Cache-Control"]
104+
end
105+
106+
test "invalidates cache and returns a 500 if the an error is raised on download" do
107+
# This mock requires a pre-processed variant as processing the variant will call to download
108+
mock_download = lambda do |_|
109+
raise StandardError.new "Something is not cool!"
110+
end
111+
112+
@blob.service.stub(:download, mock_download) do
113+
get rails_blob_representation_proxy_url(
114+
filename: @blob.filename,
115+
signed_blob_id: @blob.signed_id,
116+
variation_key: ActiveStorage::Variation.encode(@transformations))
117+
end
118+
assert_response :internal_server_error
119+
assert_equal "no-cache", response.headers["Cache-Control"]
120+
end
87121
end
88122

89123
class ActiveStorage::Representations::ProxyControllerWithPreviewsTest < ActionDispatch::IntegrationTest

0 commit comments

Comments
 (0)