Add Minio monad and start getService

This commit is contained in:
Aditya Manthramurthy 2016-10-31 01:10:30 -07:00
parent 4569348dc2
commit 07eb59fda5
5 changed files with 126 additions and 69 deletions

View File

@ -1,19 +1,13 @@
module Main where module Main where
import qualified Network.HTTP.Types as HT -- import qualified Network.HTTP.Conduit as NC
import qualified Network.HTTP.Conduit as NC
import Protolude import Protolude
import Network.Minio.API import Network.Minio.API
import Network.Minio.Data
main :: IO () main :: IO ()
main = do main = do
resp <- minioExecute mc req mc <- connect defaultConnectInfo
print $ NC.responseStatus resp res <- runMinio mc $ getService
print $ NC.responseHeaders resp print res
print $ NC.responseBody resp
where
mc = MinioClient "localhost" 9000 "abcd1" "abcd1234" False "us-east-1"
req = RequestInfo HT.methodGet Nothing Nothing [] [] "" ""

View File

@ -1,12 +1,16 @@
module Network.Minio.API module Network.Minio.API
( (
minioExecute connect
, defaultConnectInfo
, RequestInfo(..)
, runMinio
, getService
) where ) where
import Network.HTTP.Client (defaultManagerSettings)
import qualified Network.HTTP.Types as HT import qualified Network.HTTP.Types as HT
import Network.HTTP.Conduit (Response) import Network.HTTP.Conduit (Response)
import qualified Network.HTTP.Conduit as NC import qualified Network.HTTP.Conduit as NC
import Network.HTTP.Types (Method, Header, Query)
import Lib.Prelude import Lib.Prelude
@ -14,37 +18,52 @@ import Network.Minio.Data
import Network.Minio.Data.Crypto import Network.Minio.Data.Crypto
import Network.Minio.Sign.V4 import Network.Minio.Sign.V4
runRequestDebug r mgr = do -- runRequestDebug r mgr = do
print $ "runRequestDebug" -- print $ "runRequestDebug"
print $ NC.method r -- print $ NC.method r
print $ NC.secure r -- print $ NC.secure r
print $ NC.host r -- print $ NC.host r
print $ NC.port r -- print $ NC.port r
print $ NC.path r -- print $ NC.path r
print $ NC.queryString r -- print $ NC.queryString r
print $ NC.requestHeaders r -- print $ NC.requestHeaders r
-- print $ NC.requestBody r -- -- print $ NC.requestBody r
NC.httpLbs r mgr -- NC.httpLbs r mgr
minioExecute :: MinioClient -> RequestInfo -> IO (Response LByteString) mkSRequest :: RequestInfo -> Minio (Response LByteString)
minioExecute mc ri = do mkSRequest ri = do
mgr <- NC.newManager defaultManagerSettings let PayloadSingle pload = payload ri
finalHeaders <- signV4 mc updatedRI phash = hashSHA256 pload
runRequestDebug (req finalHeaders) mgr newRI = ri {
where payloadHash = phash
req h = NC.defaultRequest { , headers = ("x-amz-content-sha256", phash) : (headers ri)
NC.method = method ri }
, NC.secure = mcIsSecure mc
, NC.host = encodeUtf8 $ mcEndPointHost mc
, NC.port = mcEndPointPort mc
, NC.path = getPathFromRI ri
, NC.queryString = HT.renderQuery False $ queryParams ri
, NC.requestHeaders = h
, NC.requestBody = NC.RequestBodyBS (payload ri)
}
phash = hashSHA256 $ payload ri ci <- asks mcConnInfo
updatedRI = ri {
payloadHash = phash reqHeaders <- liftIO $ signV4 ci newRI
, headers = ("x-amz-content-sha256", phash) : (headers ri)
} mgr <- asks mcConnManager
let req = NC.defaultRequest {
NC.method = method newRI
, NC.secure = connectIsSecure ci
, NC.host = encodeUtf8 $ connectHost ci
, NC.port = connectPort ci
, NC.path = getPathFromRI ri
, NC.queryString = HT.renderQuery False $ queryParams ri
, NC.requestHeaders = reqHeaders
, NC.requestBody = NC.RequestBodyBS pload
}
NC.httpLbs req mgr
requestInfo :: Method -> Maybe Bucket -> Maybe Object
-> Query -> [Header] -> Payload
-> RequestInfo
requestInfo m b o q h p = RequestInfo m b o q h p ""
getService :: Minio (Response LByteString)
getService = mkSRequest $
requestInfo HT.methodGet Nothing Nothing [] [] $
PayloadSingle ""

