serverless-haskell 0.10.5 → 0.11.2
raw patch · 5 files changed
+82/−51 lines, 5 filesdep +http-clientdep +safe-exceptionsdep −networkdep −network-simple
Dependencies added: http-client, safe-exceptions
Dependencies removed: network, network-simple
Files
- README.md +15/−3
- serverless-haskell.cabal +7/−7
- src/AWSLambda.hs +2/−12
- src/AWSLambda/Events/APIGateway.hs +0/−1
- src/AWSLambda/Handler.hs +58/−28
README.md view
@@ -121,8 +121,13 @@ [AWSLambda.Events.APIGateway](https://hackage.haskell.org/package/serverless-haskell/docs/AWSLambda-Events-APIGateway.html) in the handler to process them. -[Serverless Offline] can be used for local testing of API Gateway requests.+[Serverless Offline] can be used for local testing of API Gateway requests. You+must use `--useDocker` flag so that the native Haskell runtime works correctly. +When using [Serverless Offline], make sure that the project directory is+world-readable, otherwise the started Docker container will be unable to access+the handlers and all invocations will return HTTP status 502.+ ### Notes * Only AWS Lambda is supported at the moment. Other cloud providers would@@ -153,8 +158,14 @@ Integration test is only automatically run up to deployment due to the need for an AWS account. To run manually: -* Ensure you have the required dependencies: `curl`, [jq], [NPM], `pwgen` and- [Stack].+* Ensure you have the required dependencies:+ - `curl`+ - [jq]+ - `libpcre` headers (`-devel` package or similar)+ - [NPM]+ - [`pkg-config`](pkg-config)+ - `pwgen`+ - [Stack] * Get an AWS account and add the access credentials into your shell environment. * Run `./integration-test/run.sh`. The exit code indicates success. * To verify just the packaging, without deployment, run@@ -180,6 +191,7 @@ [Docker]: https://www.docker.com/ [jq]: https://stedolan.github.io/jq/ [NPM]: https://www.npmjs.com/+[pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/ [Serverless]: https://serverless.com/framework/ [Serverless Offline]: https://github.com/dherault/serverless-offline [Stack]: https://haskellstack.org
serverless-haskell.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: a0188a30ab4b0a3456e1359270cfe59d4445645d2de25f44bc3c3a1ccdf9a551+-- hash: 81b4069c39b4cebbf975a9e2d1fc55161e337ed88e6a6f0cf45373663399f0b5 name: serverless-haskell-version: 0.10.5+version: 0.11.2 synopsis: Deploying Haskell code onto AWS Lambda using Serverless description: Utilities to help process the events from AWS Lambda when deployed with the serverless-haskell plugin. category: AWS, Cloud, Network@@ -54,11 +54,11 @@ , base >=4.7 && <5 , bytestring , case-insensitive+ , http-client , http-types , iproute , lens- , network- , network-simple+ , safe-exceptions , text , time , unix@@ -92,12 +92,12 @@ , case-insensitive , hspec , hspec-discover+ , http-client , http-types , iproute , lens- , network- , network-simple , raw-strings-qq+ , safe-exceptions , serverless-haskell , text , time
src/AWSLambda.hs view
@@ -55,7 +55,8 @@ handler to process them. <https://github.com/dherault/serverless-offline Serverless Offline> can be used-for local testing of API Gateway requests.+for local testing of API Gateway requests. You must use @--useDocker@ flag so+that the native Haskell runtime works correctly. = Additional features @@ -70,17 +71,6 @@ > stackBuildArgs: > - --pedantic > - --allow-different-user--* To start the executable with extra arguments, add them to @arguments@ under-the function name:-- > custom:- > haskell:- > arguments:- > myfunc:- > - --arg1- > - --arg2- > - arg3 * Dependent system libraries not present in the AWS Lambda environment will be automatically uploaded along with the executable. Note that while statically
src/AWSLambda/Events/APIGateway.hs view
@@ -37,7 +37,6 @@ import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap import Data.IP-import Data.Text (Text) import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8, encodeUtf8) import GHC.Generics (Generic)
src/AWSLambda/Handler.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ {-| Module : AWSLambda.Handler Stability : experimental@@ -9,13 +11,16 @@ ( lambdaMain ) where -import Control.Exception (finally)-import Control.Monad (void)+import Control.Exception.Safe (SomeException(..), displayException, tryAny)+import Control.Monad (forever, void) +import Data.Aeson ((.=)) import qualified Data.Aeson as Aeson -import Data.ByteString (ByteString)+import Data.Typeable (typeOf)+ import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as Char8 import qualified Data.ByteString.Lazy as LBS import qualified Data.Text.Encoding as Text@@ -23,9 +28,8 @@ import GHC.IO.Handle (BufferMode(..), hSetBuffering) -import Network.Simple.TCP (connect)-import Network.Socket (close, withSocketsDo)-import Network.Socket.ByteString (send)+import Network.HTTP.Client+import Network.HTTP.Types (HeaderName) import System.Environment (lookupEnv) import System.IO (stdout)@@ -84,31 +88,57 @@ => (event -> IO res) -- ^ Function to process the event -> IO () lambdaMain act =- withSendResult $ \sendResult -> do- input <- ByteString.getLine- case Aeson.eitherDecodeStrict input of+ runMain $ \input -> do+ case Aeson.eitherDecode input of Left err -> error err Right event -> do result <- act event- sendResult $ LBS.toStrict $ Aeson.encode result- pure ()+ pure $ Aeson.encode result --- | Invoke an action with the method to output the result. If called by the--- JavaScript wrapper, use the server started by it, otherwise use standard--- output. Also set line buffering on standard output for AWS Lambda so the logs--- are output in a timely manner.-withSendResult :: ((ByteString -> IO ()) -> IO r) -> IO r-withSendResult act = do- communicationPortString <- lookupEnv communicationPortEnv- case communicationPortString of- Just communicationPort -> do+-- Process the incoming requests (using the AWS Lambda runtime interface or from the standard input).+-- Also set line buffering on standard output for AWS Lambda so the logs are output in a timely manner.+runMain :: (LBS.ByteString -> IO LBS.ByteString) -> IO ()+runMain act = do+ lambdaApiAddress <- lookupEnv lambdaApiAddressEnv+ case lambdaApiAddress of+ Just address -> do hSetBuffering stdout LineBuffering- withSocketsDo $- connect "127.0.0.1" communicationPort $ \(socket, _) ->- act (void . send socket) `finally` close socket- Nothing -> act $ Text.putStrLn . Text.decodeUtf8+ manager <- newManager defaultManagerSettings+ forever $ do+ invocation <- httpLbs (invocationRequest address) manager+ let input = responseBody invocation+ let requestId = responseRequestId invocation+ resultOrError <- tryAny $ act input+ case resultOrError of+ Right result -> void $ httpNoBody (resultRequest address requestId result) manager+ Left exception -> void $ httpNoBody (errorRequest address requestId exception) manager+ Nothing -> do+ input <- LBS.fromStrict <$> ByteString.getLine+ result <- act input+ Text.putStrLn $ Text.decodeUtf8 $ LBS.toStrict result --- | Environment variable signalling the port the JavaScript wrapper is--- listening on-communicationPortEnv :: String-communicationPortEnv = "SERVERLESS_HASKELL_COMMUNICATION_PORT"+lambdaApiAddressEnv :: String+lambdaApiAddressEnv = "AWS_LAMBDA_RUNTIME_API"++lambdaRequest :: String -> String -> Request+lambdaRequest apiAddress rqPath = parseRequest_ $ "http://" ++ apiAddress ++ "/2018-06-01" ++ rqPath++invocationRequest :: String -> Request+invocationRequest apiAddress = (lambdaRequest apiAddress "/runtime/invocation/next") { responseTimeout = responseTimeoutNone }++resultRequest :: String -> String -> LBS.ByteString -> Request+resultRequest apiAddress requestId result = (lambdaRequest apiAddress $ "/runtime/invocation/" ++ requestId ++ "/response") { method = "POST", requestBody = RequestBodyLBS result }++errorRequest :: String -> String -> SomeException -> Request+errorRequest apiAddress requestId exception = (lambdaRequest apiAddress $ "/runtime/invocation/" ++ requestId ++ "/error") { method = "POST", requestBody = RequestBodyLBS body }+ where+ body = Aeson.encode $ Aeson.object [ "errorMessage" .= displayException exception, "errorType" .= exceptionType exception]++exceptionType :: SomeException -> String+exceptionType (SomeException e) = show (typeOf e)++requestIdHeader :: HeaderName+requestIdHeader = "Lambda-Runtime-Aws-Request-Id"++responseRequestId :: Response a -> String+responseRequestId = Char8.unpack . snd . head . filter (uncurry $ \h _ -> h == requestIdHeader) . responseHeaders