packages feed

hal 0.4.3 → 0.4.4

raw patch · 3 files changed

+89/−40 lines, 3 filesdep +conduitdep +conduit-extradep +http-clientPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: conduit, conduit-extra, http-client

API changes (from Hackage documentation)

- AWS.Lambda.RuntimeClient: getBaseRuntimeRequest :: IO Request
+ AWS.Lambda.RuntimeClient: data RuntimeClientConfig
+ AWS.Lambda.RuntimeClient: getRuntimeClientConfig :: IO RuntimeClientConfig
- AWS.Lambda.RuntimeClient: getNextEvent :: Request -> IO (Response Value)
+ AWS.Lambda.RuntimeClient: getNextEvent :: RuntimeClientConfig -> IO (Response Value)
- AWS.Lambda.RuntimeClient: sendEventError :: Request -> ByteString -> String -> IO ()
+ AWS.Lambda.RuntimeClient: sendEventError :: RuntimeClientConfig -> ByteString -> String -> IO ()
- AWS.Lambda.RuntimeClient: sendEventSuccess :: ToJSON a => Request -> ByteString -> a -> IO ()
+ AWS.Lambda.RuntimeClient: sendEventSuccess :: ToJSON a => RuntimeClientConfig -> ByteString -> a -> IO ()
- AWS.Lambda.RuntimeClient: sendInitError :: Request -> String -> IO ()
+ AWS.Lambda.RuntimeClient: sendInitError :: RuntimeClientConfig -> String -> IO ()

Files

