packages feed

mig-wai (empty) → 0.1.0.0

raw patch · 5 files changed

+234/−0 lines, 5 filesdep +basedep +bytestringdep +containerssetup-changed

Dependencies added: base, bytestring, containers, data-default, exceptions, mig, text, wai

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Anton Kholomiov (c) 2023++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 Author name here 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,3 @@+# mig-wai++Library to render mig-servers as WAI-applications.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ mig-wai.cabal view
@@ -0,0 +1,49 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.2.+--+-- see: https://github.com/sol/hpack++name:               mig-wai+version:            0.1.0.0+synopsis:           Render mig-servers as wai-applications+description:        Library to render mig-servers as WAI-applications.+category:           Web+homepage:           https://github.com/anton-k/mig#readme+bug-reports:        https://github.com/anton-k/mig/issues+author:             Anton Kholomiov+maintainer:         anton.kholomiov@gmail.com+copyright:          2023 Anton Kholomiov+license:            BSD3+license-file:       LICENSE+build-type:         Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/anton-k/mig++library+  exposed-modules:+      Mig.Server.Wai+  other-modules:+      Paths_mig_wai+  hs-source-dirs:+      src+  default-extensions:+      OverloadedRecordDot+      DuplicateRecordFields+      OverloadedStrings+      LambdaCase+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -Wunused-packages+  build-depends:+      base >=4.7 && <5+    , bytestring+    , containers+    , data-default+    , exceptions+    , mig >=0.2+    , text+    , wai+  default-language: GHC2021
+ src/Mig/Server/Wai.hs view
@@ -0,0 +1,149 @@+-- | Converts mig server to WAI-application.+module Mig.Server.Wai (+  ServerConfig (..),+  FindRouteType (..),+  Kilobytes,+  toApplication,+) where++import Control.Monad.Catch+import Data.ByteString qualified as B+import Data.ByteString.Lazy qualified as BL+import Data.Default+import Data.Foldable+import Data.IORef+import Data.Map.Strict qualified as Map+import Data.Maybe+import Data.Sequence (Seq (..), (|>))+import Data.Sequence qualified as Seq+import Data.Text (Text)+import Network.Wai qualified as Wai++import Mig.Core+import Mig.Core.Server.Cache++-- | Size of the input body+type Kilobytes = Int++-- | Server config+data ServerConfig = ServerConfig+  { maxBodySize :: Maybe Kilobytes+  -- ^ limit the request body size. By default it is unlimited.+  , cache :: Maybe CacheConfig+  -- ^ LRU cache if needed (default is no cache)+  , findRoute :: FindRouteType+  -- ^ API normal form and find route strategy (default is plain api finder)+  }++instance Default ServerConfig where+  def = ServerConfig Nothing Nothing PlainFinder++-- | Algorithm to find route handlers by path+data FindRouteType+  = -- | converts api to tree-like structure (prefer it for servers with many routes)+    TreeFinder+  | -- | no optimization (prefer it for small servers)+    PlainFinder++-- | Converts mig server to WAI-application.+-- Note that only IO-based servers are supported. To use custom monad+-- we can use @hoistServer@ function which renders monad to IO based or+-- the class @HasServer@ which defines such transformatio for several useful cases.+toApplication :: ServerConfig -> Server IO -> Wai.Application+toApplication config = case config.cache of+  Just cacheConfig ->+    case config.findRoute of+      TreeFinder -> toApplicationWithCache cacheConfig config treeApiStrategy+      PlainFinder -> toApplicationWithCache cacheConfig config plainApiStrategy+  Nothing ->+    case config.findRoute of+      TreeFinder -> toApplicationNoCache config treeApiStrategy+      PlainFinder -> toApplicationNoCache config plainApiStrategy++-- | Convert server to WAI-application+toApplicationNoCache :: ServerConfig -> FindRoute nf IO -> Server IO -> Wai.Application+toApplicationNoCache config findRoute server req procResponse = do+  mResp <- handleServerError onErr (fromServer findRoute server) =<< fromRequest config.maxBodySize req+  procResponse $ toWaiResponse $ fromMaybe noResult mResp+  where+    noResult = badRequest @Text ("Server produces nothing" :: Text)++    onErr :: SomeException -> ServerFun IO+    onErr err = const $ pure $ Just $ badRequest @Text $ "Error: Exception has happened: " <> toText (show err)++-- | Convert server to WAI-application+toApplicationWithCache :: CacheConfig -> ServerConfig -> FindRoute nf IO -> Server IO -> Wai.Application+toApplicationWithCache cacheConfig config findRoute server req procResponse = do+  cache <- newRouteCache cacheConfig+  mResp <- handleServerError onErr (fromServerWithCache findRoute cache server) =<< fromRequest config.maxBodySize req+  procResponse $ toWaiResponse $ fromMaybe noResult mResp+  where+    noResult = badRequest @Text ("Server produces nothing" :: Text)++    onErr :: SomeException -> ServerFun IO+    onErr err = const $ pure $ Just $ badRequest @Text $ "Error: Exception has happened: " <> toText (show err)++-- | Convert response to low-level WAI-response+toWaiResponse :: Response -> Wai.Response+toWaiResponse resp =+  case resp.body of+    FileResp file -> Wai.responseFile resp.status resp.headers file Nothing+    RawResp _ str -> lbs str+    StreamResp -> undefined -- TODO+  where+    lbs = Wai.responseLBS resp.status resp.headers++{-| Read request from low-level WAI-request+First argument limits the size of input body. The body is read in chunks.+-}+fromRequest :: Maybe Kilobytes -> Wai.Request -> IO Request+fromRequest maxSize req = do+  bodyCache <- newBodyCache+  pure $+    Request+      { path = Wai.pathInfo req+      , query = Map.fromList (Wai.queryString req)+      , headers = Map.fromList $ Wai.requestHeaders req+      , method = Wai.requestMethod req+      , readBody = readBodyCache getRequestBody bodyCache+      , capture = mempty+      , isSecure = Wai.isSecure req+      }+  where+    getRequestBody =+      fmap (fmap BL.fromChunks) $ readRequestBody (Wai.getRequestBodyChunk req) maxSize++newtype BodyCache a = BodyCache (IORef (Maybe a))++newBodyCache :: IO (BodyCache a)+newBodyCache = BodyCache <$> newIORef Nothing++readBodyCache :: IO a -> BodyCache a -> IO a+readBodyCache getter (BodyCache ref) = do+  mVal <- readIORef ref+  case mVal of+    Just val -> pure val+    Nothing -> do+      val <- getter+      writeIORef ref (Just val)+      pure val++-- | Read request body in chunks. Note that this function can be used only once+readRequestBody :: IO B.ByteString -> Maybe Kilobytes -> IO (Either Text [B.ByteString])+readRequestBody readChunk maxSize = loop 0 Seq.empty+  where+    loop :: Kilobytes -> Seq B.ByteString -> IO (Either Text [B.ByteString])+    loop !currentSize !result+      | isBigger currentSize = pure outOfSize+      | otherwise = do+          chunk <- readChunk+          if B.null chunk+            then pure $ Right (toList result)+            else loop (currentSize + B.length chunk) (result |> chunk)++    outOfSize :: Either Text a+    outOfSize = Left "Request is too big Jim!"++    isBigger = case maxSize of+      Just size -> \current -> current > size+      Nothing -> const False