iterio-server-0.2: Data/IterIO/Server/TCPServer.hs
-- |Generic building blocks for creating TCP Servers based on 'IterIO'
module Data.IterIO.Server.TCPServer (
TCPServer(..),
runTCPServer,
defaultServerAcceptor,
minimalTCPServer,
simpleHttpServer,
echoServer
) where
import Control.Concurrent.MonadIO
import Control.Monad
import qualified Data.ByteString.Lazy as L
import qualified Network.Socket as Net
import System.IO
import Data.IterIO
import Data.IterIO.Http
import Data.ListLike.IO
-- |Sets up a TCP socket to listen on the given port.
sockListenTCP :: Net.PortNumber -> IO Net.Socket
sockListenTCP pn = do
sock <- Net.socket Net.AF_INET Net.Stream Net.defaultProtocol
Net.setSocketOption sock Net.ReuseAddr 1
Net.bindSocket sock (Net.SockAddrInet pn Net.iNADDR_ANY)
Net.listen sock Net.maxListenQueue
return sock
-- |'TCPServer' holds all the information necessary to run
-- bind to a sock and respond to TCP requests from the network.
data TCPServer inp m = TCPServer {
-- |The TCP port the server will listen for incomming connections on.
serverPort :: Net.PortNumber
-- |This 'Inum' implements the actual functionality of the server. The input
-- and output of the 'Inum' correspond to the input and output of the socket.
, serverHandler :: Inum inp inp m ()
-- |A function to transform an accept incomming connection into an iter and onum.
-- Most servers should just use 'defaultSocketAcceptor' but this can be used for
-- special cases, e.g. accepting SSL connections with 'Data.IterIO.SSL.iterSSL'.
, serverAcceptor :: Net.Socket -> m (Iter inp m (), Onum inp m ())
-- |Must execute the monadic result. Servers operating in the 'IO' Monad can
-- use 'id'.
, serverResultHandler :: m () -> IO ()
}
instance Show (TCPServer inp m) where
show s = "TCPServer { serverPort: " ++ (show $ serverPort s) ++ " }"
-- |For convenience, a TCPServer in the 'IO' Monad with null defaults:
--
-- * Port 0 (next availabel port)
--
-- * Handler set to 'inumNop'
--
-- * Acceptor set to 'defaultServerAcceptor'
--
-- * Request handler set to 'id' (noop)
--
minimalTCPServer :: (ListLikeIO inp e, ChunkData inp) => TCPServer inp IO
minimalTCPServer = TCPServer 0 inumNop defaultServerAcceptor id
-- |This acceptor creates an 'Iter' and 'Onum' using 'handleI' and
-- 'enumHandle' respectively.
defaultServerAcceptor :: (ListLikeIO inp e,
ChunkData inp, MonadIO m)
=> Net.Socket -> m (Iter inp m (), Onum inp m a)
defaultServerAcceptor sock = liftIO $ do
h <- Net.socketToHandle sock ReadWriteMode
hSetBuffering h NoBuffering
return (handleI h, enumHandle h)
-- |Runs a 'TCPServer' in a loop.
runTCPServer :: (ListLikeIO inp e,
ChunkData inp, Monad m)
=> TCPServer inp m
-> IO ()
runTCPServer server = do
sock <- sockListenTCP $ serverPort server
let handler = serverResultHandler server
forever $ do
(s, _) <- Net.accept sock
_ <- forkIO $ handler $ do
(iter, enum) <- (serverAcceptor server) s
enum |$ serverHandler server .| iter
return ()
-- |Creates a simple HTTP server from an 'HTTPRequestHandler'.
simpleHttpServer :: Net.PortNumber
-> HttpRequestHandler IO ()
-> TCPServer L.ByteString IO
simpleHttpServer port reqHandler = minimalTCPServer { serverPort = port, serverHandler = httpAppHandler }
where httpAppHandler = inumHttpServer $ ioHttpServer reqHandler
-- |Creates a 'TCPServer' that echoes each line from the client until EOF.
echoServer :: Net.PortNumber -> TCPServer String IO
echoServer port = minimalTCPServer { serverPort = port, serverHandler = echoAppHandler }
where echoAppHandler = mkInumM $ forever $ do
input <- safeLineI
case input of
Just output -> irun $ enumPure $ output ++ "\r\n"
Nothing -> irun $ enumPure []