module RFC.Wai
( runApplication
, defaultMiddleware
, module Network.Wai
) where
import Control.Logger.Simple
import Control.Monad.State.Lazy hiding (fail, mapM_)
import Network
import Network.HTTP.Types.Header
import Network.HTTP.Types.Method
import Network.Socket
import Network.Wai
import Network.Wai.Cli hiding (socket)
import Network.Wai.Handler.Warp
import Network.Wai.Middleware.AcceptOverride
import Network.Wai.Middleware.Approot (envFallback)
import Network.Wai.Middleware.Autohead
import Network.Wai.Middleware.Cors
import Network.Wai.Middleware.Gzip
import Network.Wai.Middleware.Jsonp
import Network.Wai.Middleware.MethodOverridePost
import Network.Wai.Middleware.RequestLogger (logStdout,
logStdoutDev)
import RFC.Env (FromEnv (..),
decodeEnv, envMaybe,
isDevelopment,
showEnv, (.!=))
import RFC.Prelude
import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory)
runApplication :: Application -> IO ()
runApplication app = withSocketsDo $ do
showEnv
middlewares <- defaultMiddleware
warpSettingsResult <- decodeEnv
case warpSettingsResult of
Left err -> fail err
Right warpSettings -> do
reuseSocket <- bindSocketReusePort $ getPort warpSettings
runGraceful
ServeNormally
(flip runSettingsSocket $ reuseSocket)
warpSettings
(middlewares app)
bindSocketReusePort :: Port -> IO Socket
bindSocketReusePort p =
bracketOnError (socket AF_INET Stream defaultProtocol) close $ \sock -> do
mapM_ (uncurry $ setSocketOption sock) $ filter (isSupportedSocketOption . fst)
[ (NoDelay , 1)
, (KeepAlive, 1)
, (ReuseAddr, 1)
, (ReusePort, 1)
]
bind sock $ SockAddrInet (fromIntegral p) iNADDR_ANY
listen sock (max 2048 maxListenQueue)
return sock
instance FromEnv Settings where
fromEnv = do
portNumber <- envMaybe "PORT" .!= 3000
return
$ setPort portNumber
$ setOnExceptionResponse
(if isDevelopment then
exceptionResponseForDebug
else
defaultOnExceptionResponse
)
$ setServerName emptyUTF8
$ defaultSettings
defaultMiddleware :: IO Middleware
defaultMiddleware = do
approot <- envFallback
tmpDir <- getTmpDir
gzipDir <- mkGzipDir tmpDir
return $
methodOverridePost .
acceptOverride .
(cors $ const $ Just corsConfig).
autohead .
jsonp .
approot .
gzip (gzipConfig gzipDir) .
(if isDev then logStdoutDev else logStdout)
where
isDev = isDevelopment
handleError msg e = do
logError . cs $ "Error while: " ++ msg
logError . cs . show $ e
throwIO e
getTmpDir = catchAnyDeep getCanonicalTemporaryDirectory (handleError "getting temp dir")
mkGzipDir tmpDir = catchAnyDeep (createTempDirectory tmpDir "wai-gzip-middleware") (handleError "making temp dir")
corsConfig = simpleCorsResourcePolicy
{ corsRequireOrigin = False
, corsVaryOrigin = False
, corsMaxAge = Nothing
, corsRequestHeaders = hContentType : simpleHeaders
, corsMethods =
[ methodGet
, methodPost
, methodHead
, methodDelete
, methodPatch
, methodOptions
, methodPut
]
}
gzipConfig gzipDir = def
{ gzipFiles = GzipCacheFolder gzipDir
, gzipCheckMime = const True
}