{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} module Main where import Control.Concurrent.Async (mapConcurrently_) import qualified Data.Yaml as Yaml import GHC.Generics (Generic) import Network.Wai (Application) import Network.Wai.Application.Static (defaultFileServerSettings, staticApp) import Network.Wai.Handler.Warp (defaultSettings, run, setPort) import Network.Wai.Handler.WarpTLS (runTLS, tlsSettings, TLSSettings) import System.Directory (doesFileExist) import DemoCertificate (demoTLSSettings) -- | a data type representing Http or Https warp handlers data WarpHandler = HTTP | HTTPS deriving (Show, Eq, Generic, Yaml.FromJSON, Yaml.ToJSON) -- | a tuple (WarpHandler, IP port) type Handler = (WarpHandler, Int) -- | configuration settings that specify the behaviour of the server data SrvConfig = SrvConfig { handlers :: ![Handler], pathToCert :: !(Maybe FilePath), pathToKey :: !(Maybe FilePath), documentRoot :: !FilePath } deriving (Show, Eq, Generic, Yaml.FromJSON, Yaml.ToJSON) -- | create a configuration with default settings defaultConfig :: SrvConfig defaultConfig = SrvConfig { handlers = [(HTTP, 8080), (HTTPS, 8443)], pathToCert = Nothing, -- Just "certificate.pem", pathToKey = Nothing, -- Just "key.pem", documentRoot = "." } -- | create a static WAI Application from a configuration staticAppFrom :: SrvConfig -> Application staticAppFrom config = staticApp $ defaultFileServerSettings (documentRoot config) -- create an HTTP handler action based on config settings httpHandler :: Handler -> SrvConfig -> IO () httpHandler (_, port) config = do putStrLn $ "Starting HTTP server on port " <> show port run port $ staticAppFrom config -- create an HTTPS handler action based on config settings httpsHandler :: Handler -> SrvConfig -> IO () httpsHandler (_, port) config = do putStrLn $ "Starting HTTPS server on port " <> show port runTLS (getTlsSettings (pathToCert config) (pathToKey config)) (setPort port defaultSettings) (staticAppFrom config) -- | create a TLS settings object from file path settings. If no file paths are -- provided, use the demo in-memory certificate getTlsSettings :: Maybe FilePath -> Maybe FilePath -> TLSSettings getTlsSettings (Just cert) (Just key) = tlsSettings cert key getTlsSettings _ _ = demoTLSSettings -- | derive all handler IO actions from the configuration getAllHandlers :: SrvConfig -> [IO ()] getAllHandlers config = map (handlerToAction config) (handlers config) where handlerToAction :: SrvConfig -> Handler -> IO () handlerToAction cfg handler = case handler of (HTTP, _) -> httpHandler handler cfg (HTTPS, _) -> httpsHandler handler cfg configFile :: FilePath configFile = "srv.yaml" -- | start all handlers configured in config.yaml main :: IO () main = do putStrLn "starting up srv..." configIsPresent <- doesFileExist configFile config <- if configIsPresent then do putStrLn $ "reading " ++ configFile ++ "..." Yaml.decodeFileThrow configFile else do putStrLn $ configFile ++ " not found, generating file with default config" Yaml.encodeFile configFile defaultConfig return defaultConfig -- run all handlers defined in config mapConcurrently_ id (getAllHandlers config)