packages feed

srv-0.1.0.0: app/Main.hs

{-# 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)