Add GetObjectResponse data type (#134)
This allows retrieving the ObjectInfo of an object during the getObject call.
This commit is contained in:
parent
777ca8f616
commit
1e6579b02b
@ -8,6 +8,9 @@ Changelog
|
|||||||
and Data.HashSet.
|
and Data.HashSet.
|
||||||
* Add `oiUserMetadata` to parse and return user metadata stored with
|
* Add `oiUserMetadata` to parse and return user metadata stored with
|
||||||
an object.
|
an object.
|
||||||
|
* Add `GetObjectResponse` data type for the value returned by
|
||||||
|
`getObject`. It now contains parsed ObjectInfo along with the
|
||||||
|
conduit of object bytes.
|
||||||
|
|
||||||
## Version 1.4.0
|
## Version 1.4.0
|
||||||
|
|
||||||
|
|||||||
@ -147,6 +147,9 @@ module Network.Minio
|
|||||||
, gooIfModifiedSince
|
, gooIfModifiedSince
|
||||||
, gooIfUnmodifiedSince
|
, gooIfUnmodifiedSince
|
||||||
, gooSSECKey
|
, gooSSECKey
|
||||||
|
, GetObjectResponse
|
||||||
|
, gorObjectInfo
|
||||||
|
, gorObjectStream
|
||||||
|
|
||||||
-- ** Server-side object copying
|
-- ** Server-side object copying
|
||||||
, copyObject
|
, copyObject
|
||||||
@ -242,7 +245,7 @@ listBuckets = getService
|
|||||||
fGetObject :: Bucket -> Object -> FilePath -> GetObjectOptions -> Minio ()
|
fGetObject :: Bucket -> Object -> FilePath -> GetObjectOptions -> Minio ()
|
||||||
fGetObject bucket object fp opts = do
|
fGetObject bucket object fp opts = do
|
||||||
src <- getObject bucket object opts
|
src <- getObject bucket object opts
|
||||||
C.connect src $ CB.sinkFileCautious fp
|
C.connect (gorObjectStream src) $ CB.sinkFileCautious fp
|
||||||
|
|
||||||
-- | Upload the given file to the given object.
|
-- | Upload the given file to the given object.
|
||||||
fPutObject :: Bucket -> Object -> FilePath
|
fPutObject :: Bucket -> Object -> FilePath
|
||||||
@ -272,11 +275,11 @@ copyObject dstInfo srcInfo = void $ copyObjectInternal (dstBucket dstInfo)
|
|||||||
removeObject :: Bucket -> Object -> Minio ()
|
removeObject :: Bucket -> Object -> Minio ()
|
||||||
removeObject = deleteObject
|
removeObject = deleteObject
|
||||||
|
|
||||||
-- | Get an object from the object store as a resumable source (conduit).
|
-- | Get an object from the object store.
|
||||||
getObject :: Bucket -> Object -> GetObjectOptions
|
getObject :: Bucket -> Object -> GetObjectOptions
|
||||||
-> Minio (C.ConduitM () ByteString Minio ())
|
-> Minio GetObjectResponse
|
||||||
getObject bucket object opts = snd <$> getObject' bucket object []
|
getObject bucket object opts =
|
||||||
(gooToHeaders opts)
|
getObject' bucket object [] $ gooToHeaders opts
|
||||||
|
|
||||||
-- | Get an object's metadata from the object store. It accepts the
|
-- | Get an object's metadata from the object store. It accepts the
|
||||||
-- same options as GetObject.
|
-- same options as GetObject.
|
||||||
|
|||||||
@ -537,6 +537,14 @@ gooToHeaders goo = rangeHdr ++ zip names values
|
|||||||
rangeHdr = maybe [] (\a -> [(hRange, HT.renderByteRanges [a])])
|
rangeHdr = maybe [] (\a -> [(hRange, HT.renderByteRanges [a])])
|
||||||
$ gooRange goo
|
$ gooRange goo
|
||||||
|
|
||||||
|
-- | Data type returned by 'getObject' representing the object being
|
||||||
|
-- retrieved. Use the @gor*@ functions to access its contents.
|
||||||
|
data GetObjectResponse = GetObjectResponse {
|
||||||
|
-- | ObjectInfo of the object being retrieved.
|
||||||
|
gorObjectInfo :: ObjectInfo
|
||||||
|
-- | A conduit of the bytes of the object.
|
||||||
|
, gorObjectStream :: C.ConduitM () ByteString Minio ()
|
||||||
|
}
|
||||||
|
|
||||||
-- | A data-type for events that can occur in the object storage
|
-- | A data-type for events that can occur in the object storage
|
||||||
-- server. Reference:
|
-- server. Reference:
|
||||||
|
|||||||
@ -91,7 +91,6 @@ module Network.Minio.S3API
|
|||||||
, removeAllBucketNotification
|
, removeAllBucketNotification
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Conduit as C
|
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Network.HTTP.Conduit as NC
|
import qualified Network.HTTP.Conduit as NC
|
||||||
@ -118,19 +117,37 @@ getService = do
|
|||||||
}
|
}
|
||||||
parseListBuckets $ NC.responseBody resp
|
parseListBuckets $ NC.responseBody resp
|
||||||
|
|
||||||
-- | GET an object from the service and return the response headers
|
-- Parse headers from getObject and headObject calls.
|
||||||
-- and a conduit source for the object content
|
parseGetObjectHeaders :: Object -> [HT.Header] -> Maybe ObjectInfo
|
||||||
|
parseGetObjectHeaders object headers =
|
||||||
|
let metadataPairs = getMetadata headers
|
||||||
|
userMetadata = getUserMetadataMap metadataPairs
|
||||||
|
metadata = getNonUserMetadataMap metadataPairs
|
||||||
|
in ObjectInfo <$> Just object
|
||||||
|
<*> getLastModifiedHeader headers
|
||||||
|
<*> getETagHeader headers
|
||||||
|
<*> getContentLength headers
|
||||||
|
<*> Just userMetadata
|
||||||
|
<*> Just metadata
|
||||||
|
|
||||||
|
-- | GET an object from the service and return parsed ObjectInfo and a
|
||||||
|
-- conduit source for the object content
|
||||||
getObject' :: Bucket -> Object -> HT.Query -> [HT.Header]
|
getObject' :: Bucket -> Object -> HT.Query -> [HT.Header]
|
||||||
-> Minio ([HT.Header], C.ConduitM () ByteString Minio ())
|
-> Minio GetObjectResponse
|
||||||
getObject' bucket object queryParams headers = do
|
getObject' bucket object queryParams headers = do
|
||||||
resp <- mkStreamRequest reqInfo
|
resp <- mkStreamRequest reqInfo
|
||||||
return (NC.responseHeaders resp, NC.responseBody resp)
|
let objInfoMaybe = parseGetObjectHeaders object $ NC.responseHeaders resp
|
||||||
|
objInfo <- maybe (throwIO MErrVInvalidObjectInfoResponse) return
|
||||||
|
objInfoMaybe
|
||||||
|
return $ GetObjectResponse { gorObjectInfo = objInfo
|
||||||
|
, gorObjectStream = NC.responseBody resp
|
||||||
|
}
|
||||||
where
|
where
|
||||||
reqInfo = defaultS3ReqInfo { riBucket = Just bucket
|
reqInfo = defaultS3ReqInfo { riBucket = Just bucket
|
||||||
, riObject = Just object
|
, riObject = Just object
|
||||||
, riQueryParams = queryParams
|
, riQueryParams = queryParams
|
||||||
, riHeaders = headers
|
, riHeaders = headers
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Creates a bucket via a PUT bucket call.
|
-- | Creates a bucket via a PUT bucket call.
|
||||||
putBucket :: Bucket -> Region -> Minio ()
|
putBucket :: Bucket -> Region -> Minio ()
|
||||||
@ -417,22 +434,8 @@ headObject bucket object reqHeaders = do
|
|||||||
, riHeaders = reqHeaders
|
, riHeaders = reqHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
let
|
|
||||||
headers = NC.responseHeaders resp
|
|
||||||
modTime = getLastModifiedHeader headers
|
|
||||||
etag = getETagHeader headers
|
|
||||||
size = getContentLength headers
|
|
||||||
metadataPairs = getMetadata headers
|
|
||||||
userMetadata = getUserMetadataMap metadataPairs
|
|
||||||
metadata = getNonUserMetadataMap metadataPairs
|
|
||||||
|
|
||||||
maybe (throwIO MErrVInvalidObjectInfoResponse) return $
|
maybe (throwIO MErrVInvalidObjectInfoResponse) return $
|
||||||
ObjectInfo <$> Just object
|
parseGetObjectHeaders object $ NC.responseHeaders resp
|
||||||
<*> modTime
|
|
||||||
<*> etag
|
|
||||||
<*> size
|
|
||||||
<*> Just userMetadata
|
|
||||||
<*> Just metadata
|
|
||||||
|
|
||||||
|
|
||||||
-- | Query the object store if a given bucket exists.
|
-- | Query the object store if a given bucket exists.
|
||||||
|
|||||||
@ -21,10 +21,10 @@ import Test.Tasty.HUnit
|
|||||||
import Test.Tasty.QuickCheck as QC
|
import Test.Tasty.QuickCheck as QC
|
||||||
|
|
||||||
import Conduit (replicateC)
|
import Conduit (replicateC)
|
||||||
|
import qualified Conduit as C
|
||||||
import qualified Control.Monad.Trans.Resource as R
|
import qualified Control.Monad.Trans.Resource as R
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
import Data.Conduit (yield)
|
import Data.Conduit (yield)
|
||||||
import qualified Data.Conduit as C
|
|
||||||
import qualified Data.Conduit.Binary as CB
|
import qualified Data.Conduit.Binary as CB
|
||||||
import Data.Conduit.Combinators (sinkList)
|
import Data.Conduit.Combinators (sinkList)
|
||||||
import qualified Data.HashMap.Strict as H
|
import qualified Data.HashMap.Strict as H
|
||||||
@ -42,6 +42,7 @@ import Lib.Prelude
|
|||||||
|
|
||||||
import Network.Minio
|
import Network.Minio
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
|
import Network.Minio.Data.Crypto
|
||||||
import Network.Minio.PutObject
|
import Network.Minio.PutObject
|
||||||
import Network.Minio.S3API
|
import Network.Minio.S3API
|
||||||
import Network.Minio.Utils
|
import Network.Minio.Utils
|
||||||
@ -109,6 +110,7 @@ liveServerUnitTests = testGroup "Unit tests against a live server"
|
|||||||
, putObjectContentLanguageTest
|
, putObjectContentLanguageTest
|
||||||
, putObjectStorageClassTest
|
, putObjectStorageClassTest
|
||||||
, putObjectUserMetadataTest
|
, putObjectUserMetadataTest
|
||||||
|
, getObjectTest
|
||||||
, copyObjectTests
|
, copyObjectTests
|
||||||
, presignedUrlFunTest
|
, presignedUrlFunTest
|
||||||
, presignedPostPolicyFunTest
|
, presignedPostPolicyFunTest
|
||||||
@ -771,6 +773,39 @@ putObjectUserMetadataTest = funTestWithBucket "putObject user-metadata test" $
|
|||||||
step "Cleanup actions"
|
step "Cleanup actions"
|
||||||
removeObject bucket object
|
removeObject bucket object
|
||||||
|
|
||||||
|
getObjectTest :: TestTree
|
||||||
|
getObjectTest = funTestWithBucket "getObject test" $
|
||||||
|
\step bucket -> do
|
||||||
|
step "putObject with some metadata"
|
||||||
|
let object = "object-with-metadata"
|
||||||
|
size1 = 100 :: Int64
|
||||||
|
|
||||||
|
inputFile <- mkRandFile size1
|
||||||
|
fPutObject bucket object inputFile defaultPutObjectOptions {
|
||||||
|
pooUserMetadata = [ ("x-Amz-meta-mykey1", "myval1")
|
||||||
|
, ("mykey2", "myval2")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
step "get the object - check the metadata matches"
|
||||||
|
-- retrieve obj info to check
|
||||||
|
gor <- getObject bucket object defaultGetObjectOptions
|
||||||
|
let m = oiUserMetadata $ gorObjectInfo gor
|
||||||
|
-- need to do a case-insensitive comparison
|
||||||
|
sortedMeta = sort $ map (\(k, v) -> (T.toLower k, T.toLower v)) $
|
||||||
|
H.toList m
|
||||||
|
ref = sort [("mykey1", "myval1"), ("mykey2", "myval2")]
|
||||||
|
|
||||||
|
liftIO $ (sortedMeta == ref) @? "Metadata mismatch!"
|
||||||
|
|
||||||
|
step "get the object content"
|
||||||
|
getObjectHash <- hashSHA256FromSource $ gorObjectStream gor
|
||||||
|
inputHash <- hashSHA256FromSource $ C.sourceFile inputFile
|
||||||
|
liftIO $ (getObjectHash == inputHash) @? "Input file and output file mismatched!"
|
||||||
|
|
||||||
|
step "Cleanup actions"
|
||||||
|
removeObject bucket object
|
||||||
|
|
||||||
putObjectStorageClassTest :: TestTree
|
putObjectStorageClassTest :: TestTree
|
||||||
putObjectStorageClassTest = funTestWithBucket "putObject storageClass tests" $
|
putObjectStorageClassTest = funTestWithBucket "putObject storageClass tests" $
|
||||||
\step bucket -> do
|
\step bucket -> do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user