webapp-0.4.0: Web/App/HTTP.hs
{-# LANGUAGE OverloadedStrings, RankNTypes #-}
{-|
Module : Web.App.HTTP
Copyright : (c) Nathaniel Symer, 2015
License : MIT
Maintainer : nate@symer.io
Stability : experimental
Portability : POSIX
-}
module Web.App.HTTP
(
runServer
)
where
import Network.Wai (Application)
import Network.Wai.Handler.Warp (defaultSettings, runSettingsSocket, setPort)
import Network.Wai.Handler.WarpTLS (tlsSettings, runTLSSocket, TLSSettings(..), OnInsecure(..))
import Network.Socket (close, listen, bind, socket, Socket, SocketOption(..), AddrInfoFlag(..), SocketType(..), Family(..), isListening, setSocketOption, maxListenQueue, withSocketsDo, getAddrInfo, defaultHints, addrFamily, addrFlags, addrAddress, addrProtocol, addrSocketType)
import Control.Exception (bracket, bracketOnError)
import System.Exit
import System.Posix
import Data.List (sortOn)
import Data.Maybe (fromJust)
import Data.Bool (bool)
-- | Serve your application.
runServer :: Maybe (FilePath, FilePath) -- ^ Certificate pair to use (cert, key)
-> Int -- ^ Port to bind to
-> IO () -- ^ IO to be performed immediately after binding.
-> IO () -- ^ IO to be performed on shutdown.
-> Application -- ^ WAI 'Application'.
-> IO ()
runServer cp port pre teardown app = withSocketsDo $ bracket (bindTCP port) close logic
where
logic sock = pre >> do
installHandler sigTERM handler Nothing
installHandler sigINT handler Nothing
run cp sock
where handler = Catch $ close sock >> teardown >> exitImmediately ExitSuccess
run (Just (c, k)) sock = runTLSSocket tset set sock app
where tset = (tlsSettings c k) { onInsecure = AllowInsecure }
run _ sock = runSettingsSocket set sock app
set = setPort port defaultSettings
-- | Find a Socket capable of transmitting/receiving TCP data.
-- Has preference for IPv4 hosts over IPv6 hosts.
bindTCP :: Int -> IO Socket
bindTCP p = tryAddrs . sortOn sortf =<< getAddrInfo (Just hints) Nothing (Just $ show p)
where hints = defaultHints {
addrFlags = [AI_PASSIVE, AI_NUMERICSERV, AI_NUMERICHOST],
addrSocketType = Stream
} -- TODO: add port to these hints somehow
sortf = (==) AF_INET6 . addrFamily -- sortOn odd [True, False, True] == [False, True, True]
tryAddrs = fmap fromJust . findMLazy isListening . map theBody -- uses laziness
theBody addr = bracketOnError aquire close action
where aquire = socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
action sock = do
setSocketOption sock NoDelay 1
setSocketOption sock ReuseAddr 1
bind sock $ addrAddress addr
listen sock $ max 2048 maxListenQueue
return sock
-- | Performs monadic computations until it finds a result
-- for which p returns true. It returns this value.
findMLazy :: (Monad m) => (a -> m Bool) -> [m a] -> m (Maybe a)
findMLazy _ [] = return Nothing
findMLazy p (x:xs) = x >>= \x' -> p x' >>= bool (findMLazy p xs) (return $ Just x')