View File

@ -1,36 +1,52 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Network.Minio.Data module Network.Minio.Data
( (
MinioClient(..) ConnectInfo(..)
, RequestInfo(..) , RequestInfo(..)
, MinioConn(..)
, Bucket , Bucket
, Object , Object
, getPathFromRI , getPathFromRI
, Minio
, runMinio
, defaultConnectInfo
, connect
, Payload(..)
) where ) where
import qualified Data.ByteString as B import qualified Data.ByteString as B
import Network.HTTP.Client (defaultManagerSettings)
import Network.HTTP.Types (Method, Header, Query) import Network.HTTP.Types (Method, Header, Query)
import qualified Network.HTTP.Conduit as NC
import Lib.Prelude import Lib.Prelude
data MinioClient = MinioClient { data ConnectInfo = ConnectInfo {
mcEndPointHost :: Text connectHost :: Text
, mcEndPointPort :: Int , connectPort :: Int
, mcAccessKey :: Text , connectAccessKey :: Text
, mcSecretKey :: Text , connectSecretKey :: Text
, mcIsSecure :: Bool , connectIsSecure :: Bool
, mcRegion :: Text , connectRegion :: Text
} deriving (Eq, Show) } deriving (Eq, Show)
defaultConnectInfo :: ConnectInfo
defaultConnectInfo =
ConnectInfo "localhost" 9000 "minio" "minio123" False "us-east-1"
type Bucket = ByteString type Bucket = ByteString
type Object = Text type Object = Text
data Payload = PayloadSingle ByteString
deriving (Show, Eq)
data RequestInfo = RequestInfo { data RequestInfo = RequestInfo {
method :: Method method :: Method
, bucket :: Maybe Bucket , bucket :: Maybe Bucket
, object :: Maybe Object , object :: Maybe Object
, queryParams :: Query , queryParams :: Query
, headers :: [Header] , headers :: [Header]
, payload :: ByteString , payload :: Payload
, payloadHash :: ByteString , payloadHash :: ByteString
} }
@ -39,3 +55,30 @@ getPathFromRI ri = B.concat $ parts
where where
objPart = maybe [] (\o -> ["/", encodeUtf8 o]) $ object ri objPart = maybe [] (\o -> ["/", encodeUtf8 o]) $ object ri
parts = maybe ["/"] (\b -> "/" : b : objPart) $ bucket ri parts = maybe ["/"] (\b -> "/" : b : objPart) $ bucket ri
data MinioErr = MErrMsg ByteString
deriving (Show)
newtype Minio a = Minio {
unMinio :: ReaderT MinioConn (ExceptT MinioErr IO) a
} deriving (
Functor
, Applicative
, Monad
, MonadIO
, MonadReader MinioConn
)
-- MinioConn holds connection info and a connection pool
data MinioConn = MinioConn {
mcConnInfo :: ConnectInfo
, mcConnManager :: NC.Manager
}
connect :: ConnectInfo -> IO MinioConn
connect ci = do
mgr <- NC.newManager defaultManagerSettings
return $ MinioConn ci mgr
runMinio :: MinioConn -> Minio a -> IO (Either MinioErr a)
runMinio conn = runExceptT . flip runReaderT conn . unMinio

View File

@ -7,8 +7,8 @@ module Network.Minio.Data.Crypto
, digestToBase16 , digestToBase16
) where ) where
import Crypto.Hash (SHA256(..), hashWith, Digest) import Crypto.Hash (SHA256(..), hashWith)
import Crypto.MAC.HMAC (hmac, HMAC(hmacGetDigest)) import Crypto.MAC.HMAC (hmac, HMAC)
import Data.ByteArray (ByteArrayAccess, convert) import Data.ByteArray (ByteArrayAccess, convert)
import Data.ByteArray.Encoding (convertToBase, Base(Base16)) import Data.ByteArray.Encoding (convertToBase, Base(Base16))

View File

