packages feed

polysemy-webserver (empty) → 0.1.0.0

raw patch · 6 files changed

+247/−0 lines, 6 filesdep +basedep +bytestringdep +hspecsetup-changed

Dependencies added: base, bytestring, hspec, http-conduit, http-types, polysemy, polysemy-plugin, polysemy-webserver, wai, warp

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Andrew Miller (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 Andrew Miller 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,6 @@+# polysemy-webserver++A simple wrapper around Warp and Wai to make it easier to write web servers+using Polysemy.++See test/Spec.hs for a simple example of how to use it.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ polysemy-webserver.cabal view
@@ -0,0 +1,65 @@+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: 1125f9e81101158130a70f12db4783c3043caf583c7f7d28e039506c7aed885b++name:           polysemy-webserver+version:        0.1.0.0+synopsis:       Start web servers from within a Polysemy effect stack+description:    Please see the README on GitLab at <https://gitlab.com/A1kmm/polysemy-webserver/-/blob/master/README.md>+category:       Web+author:         Andrew Miller+maintainer:     andrew@amxl.com+copyright:      Copyright (C) 2020 Andrew Miller+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://gitlab.com/A1kmm/polysemy-webserver++library+  exposed-modules:+      Polysemy.WebServer+  other-modules:+      Paths_polysemy_webserver+  hs-source-dirs:+      src+  default-extensions: DataKinds FlexibleContexts GADTs LambdaCase PolyKinds RankNTypes ScopedTypeVariables TypeApplications TypeOperators TypeFamilies+  ghc-options: -O2 -flate-specialise -fspecialise-aggressively -Wall -fno-warn-orphans -fplugin=Polysemy.Plugin+  build-depends:+      base >=4.7 && <5+    , bytestring+    , http-types+    , polysemy+    , polysemy-plugin+    , wai+    , warp+  default-language: Haskell2010++test-suite polysemy-webserver-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_polysemy_webserver+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , bytestring+    , hspec+    , http-conduit+    , http-types+    , polysemy+    , polysemy-plugin+    , polysemy-webserver+    , wai+    , warp+  default-language: Haskell2010
+ src/Polysemy/WebServer.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeApplications #-}++module Polysemy.WebServer (WebServer, PendingWebRequest, startWebServer,+  respondWebRequest, getBody, runWebServerFinal) where+import qualified Network.Wai as Wai+import qualified Network.Wai.Handler.Warp as Warp+import qualified Network.HTTP.Types.Status as HTTP+import Polysemy+import Polysemy.Final+import Data.Functor+import Control.Monad+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS++newtype PendingWebRequest =+  PendingWebRequest (Wai.Response -> IO Wai.ResponseReceived)++data WebServer m a where+  StartWebServer :: Warp.Port -> (+    Wai.Request -> PendingWebRequest -> m Wai.ResponseReceived) ->+    WebServer m ()+  RespondWebRequest :: PendingWebRequest -> Wai.Response ->+                       WebServer m Wai.ResponseReceived+  GetBody :: Int -> Wai.Request -> WebServer m (Maybe BS.ByteString)+makeSem ''WebServer++runStartWebServer :: forall rInitial r f.+  ((Final IO) `Member` r, Functor f) =>+  Warp.Port -> (+    Wai.Request -> PendingWebRequest ->+    Sem rInitial Wai.ResponseReceived) ->+  Sem (WithTactics WebServer f (Sem rInitial) r) (f ())+runStartWebServer port app = do+  s0 <- getInitialStateT+  appFnS <- bindT $ uncurry app+  ins <- getInspectorT+  let+    appFn :: (Wai.Request, PendingWebRequest) ->+             Sem r (Maybe Wai.ResponseReceived)+    appFn = runWebServerFinal . (fmap (inspect ins)) . appFnS . (s0 $>)+  withStrategicToFinal $ do+    appFnS' <- bindS (raise . appFn)+    ins' <- getInspectorS+    s1 <- getInitialStateS+    let+      appFn' :: (Wai.Request, PendingWebRequest) ->+                IO (Maybe Wai.ResponseReceived)+      appFn' = (fmap (join . inspect ins')) . appFnS' . (s1 $>)+    return $ do+      let+        doRequestIO :: Wai.Request -> (Wai.Response -> IO Wai.ResponseReceived) ->+                       IO Wai.ResponseReceived+        doRequestIO req respond = do+          maybeRR <- appFn' (req, PendingWebRequest respond)+          case maybeRR of+            Just rr -> return rr+            Nothing -> respond $+              Wai.responseLBS (HTTP.status500) [] "Internal server error"+          +      Warp.run port $ \req reply -> doRequestIO req reply+      return $ s1 $> s0++runRespondWebRequest :: forall rInitial r f.+  ((Final IO) `Member` r, Functor f) =>+  PendingWebRequest -> Wai.Response ->+  Sem (WithTactics WebServer f (Sem rInitial) r) (f Wai.ResponseReceived)+runRespondWebRequest (PendingWebRequest respond) resp = do+  s0 <- getInitialStateT+  withStrategicToFinal $ do+    s1 <- getInitialStateS+    return $ do+      rr <- respond (resp)+      return $ s1 $> (s0 $> rr)++runGetBody :: forall rInitial r f.+  ((Final IO) `Member` r, Functor f) =>+  Int -> Wai.Request ->+  Sem (WithTactics WebServer f (Sem rInitial) r) (f (Maybe BS.ByteString))+runGetBody maxLen req = do+  body <- embedFinal $ Wai.lazyRequestBody req+  let strictBody = LBS.toStrict $ LBS.take (fromIntegral $ maxLen + 1) body+  if BS.length strictBody > maxLen+    then pureT Nothing+    else pureT (Just strictBody)++runWebServerFinal :: ((Final IO) `Member` r) =>+                     Sem (WebServer ': r) a -> Sem r a+runWebServerFinal =+  interpretH (\v -> case v of+                 StartWebServer port app -> runStartWebServer port app+                 RespondWebRequest reqId response -> runRespondWebRequest reqId response+                 GetBody maxLen req -> runGetBody maxLen req+             )
+ test/Spec.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Polysemy+import Polysemy.Final+import Polysemy.WebServer+import qualified Data.ByteString.Lazy as LBS+import qualified Network.Wai as Wai+import qualified Network.Wai.Handler.Warp as Warp+import qualified Network.HTTP.Types.Status as HTTP+import Network.HTTP.Simple+import Polysemy.Async+import Test.Hspec++testServerApp :: WebServer `Member` r =>+  Wai.Request -> PendingWebRequest -> Sem r Wai.ResponseReceived+testServerApp _req pendingReq = respondWebRequest pendingReq (+  Wai.responseLBS (HTTP.status200) [] "Success")++main :: IO ()+main = runFinal . runWebServerFinal . asyncToIOFinal $ do+  -- With the WebServer effect, start a new server...+  async $ startWebServer 8123 testServerApp++  embedFinal . hspec $ do+    describe "Web Server" $ do+      it "should respond to requests" $ do+        -- Use a client library to test...+        req <- parseRequest "http://localhost:8123/"+        resp <- httpLBS req+        getResponseStatusCode resp `shouldBe` 200+        getResponseBody resp `shouldBe` "Success"