module SocketsAndPipes.Serve ( {- * Serve -} serve, {- * Example -} {- $example -} {- * Options -} ServeOptions, port {- * Alternatives -} {- $alternatives -} ) where import SocketsAndPipes.Serve.Exceptions ( displayBindFailed ) import SocketsAndPipes.Serve.Sockets ( Socket, PortNumber, PassiveSocket, passiveSocketAddress, peerSocket ) import SocketsAndPipes.Serve.Setup ( withSocketOnPort ) import SocketsAndPipes.Serve.Loop ( run ) import Control.Exception.Safe ( catch ) import qualified Data.Text.Lazy.IO as LT import qualified Data.Text.Lazy.Builder as TB import qualified System.IO as IO import Prelude hiding (print) -- | The first argument to 'serve'. data ServeOptions = ServeOnPort PortNumber {- This type is abstract so that we might add more options in the future without disturbing the users of this library. -} port :: PortNumber -- ^ The port number that your server will listen on -> ServeOptions port = ServeOnPort serve :: ServeOptions -> (Socket -> IO ()) {- ^ What to do each time a new client connects to your server. These actions run concurrently in separate threads. -} -> IO () {- ^ Perpetually listens for incoming connections and runs the @(Socket -> IO ())@ function each time a new client opens a connection. -} serve (ServeOnPort p) f = go `catch` ifBindFailed where go = withSocketOnPort p $ \s -> do print =<< displayBindSuccess s run (f . peerSocket) s ifBindFailed = print . displayBindFailed print :: TB.Builder -> IO () print = LT.hPutStrLn IO.stderr . TB.toLazyText displayBindSuccess :: PassiveSocket -> IO TB.Builder displayBindSuccess s = passiveSocketAddress s >>= \a -> return $ TB.fromString "The server is listening on address " <> TB.fromString (show a) {- $example Suppose you have a function that reads an HTTP request from a socket and then writes an HTTP response back to the socket. @handleHttpRequest :: 'Socket' -> IO ()@ Then you might start a server by running the following in GHCi: @ λ> import "SocketsAndPipes.Serve" λ> 'serve' ('port' 8000) handleHttpRequest @ And while the server is still running, test it on the command line like so: > $ curl http://localhost:8000 -} {- $alternatives The 'serve' function here is somewhat narrowly tailored to fit our purposes in /Sockets and Pipes/. Some other packages have more expansive offerings: * * -}