packages feed

aws-lambda-haskell-runtime-wai (empty) → 1.0.0

raw patch · 7 files changed

+321/−0 lines, 7 filesdep +aesondep +aws-lambda-haskell-runtimedep +aws-lambda-haskell-runtime-waisetup-changed

Dependencies added: aeson, aws-lambda-haskell-runtime, aws-lambda-haskell-runtime-wai, base, binary, bytestring, case-insensitive, http-types, iproute, network, text, unordered-containers, vault, wai

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for aws-lambda-haskell-runtime-wai++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Dobromir Nikolov (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Dobromir Nikolov nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,28 @@+# aws-lambda-haskell-runtime-wai ![Haskell CI](https://github.com/eir-forsakring/aws-lambda-haskell-runtime-wai/workflows/Haskell%20CI/badge.svg)++## Quick start++1. Set up your project to use AWS Lambda by following the instructions on the [aws-lambda-haskell-runtime website](https://theam.github.io/aws-lambda-haskell-runtime/).+2. Use the `waiHandler` function from `AWS.Lambda.Wai` to convert your [`wai`](https://hackage.haskell.org/package/wai) application to a handler. There are two ways to do this.++```haskell+-- 1. Pass in the initializeApplicationFunction+-- this will call initializeApplication per each call+handler :: WaiHandler ()+handler = waiHandler initializeApplication++-- Wai application initialization logic+initializeApplication :: IO Application+initializeApplication = ...+``` ++```haskell+-- 2. Store the Application inside your custom context and provide a getter function+-- this will initialize the application once per cold start and keep it alive while the lambda is warm+handler :: WaiHandler MyAppConfig+handler = waiHandler' getWaiApp++data MyAppConfig =+  MyAppConfig+    { getWaiApp :: Application }+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ aws-lambda-haskell-runtime-wai.cabal view
@@ -0,0 +1,76 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 84d5705be40077a09e34d445f54b862f58447fa5abac05fd535936a3196c6bbb++name:           aws-lambda-haskell-runtime-wai+version:        1.0.0+synopsis:       Run wai applications on AWS Lambda+description:    Please see the README on GitHub at <https://github.com/eir-forsakring/aws-lambda-haskell-runtime-wai#readme>+category:       AWS+homepage:       https://github.com/eir-forsakring/aws-lambda-haskell-runtime-wai#readme+bug-reports:    https://github.com/eir-forsakring/aws-lambda-haskell-runtime-wai/issues+author:         Dobromir Nikolov+maintainer:     dnikolovv@hotmail.com+copyright:      Eir Försäkring+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/eir-forsakring/aws-lambda-haskell-runtime-wai++library+  exposed-modules:+      Aws.Lambda.Wai+  other-modules:+      Paths_aws_lambda_haskell_runtime_wai+  hs-source-dirs:+      src+  build-depends:+      aeson+    , aws-lambda-haskell-runtime >=3.0.0+    , base >=4.7 && <5+    , binary+    , bytestring+    , case-insensitive+    , http-types+    , iproute+    , network+    , text+    , unordered-containers+    , vault+    , wai+  default-language: Haskell2010++test-suite aws-lambda-haskell-runtime-wai-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_aws_lambda_haskell_runtime_wai+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      aeson+    , aws-lambda-haskell-runtime >=3.0.0+    , aws-lambda-haskell-runtime-wai+    , base >=4.7 && <5+    , binary+    , bytestring+    , case-insensitive+    , http-types+    , iproute+    , network+    , text+    , unordered-containers+    , vault+    , wai+  default-language: Haskell2010
+ src/Aws/Lambda/Wai.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns        #-}++module Aws.Lambda.Wai (waiHandler, waiHandler', WaiHandler) where++import           Aws.Lambda+import           Control.Concurrent.MVar+import           Data.Aeson+import qualified Data.Aeson              as Aeson+import qualified Data.Aeson.Types        as Aeson+import qualified Data.Binary.Builder     as Binary+import           Data.ByteString         (ByteString)+import qualified Data.ByteString         as BS+import qualified Data.ByteString.Lazy    as BL+import qualified Data.CaseInsensitive    as CI+import qualified Data.HashMap.Strict     as HMap+import           Data.IORef+import qualified Data.IP                 as IP+import           Data.Text               (Text)+import qualified Data.Text               as T+import           Data.Text.Encoding      (decodeUtf8', encodeUtf8)+import qualified Data.Text.Encoding      as T+import qualified Data.Vault.Lazy         as Vault+import           GHC.IO.Unsafe           (unsafePerformIO)+import qualified Network.HTTP.Types      as H+import qualified Network.Socket          as Socket+import           Network.Wai             (Application)+import qualified Network.Wai             as Wai+import qualified Network.Wai.Internal    as Wai+import           Text.Read               (readMaybe)++type WaiHandler context = ApiGatewayRequest Text -> Context context -> IO (Either (ApiGatewayResponse Text) (ApiGatewayResponse Text))++waiHandler :: forall context. IO Wai.Application -> WaiHandler context+waiHandler initApp gatewayRequest context = initApp >>=+  \app -> waiHandler'' app gatewayRequest context++waiHandler' :: forall context. (context -> Wai.Application) -> WaiHandler context+waiHandler' getApp request context = do+  app <- getApp <$> readIORef (customContext context)+  waiHandler'' app request context++waiHandler'' :: forall context. Wai.Application -> WaiHandler context+waiHandler'' waiApplication gatewayRequest _ = do+  waiRequest <- mkWaiRequest gatewayRequest++  (status, headers, body) <- processRequest waiApplication waiRequest >>= readResponse++  print $ "Working: " <> ("Something went wai" :: ByteString)+  print $ "Actual response body (before decodeUtf8'): " <> body++  if BS.null body+  then return . pure . wrapInResponse (H.statusCode status) headers $ mempty+  else case decodeUtf8' body of+    Right responseBodyText -> do+        print $ "After decoding in wai: " <> responseBodyText+        return . pure . wrapInResponse (H.statusCode status) headers $ responseBodyText+    Left err -> error "Expected a response body that is valid UTF-8."++mkWaiRequest :: ApiGatewayRequest Text -> IO Wai.Request+mkWaiRequest ApiGatewayRequest{..} = do+  let ApiGatewayRequestContext{..} = apiGatewayRequestRequestContext+      ApiGatewayRequestContextIdentity{..} = apiGatewayRequestContextIdentity++  ip <- parseIp apiGatewayRequestContextIdentitySourceIp++  let pathInfo = H.decodePathSegments (encodeUtf8 apiGatewayRequestPath)++  let requestBodyRaw = maybe mempty T.encodeUtf8 apiGatewayRequestBody+  let requestBodyLength = Wai.KnownLength $ fromIntegral $ BS.length requestBodyRaw++  requestBodyMVar <- newMVar requestBodyRaw++  let requestBody = takeRequestBodyChunk requestBodyMVar+  let requestHeaderHost = encodeUtf8 <$> HMap.lookup "host" apiGatewayRequestHeaders+  let requestHeaderRange = encodeUtf8 <$> HMap.lookup "range" apiGatewayRequestHeaders+  let requestHeaderReferer = encodeUtf8 <$> HMap.lookup "referer" apiGatewayRequestHeaders+  let requestHeaderUserAgent = encodeUtf8 <$> HMap.lookup "User-Agent" apiGatewayRequestHeaders++  let queryParameters = toQueryStringParameters apiGatewayRequestQueryStringParameters+      rawQueryString = H.renderQuery True queryParameters+      httpVersion = getHttpVersion apiGatewayRequestContextProtocol++  let result = Wai.Request+                (encodeUtf8 apiGatewayRequestHttpMethod)+                httpVersion+                (encodeUtf8 apiGatewayRequestPath)+                rawQueryString+                (map toHeader $ HMap.toList apiGatewayRequestHeaders)+                True -- We assume it's always secure as we're passing through API Gateway+                ip+                pathInfo+                queryParameters+                requestBody+                Vault.empty+                requestBodyLength+                requestHeaderHost+                requestHeaderRange+                requestHeaderReferer+                requestHeaderUserAgent++  return result++getHttpVersion :: Text -> H.HttpVersion+getHttpVersion protocol+  | "0.9" `T.isSuffixOf` protocol = H.http09+  | "1.0" `T.isSuffixOf` protocol = H.http10+  | "1.1" `T.isSuffixOf` protocol = H.http11+  | "2.0" `T.isSuffixOf` protocol = H.http20+  | otherwise = H.http11++takeRequestBodyChunk :: MVar ByteString -> IO ByteString+takeRequestBodyChunk requestBodyMVar = do+  result <- tryTakeMVar requestBodyMVar+  case result of+    Just bs -> pure bs+    Nothing -> pure BS.empty++toQueryStringParameters :: Maybe [(Text, Maybe Text)] -> [H.QueryItem]+toQueryStringParameters (Just params@(p:ps)) =+  let toQueryItem (key, valueMay) = (encodeUtf8 key, encodeUtf8 <$> valueMay)+  in map toQueryItem params+toQueryStringParameters _ = []++parseIp :: Maybe Text -> IO Socket.SockAddr+parseIp sourceIpText =+  case sourceIpText of+    Just sourceIp ->+      case readMaybe (T.unpack sourceIp) of+      Just ip ->+        pure $ case ip of+          IP.IPv4 ip4 ->+            Socket.SockAddrInet+              0 -- default port+              (IP.toHostAddress ip4)+          IP.IPv6 ip6 ->+            Socket.SockAddrInet6+              0 -- default port+              0 -- flow info+              (IP.toHostAddress6 ip6)+              0 -- scope id+      Nothing -> error "Could not parse source ip."+    Nothing -> error "Missing source ip."++processRequest :: Application -> Wai.Request -> IO Wai.Response+processRequest app req = do+  mvar <- newEmptyMVar+  Wai.ResponseReceived <- app req $ \resp -> do+    putMVar mvar resp+    pure Wai.ResponseReceived+  takeMVar mvar++readResponse :: Wai.Response -> IO (H.Status, H.ResponseHeaders, ByteString)+readResponse (Wai.responseToStream -> (st, hdrs, mkBody)) = do+    body <- mkBody drainBody+    pure (st, hdrs, body)+  where+    drainBody :: Wai.StreamingBody -> IO ByteString+    drainBody body = do+      ioRef <- newIORef Binary.empty+      body+        (\b -> atomicModifyIORef ioRef (\b' -> (b <> b', ())))+        (pure ())+      BL.toStrict . Binary.toLazyByteString <$> readIORef ioRef++wrapInResponse+  :: Int+  -> H.ResponseHeaders+  -> res+  -> ApiGatewayResponse res+wrapInResponse code responseHeaders response =+  ApiGatewayResponse code responseHeaders response False++toHeader :: (Text, Text) -> H.Header+toHeader (name, val) = (CI.mk . encodeUtf8 $ name, encodeUtf8 val)++tshow :: Show a => a -> Text+tshow = T.pack . show
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"