hal.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 076bfc1eca4e774099d305583e11d8f5253e39b53ace8f17b0a10c55836700c8+-- hash: fbfaaa4c29a274d91431ffc4f57974eb4e6b8428ddc2c200c9794e7b2eee4a76  name:           hal-version:        0.4.3+version:        0.4.4 synopsis:       A runtime environment for Haskell applications running on AWS Lambda. description:    This library uniquely supports different types of AWS Lambda Handlers for your                 needs/comfort with advanced Haskell. Instead of exposing a single function@@ -48,9 +48,12 @@       aeson     , base >=4.7 && <5     , bytestring+    , conduit+    , conduit-extra     , containers     , envy <2     , exceptions+    , http-client     , http-conduit     , http-types     , mtl
src/AWS/Lambda/Runtime/Value.hs view
@@ -38,8 +38,9 @@   mRuntimeWithContext ) where -import           AWS.Lambda.RuntimeClient (getBaseRuntimeRequest, getNextEvent,-                                           sendEventError, sendEventSuccess, sendInitError)+import           AWS.Lambda.RuntimeClient (RuntimeClientConfig, getRuntimeClientConfig,+                                           getNextEvent, sendEventError, sendEventSuccess,+                                           sendInitError) import           AWS.Lambda.Combinators   (withIOInterface,                                            withFallibleInterface,                                            withPureInterface,@@ -61,8 +62,7 @@ import           Data.Text                (unpack) import           Data.Text.Encoding       (decodeUtf8) import           Data.Time.Clock.POSIX    (posixSecondsToUTCTime)-import           Network.HTTP.Simple      (Request, getResponseBody,-                                           getResponseHeader)+import           Network.HTTP.Simple      (getResponseBody, getResponseHeader) import           System.Environment       (setEnv) import           System.Envy              (decodeEnv) @@ -97,11 +97,11 @@     _ -> Nothing  -runtimeLoop :: (HasLambdaContext r, MonadReader r m, MonadCatch m, MonadIO m, ToJSON result) => Request -> StaticContext ->+runtimeLoop :: (HasLambdaContext r, MonadReader r m, MonadCatch m, MonadIO m, ToJSON result) => RuntimeClientConfig -> StaticContext ->   (Value -> m result) -> m ()-runtimeLoop baseRuntimeRequest staticContext fn = do+runtimeLoop runtimeClientConfig staticContext fn = do   -- Get an event-  nextRes <- liftIO $ getNextEvent baseRuntimeRequest+  nextRes <- liftIO $ getNextEvent runtimeClientConfig    -- If we got an event but our requestId is invalid/missing, there's no hope of meaningful recovery   let reqIdBS = head $ getResponseHeader "Lambda-Runtime-Aws-Request-Id" nextRes@@ -149,8 +149,8 @@         return $ first (displayException :: SomeException -> String) caughtResult    liftIO $ case result of-    Right r -> sendEventSuccess baseRuntimeRequest reqIdBS r-    Left e  -> sendEventError baseRuntimeRequest reqIdBS e+    Right r -> sendEventSuccess runtimeClientConfig reqIdBS r+    Left e  -> sendEventError runtimeClientConfig reqIdBS e   -- | For any monad that supports IO/catch/Reader LambdaContext.@@ -198,17 +198,15 @@ mRuntimeWithContext :: (HasLambdaContext r, MonadCatch m, MonadReader r m, MonadIO m, ToJSON result) =>   (Value -> m result) -> m () mRuntimeWithContext fn = do-  -- TODO: Hide the implementation details of Request and StaticContext.-  -- If we instead have a method that returns an opaque RuntimeClientConfig-  -- that encapsulates these details, and then all clientMethods accept-  -- the RuntimeClientConfig instead of a baseRuntimeRequest.-  baseRuntimeRequest <- liftIO getBaseRuntimeRequest+  -- TODO: Hide the implementation details of StaticContext within+  -- RuntimeClientConfig that encapsulates more details+  runtimeClientConfig <- liftIO getRuntimeClientConfig    possibleStaticCtx <- liftIO $ (decodeEnv :: IO (Either String StaticContext))    case possibleStaticCtx of-    Left err -> liftIO $ sendInitError baseRuntimeRequest err-    Right staticContext -> forever $ runtimeLoop baseRuntimeRequest staticContext fn+    Left err -> liftIO $ sendInitError runtimeClientConfig err+    Right staticContext -> forever $ runtimeLoop runtimeClientConfig staticContext fn  -- | For functions that can read the lambda context and use IO within the same monad. --
src/AWS/Lambda/RuntimeClient.hs view
@@ -8,7 +8,8 @@ -}  module AWS.Lambda.RuntimeClient (-  getBaseRuntimeRequest,+  RuntimeClientConfig,+  getRuntimeClientConfig,   getNextEvent,   sendEventSuccess,   sendEventError,@@ -17,15 +18,26 @@  import           Control.Concurrent        (threadDelay) import           Control.Exception         (displayException, try, throw)+import           Control.Monad             (unless)+import           Control.Monad.IO.Class    (MonadIO, liftIO) import           Data.Aeson                (encode, Value)+import           Data.Aeson.Parser         (value') import           Data.Aeson.Types          (ToJSON) import           Data.Bifunctor            (first) import qualified Data.ByteString           as BS+import           Data.Conduit              (ConduitM, runConduit, yield, (.|))+import           Data.Conduit.Attoparsec   (sinkParser) import           GHC.Generics              (Generic (..))-import           Network.HTTP.Simple       (HttpException,-                                            Request, Response,-                                            getResponseStatus, httpJSON,-                                            httpNoBody, parseRequest,+import           Network.HTTP.Client       (BodyReader, HttpException, Manager,+                                            Request, Response, brRead,+                                            defaultManagerSettings, httpNoBody,+                                            managerConnCount,+                                            managerIdleConnectionCount,+                                            managerResponseTimeout,+                                            managerSetProxy, newManager,+                                            noProxy, parseRequest, responseBody,+                                            responseTimeoutNone, withResponse)+import           Network.HTTP.Simple       (getResponseStatus,                                             setRequestBodyJSON,                                             setRequestBodyLBS,                                             setRequestCheckStatus,@@ -43,6 +55,8 @@  instance ToJSON LambdaError +data RuntimeClientConfig = RuntimeClientConfig Request Manager+ -- Exposed Handlers  -- TODO: It would be interesting if we could make the interface a sort of@@ -50,16 +64,29 @@ -- things off we get a 'getNextEvent' handler and then the 'getNextEvent' -- handler returns both the 'success' and 'error' handlers.  So things like -- baseRequest and reqId are pre-injected.-getBaseRuntimeRequest :: IO Request-getBaseRuntimeRequest = do+getRuntimeClientConfig :: IO RuntimeClientConfig+getRuntimeClientConfig = do   awsLambdaRuntimeApi <- getEnv "AWS_LAMBDA_RUNTIME_API"-  parseRequest $ "http://" ++ awsLambdaRuntimeApi+  req <- parseRequest $ "http://" ++ awsLambdaRuntimeApi+  man <- newManager+           -- In the off chance that they set a proxy value, we don't want to+           -- use it.  There's also no reason to spend time reading env vars.+           $ managerSetProxy noProxy+           $ defaultManagerSettings+             -- This is the most important setting, we must not timeout requests+             { managerResponseTimeout = responseTimeoutNone+             -- We only ever need a single connection, because we'll never make+             -- concurrent requests and never talk to more than one host.+             , managerConnCount = 1+             , managerIdleConnectionCount = 1+             }+  return $ RuntimeClientConfig req man  -- AWS lambda guarantees that we will get valid JSON, -- so parsing is guaranteed to succeed.-getNextEvent :: Request -> IO (Response Value)-getNextEvent baseRuntimeRequest = do-  resOrEx <- runtimeClientRetryTry $ httpJSON $ toNextEventRequest baseRuntimeRequest+getNextEvent :: RuntimeClientConfig -> IO (Response Value)+getNextEvent rcc@(RuntimeClientConfig baseRuntimeRequest manager) = do+  resOrEx <- runtimeClientRetryTry $ flip httpValue manager $ toNextEventRequest baseRuntimeRequest   let checkStatus res = if not $ statusIsSuccessful $ getResponseStatus res then         Left "Unexpected Runtime Error:  Could not retrieve next event."       else@@ -67,13 +94,13 @@   let resOrMsg = first (displayException :: HttpException -> String) resOrEx >>= checkStatus   case resOrMsg of     Left msg -> do-      _ <- sendInitError baseRuntimeRequest msg+      _ <- sendInitError rcc msg       error msg     Right y -> return y -sendEventSuccess :: ToJSON a => Request -> BS.ByteString -> a -> IO ()-sendEventSuccess baseRuntimeRequest reqId json = do-  resOrEx <- runtimeClientRetryTry $ httpNoBody $ toEventSuccessRequest reqId json baseRuntimeRequest+sendEventSuccess :: ToJSON a => RuntimeClientConfig -> BS.ByteString -> a -> IO ()+sendEventSuccess rcc@(RuntimeClientConfig baseRuntimeRequest manager) reqId json = do+  resOrEx <- runtimeClientRetryTry $ flip httpNoBody manager $ toEventSuccessRequest reqId json baseRuntimeRequest    let resOrTypedMsg = case resOrEx of         Left ex ->@@ -94,17 +121,38 @@   case resOrTypedMsg of     Left (Left msg) ->       -- If an exception occurs here, we want that to propogate-      sendEventError baseRuntimeRequest reqId msg+      sendEventError rcc reqId msg     Left (Right msg) -> error msg     Right () -> return () -sendEventError :: Request -> BS.ByteString -> String -> IO ()-sendEventError baseRuntimeRequest reqId e =-  fmap (const ()) $ runtimeClientRetry $ httpNoBody $ toEventErrorRequest reqId e baseRuntimeRequest+sendEventError :: RuntimeClientConfig -> BS.ByteString -> String -> IO ()+sendEventError (RuntimeClientConfig baseRuntimeRequest manager) reqId e =+  fmap (const ()) $ runtimeClientRetry $ flip httpNoBody manager $ toEventErrorRequest reqId e baseRuntimeRequest -sendInitError :: Request -> String -> IO ()-sendInitError baseRuntimeRequest e =-  fmap (const ()) $ runtimeClientRetry $ httpNoBody $ toInitErrorRequest e baseRuntimeRequest+sendInitError :: RuntimeClientConfig -> String -> IO ()+sendInitError (RuntimeClientConfig baseRuntimeRequest manager) e =+  fmap (const ()) $ runtimeClientRetry $ flip httpNoBody manager $ toInitErrorRequest e baseRuntimeRequest++-- Helpers for Requests with JSON Bodies++httpValue :: Request -> Manager -> IO (Response Value)+httpValue request manager =+  withResponse request manager (\bodyReaderRes -> do+    value <- runConduit $ bodyReaderSource (responseBody bodyReaderRes) .| sinkParser value'+    return $ fmap (const value) bodyReaderRes+  )++bodyReaderSource :: MonadIO m+                 => BodyReader+                 -> ConduitM i BS.ByteString m ()+bodyReaderSource br =+    loop+  where+    loop = do+        bs <- liftIO $ brRead br+        unless (BS.null bs) $ do+            yield bs+            loop  -- Retry Helpers