summaryrefslogtreecommitdiff
path: root/src/Network/StreamServer.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/StreamServer.hs')
-rw-r--r--src/Network/StreamServer.hs143
1 files changed, 143 insertions, 0 deletions
diff --git a/src/Network/StreamServer.hs b/src/Network/StreamServer.hs
new file mode 100644
index 00000000..a6cead0e
--- /dev/null
+++ b/src/Network/StreamServer.hs
@@ -0,0 +1,143 @@
1-- | This module implements a bare-bones TCP or Unix socket server.
2{-# LANGUAGE TypeFamilies #-}
3{-# LANGUAGE TypeOperators #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE RankNTypes #-}
6module Network.StreamServer
7 ( streamServer
8 , ServerHandle
9 , ServerConfig(..)
10 , withSession
11 , quitListening
12 , dummyServerHandle
13 ) where
14
15import Data.Monoid
16import Network.Socket as Socket
17import Data.ByteString.Char8
18 ( hGetNonBlocking
19 )
20import qualified Data.ByteString.Char8 as S
21 ( hPutStrLn
22 )
23import System.Directory (removeFile)
24import System.IO
25 ( IOMode(..)
26 , hSetBuffering
27 , BufferMode(..)
28 , hWaitForInput
29 , hClose
30 , hIsEOF
31 , hPutStrLn
32 , stderr
33 , hFlush
34 )
35import Control.Monad
36import Control.Monad.Fix (fix)
37import Control.Concurrent (forkIO, threadDelay, ThreadId, mkWeakThreadId)
38import Control.Exception (catch,handle,try,finally)
39import System.IO.Error (tryIOError)
40import System.Mem.Weak
41import System.IO.Error
42
43-- import Data.Conduit
44import Control.Monad.IO.Class (MonadIO (liftIO))
45import qualified Data.ByteString as S (ByteString)
46import System.IO (Handle)
47import Control.Concurrent.MVar (newMVar)
48
49import Network.SocketLike
50
51data ServerHandle = ServerHandle Socket (Weak ThreadId)
52
53
54-- | Create a useless do-nothing 'ServerHandle'.
55dummyServerHandle :: IO ServerHandle
56dummyServerHandle = do
57 mvar <- newMVar Closed
58 let sock = MkSocket 0 AF_UNSPEC NoSocketType 0 mvar
59 thread <- mkWeakThreadId <=< forkIO $ return ()
60 return (ServerHandle sock thread)
61
62removeSocketFile :: SockAddr -> IO ()
63removeSocketFile (SockAddrUnix fname) = removeFile fname
64removeSocketFile _ = return ()
65
66-- | Terminate the server accept-loop. Call this to shut down the server.
67quitListening :: ServerHandle -> IO ()
68quitListening (ServerHandle socket _) =
69 finally (Socket.getSocketName socket >>= removeSocketFile)
70 (Socket.close socket)
71
72
73-- | It's 'bshow' instead of 'show' to enable swapping in a 'ByteString'
74-- variation. (This is not exported.)
75bshow :: Show a => a -> String
76bshow e = show e
77
78-- | Send a string to stderr. Not exported. Default 'serverWarn' when
79-- 'withSession' is used to configure the server.
80warnStderr :: String -> IO ()
81warnStderr str = hPutStrLn stderr str >> hFlush stderr
82
83data ServerConfig = ServerConfig
84 { serverWarn :: String -> IO ()
85 -- ^ Action to report warnings and errors.
86 , serverSession :: RestrictedSocket -> Int -> Handle -> IO ()
87 -- ^ Action to handle interaction with a client
88 }
89
90-- | Initialize a 'ServerConfig' using the provided session handler.
91withSession :: (RestrictedSocket -> Int -> Handle -> IO ()) -> ServerConfig
92withSession session = ServerConfig warnStderr session
93
94-- | Launch a thread to listen at the given bind address and dispatch
95-- to session handler threads on every incomming connection. Supports
96-- IPv4 and IPv6, TCP and unix sockets.
97--
98-- The returned handle can be used with 'quitListening' to terminate the
99-- thread and prevent any new sessions from starting. Currently active
100-- session threads will not be terminated or signaled in any way.
101streamServer :: ServerConfig -> SockAddr -> IO ServerHandle
102streamServer cfg addr = do
103 let warn = serverWarn cfg
104 family = case addr of
105 SockAddrInet {} -> AF_INET
106 SockAddrInet6 {} -> AF_INET6
107 SockAddrUnix {} -> AF_UNIX
108 sock <- socket family Stream 0
109 setSocketOption sock ReuseAddr 1
110 fix $ \loop ->
111 tryIOError (removeSocketFile addr) >> bind sock addr
112 `catchIOError` \e -> do warn $ "bind-error: " <> bshow addr <> " " <> bshow e
113 threadDelay 5000000
114 loop
115 listen sock maxListenQueue
116 thread <- mkWeakThreadId <=< forkIO $ acceptLoop cfg sock 0
117 return (ServerHandle sock thread)
118
119-- | Not exported. This, combined with 'acceptException' form a mutually recursive
120-- loop that handles incomming connections. To quit the loop, the socket must be
121-- closed by 'quitListening'.
122acceptLoop :: ServerConfig -> Socket -> Int -> IO ()
123acceptLoop cfg sock n = handle (acceptException cfg n sock) $ do
124 con <- accept sock
125 let conkey = n + 1
126 h <- socketToHandle (fst con) ReadWriteMode
127 forkIO $ serverSession cfg (restrictHandleSocket h (fst con)) conkey h
128 acceptLoop cfg sock (n + 1)
129
130acceptException :: ServerConfig -> Int -> Socket -> IOError -> IO ()
131acceptException cfg n sock ioerror = do
132 Socket.close sock
133 case show (ioeGetErrorType ioerror) of
134 "resource exhausted" -> do -- try again
135 serverWarn cfg $ ("acceptLoop: resource exhasted")
136 threadDelay 500000
137 acceptLoop cfg sock (n + 1)
138 "invalid argument" -> do -- quit on closed socket
139 return ()
140 message -> do -- unexpected exception
141 serverWarn cfg $ ("acceptLoop: "<>bshow message)
142 return ()
143