2010-03-13 16:37:24 -05:00
module Fog
2011-06-15 17:26:43 -04:00
module Storage
class AWS
2010-03-13 16:37:24 -05:00
class Real
2009-08-08 15:31:32 -04:00
# Get an object from S3
#
2013-01-04 00:56:21 -05:00
# @param bucket_name [String] Name of bucket to read from
# @param object_name [String] Name of object to read
# @param options [Hash]
# @option options If-Match [String] Returns object only if its etag matches this value, otherwise returns 412 (Precondition Failed).
# @option options If-Modified-Since [Time] Returns object only if it has been modified since this time, otherwise returns 304 (Not Modified).
# @option options If-None-Match [String] Returns object only if its etag differs from this value, otherwise returns 304 (Not Modified)
# @option options If-Unmodified-Since [Time] Returns object only if it has not been modified since this time, otherwise returns 412 (Precodition Failed).
# @option options Range [String] Range of object to download
# @option options versionId [String] specify a particular version to retrieve
2010-05-10 23:28:27 -04:00
#
2013-01-04 00:56:21 -05:00
# @return [Excon::Response] response:
# * body [String]- Contents of object
# * headers [Hash]:
# * Content-Length [String] - Size of object contents
# * Content-Type [String] - MIME type of object
# * ETag [String] - Etag of object
# * Last-Modified [String] - Last modified timestamp for object
2010-05-10 23:28:27 -04:00
#
2013-01-04 00:56:21 -05:00
# @see http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html
2010-10-29 21:05:59 -04:00
2009-09-09 23:44:28 -04:00
def get_object ( bucket_name , object_name , options = { } , & block )
2009-09-08 23:24:21 -04:00
unless bucket_name
raise ArgumentError . new ( 'bucket_name is required' )
end
unless object_name
raise ArgumentError . new ( 'object_name is required' )
end
2012-03-17 15:28:31 -04:00
params = { :headers = > { } }
2010-05-14 15:29:05 -04:00
if version_id = options . delete ( 'versionId' )
2012-03-17 15:28:31 -04:00
params [ :query ] = { 'versionId' = > version_id }
end
params [ :headers ] . merge! ( options )
if options [ 'If-Modified-Since' ]
params [ :headers ] [ 'If-Modified-Since' ] = Fog :: Time . at ( options [ 'If-Modified-Since' ] . to_i ) . to_date_header
2010-05-14 15:29:05 -04:00
end
2012-03-17 15:28:31 -04:00
if options [ 'If-Unmodified-Since' ]
params [ :headers ] [ 'If-Unmodified-Since' ] = Fog :: Time . at ( options [ 'If-Unmodified-Since' ] . to_i ) . to_date_header
2011-07-05 18:57:08 -04:00
end
2012-03-17 15:28:31 -04:00
if block_given?
params [ :response_block ] = Proc . new
2011-07-05 18:57:08 -04:00
end
2012-03-17 15:28:31 -04:00
request ( params . merge! ( {
2011-05-03 05:23:01 -04:00
:expects = > [ 200 , 206 ] ,
2009-08-16 14:44:50 -04:00
:host = > " #{ bucket_name } . #{ @host } " ,
2009-12-08 14:22:46 -05:00
:idempotent = > true ,
2009-08-16 14:44:50 -04:00
:method = > 'GET' ,
2010-05-10 23:28:27 -04:00
:path = > CGI . escape ( object_name ) ,
2012-03-17 15:28:31 -04:00
} ) )
2009-08-08 15:31:32 -04:00
end
2009-07-13 22:14:59 -04:00
end
2009-08-08 15:31:32 -04:00
2010-10-29 21:16:36 -04:00
class Mock # :nodoc:all
2009-08-08 15:31:32 -04:00
2009-09-10 13:08:29 -04:00
def get_object ( bucket_name , object_name , options = { } , & block )
2011-12-15 17:23:50 -05:00
version_id = options . delete ( 'versionId' )
2009-09-08 23:24:21 -04:00
unless bucket_name
raise ArgumentError . new ( 'bucket_name is required' )
end
2011-12-15 17:23:50 -05:00
2009-09-08 23:24:21 -04:00
unless object_name
raise ArgumentError . new ( 'object_name is required' )
end
2011-12-15 17:23:50 -05:00
2009-11-20 14:08:08 -05:00
response = Excon :: Response . new
2011-06-07 21:46:37 -04:00
if ( bucket = self . data [ :buckets ] [ bucket_name ] )
2011-12-15 17:23:50 -05:00
object = nil
if bucket [ :objects ] . has_key? ( object_name )
2012-02-06 09:53:27 -05:00
object = version_id ? bucket [ :objects ] [ object_name ] . find { | object | object [ 'VersionId' ] == version_id } : bucket [ :objects ] [ object_name ] . first
2011-12-15 17:23:50 -05:00
end
2011-12-15 14:26:08 -05:00
if ( object && ! object [ :delete_marker ] )
2011-06-07 21:46:37 -04:00
if options [ 'If-Match' ] && options [ 'If-Match' ] != object [ 'ETag' ]
response . status = 412
elsif options [ 'If-Modified-Since' ] && options [ 'If-Modified-Since' ] > Time . parse ( object [ 'Last-Modified' ] )
response . status = 304
elsif options [ 'If-None-Match' ] && options [ 'If-None-Match' ] == object [ 'ETag' ]
response . status = 304
elsif options [ 'If-Unmodified-Since' ] && options [ 'If-Unmodified-Since' ] < Time . parse ( object [ 'Last-Modified' ] )
response . status = 412
2009-09-10 13:08:29 -04:00
else
2011-06-07 21:46:37 -04:00
response . status = 200
for key , value in object
case key
when 'Cache-Control' , 'Content-Disposition' , 'Content-Encoding' , 'Content-Length' , 'Content-MD5' , 'Content-Type' , 'ETag' , 'Expires' , 'Last-Modified' , / ^x-amz-meta- /
response . headers [ key ] = value
end
end
2011-12-29 14:08:22 -05:00
response . headers [ 'x-amz-version-id' ] = object [ 'VersionId' ] if bucket [ :versioning ]
2012-08-23 04:04:54 -04:00
body = object [ :body ]
if options [ 'Range' ]
# since AWS S3 itself does not support multiple range headers, we will use only the first
ranges = byte_ranges ( options [ 'Range' ] , body . size )
unless ranges . nil? || ranges . empty?
response . status = 206
body = body [ ranges . first ]
end
end
2011-06-07 21:46:37 -04:00
unless block_given?
2012-08-23 04:04:54 -04:00
response . body = body
2011-06-07 21:46:37 -04:00
else
2012-08-23 04:04:54 -04:00
data = StringIO . new ( body )
2011-06-07 21:46:37 -04:00
remaining = data . length
while remaining > 0
chunk = data . read ( [ remaining , Excon :: CHUNK_SIZE ] . min )
block . call ( chunk )
remaining -= Excon :: CHUNK_SIZE
end
2009-09-10 13:08:29 -04:00
end
end
2011-12-15 17:23:50 -05:00
elsif version_id && ! object
response . status = 400
response . body = {
'Error' = > {
'Code' = > 'InvalidArgument' ,
'Message' = > 'Invalid version id specified' ,
'ArgumentValue' = > version_id ,
'ArgumentName' = > 'versionId' ,
'RequestId' = > Fog :: Mock . random_hex ( 16 ) ,
'HostId' = > Fog :: Mock . random_base64 ( 65 )
}
}
2012-02-06 09:53:27 -05:00
raise ( Excon :: Errors . status_error ( { :expects = > 200 } , response ) )
2011-06-07 21:46:37 -04:00
else
response . status = 404
response . body = " ...<Code>NoSuchKey< \ /Code>... "
raise ( Excon :: Errors . status_error ( { :expects = > 200 } , response ) )
2009-08-08 15:31:32 -04:00
end
else
2009-08-16 14:45:52 -04:00
response . status = 404
2011-06-07 21:46:37 -04:00
response . body = " ...<Code>NoSuchBucket</Code>... "
2009-11-20 14:08:08 -05:00
raise ( Excon :: Errors . status_error ( { :expects = > 200 } , response ) )
2009-08-08 15:31:32 -04:00
end
response
end
2012-08-23 04:04:54 -04:00
private
# === Borrowed from rack
# Parses the "Range:" header, if present, into an array of Range objects.
# Returns nil if the header is missing or syntactically invalid.
# Returns an empty array if none of the ranges are satisfiable.
def byte_ranges ( http_range , size )
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
return nil unless http_range
ranges = [ ]
http_range . split ( / , \ s* / ) . each do | range_spec |
matches = range_spec . match ( / bytes=( \ d*)-( \ d*) / )
return nil unless matches
r0 , r1 = matches [ 1 ] , matches [ 2 ]
if r0 . empty?
return nil if r1 . empty?
# suffix-byte-range-spec, represents trailing suffix of file
r0 = [ size - r1 . to_i , 0 ] . max
r1 = size - 1
else
r0 = r0 . to_i
if r1 . empty?
r1 = size - 1
else
r1 = r1 . to_i
return nil if r1 < r0 # backwards range is syntactically invalid
r1 = size - 1 if r1 > = size
end
end
ranges << ( r0 .. r1 ) if r0 < = r1
end
ranges
end
2009-08-08 15:31:32 -04:00
end
2009-07-13 22:14:59 -04:00
end
end
end