From 5cd2d07bdcda4b2f547830d2becafe5e0722fa43 Mon Sep 17 00:00:00 2001 From: Cameron Bothner Date: Sat, 18 Aug 2018 13:31:33 -0400 Subject: Translate service-specific missing object exceptions into a generic one `ActiveStorage::Blob#download` and `ActiveStorage::Blob#open` raise `ActiveStorage::FileNotFoundError` when the corresponding file is missing from the storage service. Services translate service-specific missing object exceptions (e.g. `Google::Cloud::NotFoundError` for the GCS service and `Errno::ENOENT` for the disk service) into `ActiveStorage::FileNotFoundError`. --- activestorage/lib/active_storage/errors.rb | 4 +++ .../service/azure_storage_service.rb | 24 ++++++++++++--- .../lib/active_storage/service/disk_service.rb | 34 +++++++++++++++------- .../lib/active_storage/service/gcs_service.rb | 14 +++++++-- .../lib/active_storage/service/s3_service.rb | 14 +++++++-- 5 files changed, 72 insertions(+), 18 deletions(-) (limited to 'activestorage/lib/active_storage') diff --git a/activestorage/lib/active_storage/errors.rb b/activestorage/lib/active_storage/errors.rb index f4bf66a615..6475c1d076 100644 --- a/activestorage/lib/active_storage/errors.rb +++ b/activestorage/lib/active_storage/errors.rb @@ -19,4 +19,8 @@ module ActiveStorage # Raised when uploaded or downloaded data does not match a precomputed checksum. # Indicates that a network error or a software bug caused data corruption. class IntegrityError < Error; end + + # Raised when ActiveStorage::Blob#download is called on a blob where the + # backing file is no longer present in its service. + class FileNotFoundError < Error; end end diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb index b26234c722..66aabc1f9f 100644 --- a/activestorage/lib/active_storage/service/azure_storage_service.rb +++ b/activestorage/lib/active_storage/service/azure_storage_service.rb @@ -34,16 +34,20 @@ module ActiveStorage end else instrument :download, key: key do - _, io = blobs.get_blob(container, key) - io.force_encoding(Encoding::BINARY) + handle_errors do + _, io = blobs.get_blob(container, key) + io.force_encoding(Encoding::BINARY) + end end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end) - io.force_encoding(Encoding::BINARY) + handle_errors do + _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end) + io.force_encoding(Encoding::BINARY) + end end end @@ -139,11 +143,23 @@ module ActiveStorage chunk_size = 5.megabytes offset = 0 + raise ActiveStorage::FileNotFoundError unless blob.present? + while offset < blob.properties[:content_length] _, chunk = blobs.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1) yield chunk.force_encoding(Encoding::BINARY) offset += chunk_size end end + + def handle_errors + yield + rescue Azure::Core::Http::HTTPError => e + if e.type == "BlobNotFound" + raise ActiveStorage::FileNotFoundError + else + raise + end + end end end diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb index 9f304b7e01..52f3a3df16 100644 --- a/activestorage/lib/active_storage/service/disk_service.rb +++ b/activestorage/lib/active_storage/service/disk_service.rb @@ -22,27 +22,31 @@ module ActiveStorage end end - def download(key) + def download(key, &block) if block_given? instrument :streaming_download, key: key do - File.open(path_for(key), "rb") do |file| - while data = file.read(5.megabytes) - yield data - end - end + stream key, &block end else instrument :download, key: key do - File.binread path_for(key) + begin + File.binread path_for(key) + rescue Errno::ENOENT + raise ActiveStorage::FileNotFoundError + end end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - File.open(path_for(key), "rb") do |file| - file.seek range.begin - file.read range.size + begin + File.open(path_for(key), "rb") do |file| + file.seek range.begin + file.read range.size + end + rescue Errno::ENOENT + raise ActiveStorage::FileNotFoundError end end end @@ -122,6 +126,16 @@ module ActiveStorage end private + def stream(key) + File.open(path_for(key), "rb") do |file| + while data = file.read(5.megabytes) + yield data + end + end + rescue Errno::ENOENT + raise ActiveStorage::FileNotFoundError + end + def folder_for(key) [ key[0..1], key[2..3] ].join("/") end diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb index eb46973509..18c0f14cfc 100644 --- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -34,14 +34,22 @@ module ActiveStorage end else instrument :download, key: key do - file_for(key).download.string + begin + file_for(key).download.string + rescue Google::Cloud::NotFoundError + raise ActiveStorage::FileNotFoundError + end end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - file_for(key).download(range: range).string + begin + file_for(key).download(range: range).string + rescue Google::Cloud::NotFoundError + raise ActiveStorage::FileNotFoundError + end end end @@ -116,6 +124,8 @@ module ActiveStorage chunk_size = 5.megabytes offset = 0 + raise ActiveStorage::FileNotFoundError unless file.present? + while offset < file.size yield file.download(range: offset..(offset + chunk_size - 1)).string offset += chunk_size diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb index 0286e7ff21..89a9e54158 100644 --- a/activestorage/lib/active_storage/service/s3_service.rb +++ b/activestorage/lib/active_storage/service/s3_service.rb @@ -33,14 +33,22 @@ module ActiveStorage end else instrument :download, key: key do - object_for(key).get.body.string.force_encoding(Encoding::BINARY) + begin + object_for(key).get.body.string.force_encoding(Encoding::BINARY) + rescue Aws::S3::Errors::NoSuchKey + raise ActiveStorage::FileNotFoundError + end end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY) + begin + object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY) + rescue Aws::S3::Errors::NoSuchKey + raise ActiveStorage::FileNotFoundError + end end end @@ -103,6 +111,8 @@ module ActiveStorage chunk_size = 5.megabytes offset = 0 + raise ActiveStorage::FileNotFoundError unless object.exists? + while offset < object.content_length yield object.get(range: "bytes=#{offset}-#{offset + chunk_size - 1}").body.read.force_encoding(Encoding::BINARY) offset += chunk_size -- cgit v1.2.3