@ -6,6 +6,7 @@ module Network.Minio.Sign.V4
, getHeadersToSign , getHeadersToSign
, getCanonicalRequest , getCanonicalRequest
, SignV4Data(..) , SignV4Data(..)
, debugPrintSignV4Data
) where ) where
import qualified Data.ByteString as B import qualified Data.ByteString as B
@ -64,34 +65,34 @@ debugPrintSignV4Data (SignV4Data t s cr h2s ih oh sts sk) = do
-- request path, headers, query params and payload hash, generates an -- request path, headers, query params and payload hash, generates an
-- updated set of headers, including the x-amz-date header and the -- updated set of headers, including the x-amz-date header and the
-- Authorization header, which includes the signature. -- Authorization header, which includes the signature.
signV4 :: MinioClient -> RequestInfo signV4 :: ConnectInfo -> RequestInfo
-> IO [Header] -> IO [Header]
signV4 mc ri = do signV4 ci ri = do
timestamp <- Time.getCurrentTime timestamp <- Time.getCurrentTime
let signData = signV4AtTime timestamp mc ri let signData = signV4AtTime timestamp ci ri
debugPrintSignV4Data signData -- debugPrintSignV4Data signData
return $ sv4OutputHeaders signData return $ sv4OutputHeaders signData
-- | Takes a timestamp, server params and request params and generates -- | Takes a timestamp, server params and request params and generates
-- an updated list of headers. -- an updated list of headers.
signV4AtTime :: UTCTime -> MinioClient -> RequestInfo -> SignV4Data signV4AtTime :: UTCTime -> ConnectInfo -> RequestInfo -> SignV4Data
signV4AtTime ts mc ri = signV4AtTime ts ci ri =
SignV4Data ts scope canonicalRequest headersToSign (headers ri) outHeaders stringToSign signingKey SignV4Data ts scope canonicalRequest headersToSign (headers ri) outHeaders stringToSign signingKey
where where
outHeaders = authHeader : headersWithDate outHeaders = authHeader : headersWithDate
timeBS = awsTimeFormatBS ts timeBS = awsTimeFormatBS ts
dateHeader = (mk "X-Amz-Date", timeBS) dateHeader = (mk "X-Amz-Date", timeBS)
hostHeader = (mk "host", encodeUtf8 $ mcEndPointHost mc) hostHeader = (mk "host", encodeUtf8 $ connectHost ci)
headersWithDate = dateHeader : hostHeader : (headers ri) headersWithDate = dateHeader : hostHeader : (headers ri)
authHeader = (mk "Authorization", authHeaderValue) authHeader = (mk "Authorization", authHeaderValue)
scope = getScope ts mc scope = getScope ts ci
authHeaderValue = B.concat [ authHeaderValue = B.concat [
"AWS4-HMAC-SHA256 Credential=", "AWS4-HMAC-SHA256 Credential=",
encodeUtf8 (mcAccessKey mc), "/", scope, encodeUtf8 (connectAccessKey ci), "/", scope,
", SignedHeaders=", signedHeaders, ", SignedHeaders=", signedHeaders,
", Signature=", signature ", Signature=", signature
] ]
@ -104,9 +105,9 @@ signV4AtTime ts mc ri =
signingKey = hmacSHA256RawBS "aws4_request" signingKey = hmacSHA256RawBS "aws4_request"
. hmacSHA256RawBS "s3" . hmacSHA256RawBS "s3"
. hmacSHA256RawBS (encodeUtf8 $ mcRegion mc) . hmacSHA256RawBS (encodeUtf8 $ connectRegion ci)
. hmacSHA256RawBS (awsDateFormatBS ts) . hmacSHA256RawBS (awsDateFormatBS ts)
$ (B.concat ["AWS4", encodeUtf8 $ mcSecretKey mc]) $ (B.concat ["AWS4", encodeUtf8 $ connectSecretKey ci])
stringToSign = B.intercalate "\n" $ stringToSign = B.intercalate "\n" $
["AWS4-HMAC-SHA256", ["AWS4-HMAC-SHA256",
@ -118,10 +119,10 @@ signV4AtTime ts mc ri =
canonicalRequest = getCanonicalRequest ri headersToSign canonicalRequest = getCanonicalRequest ri headersToSign
getScope :: UTCTime -> MinioClient -> ByteString getScope :: UTCTime -> ConnectInfo -> ByteString
getScope ts mc = B.intercalate "/" $ [ getScope ts ci = B.intercalate "/" $ [
pack $ Time.formatTime Time.defaultTimeLocale "%Y%m%d" ts, pack $ Time.formatTime Time.defaultTimeLocale "%Y%m%d" ts,
"us-east-1", "s3", "aws4_request" encodeUtf8 $ connectRegion ci, "s3", "aws4_request"
] ]
getHeadersToSign :: [Header] -> [(ByteString, ByteString)] getHeadersToSign :: [Header] -> [(ByteString, ByteString)]