module IRE.Server ( server ) where import Control.Exception.Base (bracket, catch, throwIO) import Data.Bits ((.|.)) import Data.Yaml.Include (decodeFileEither) import Network.Socket (Family(AF_INET, AF_UNIX), SockAddr(..), Socket, SocketOption(ReuseAddr), SocketType(Stream), bind, close, getSocketName, inet_addr, listen, maxListenQueue, setSocketOption, socket) import Network.Wai.Handler.Warp (Port, defaultSettings, runSettingsSocket) import System.Exit (exitFailure) import System.IO (hPutStrLn, stderr) import System.IO.Error (isDoesNotExistError) import System.Posix.Files (fileExist, groupReadMode, groupWriteMode, ownerReadMode, ownerWriteMode, removeLink, setFileMode, socketMode) import IRE.Application (app) import IRE.Config (ConfigFile(..), defaultConfig) import qualified IRE.Logging as Log import IRE.YOLO (newDetector) type Listen = Either Port FilePath server :: FilePath -> FilePath -> IO () server rootDir configFile = do cf <- readConfigFile configFile net <- newDetector (cfYOLO cf) Log.start Log.Debug let socketSpec = case cfSocket cf of Just p -> Right p Nothing -> Left $ cfPort cf bracket (createSocket socketSpec) closeSocket (\sock -> do listen sock maxListenQueue runSettingsSocket defaultSettings sock =<< app rootDir net) readConfigFile :: FilePath -> IO ConfigFile readConfigFile f = do e <- fileExist f if e then do r <- decodeFileEither f case r of Left ex -> do hPutStrLn stderr $ "FATAL: " ++ f ++ ": " ++ show ex exitFailure Right cf -> return cf else return defaultConfig createSocket :: Listen -> IO Socket createSocket (Right path) = do removeIfExists path sock <- socket AF_UNIX Stream 0 bind sock $ SockAddrUnix path setFileMode path $ socketMode .|. ownerWriteMode .|. ownerReadMode .|. groupWriteMode .|. groupReadMode Log.info $ "Listening on UNIX socket `" ++ path ++ "'" return sock createSocket (Left port) = do sock <- socket AF_INET Stream 0 setSocketOption sock ReuseAddr 1 addr <- inet_addr "127.0.0.1" bind sock $ SockAddrInet (fromIntegral port) addr Log.info $ "Listening on localhost:" ++ show port return sock closeSocket :: Socket -> IO () closeSocket sock = do name <- getSocketName sock close sock case name of SockAddrUnix path -> removeIfExists path _ -> return () removeIfExists :: FilePath -> IO () removeIfExists fileName = removeLink fileName `catch` handleExists where handleExists e | isDoesNotExistError e = return () | otherwise = throwIO e