@@ -1,20 +1,17 @@
1−{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving, CPP, ExistentialQuantification, StandaloneDeriving, GADTs, RecordWildCards #-}
1+{-# LANGUAGE DerivingVia, DeriveGeneric, RankNTypes, ScopedTypeVariables, MultiParamTypeClasses, OverloadedStrings, GeneralizedNewtypeDeriving #-}
2−{-# OPTIONS_GHC -fno-warn-orphans #-}
3{- HLINT ignore "Use lambda-case" -}
2{- HLINT ignore "Use lambda-case" -}
4module Network.RPC.Curryer.Server where
3module Network.RPC.Curryer.Server where
5import qualified Streamly.Prelude as S
5import qualified Streamly.Prelude as S
6import Streamly.Network.Socket
6import Streamly.Network.Socket
7−import Streamly.Internal.Network.Socket (handleWithM)
7+import Streamly.Internal.Network.Socket (handleWithM, writeChunk)
8import Network.Socket as Socket
8import Network.Socket as Socket
9−import Network.Socket.ByteString as Socket
10−import Streamly.Internal.Data.Parser as P hiding (concatMap)
9+import Streamly.Internal.Data.Parser.ParserD as PD
11import Codec.Winery
10import Codec.Winery
12import Codec.Winery.Internal (varInt, decodeVarInt, getBytes)
11import Codec.Winery.Internal (varInt, decodeVarInt, getBytes)
13import Codec.Winery.Class (mkExtractor)
12import Codec.Winery.Class (mkExtractor)
14import GHC.Generics
13import GHC.Generics
15−import GHC.Fingerprint
17−import Control.Concurrent.MVar (MVar, newMVar, withMVar)
14+import Control.Concurrent.MVar (MVar)
18import Control.Exception
15import Control.Exception
19import Data.Function ((&))
16import Data.Function ((&))
20import Data.Word
17import Data.Word
@@ -23,103 +20,53 @@ import qualified Data.ByteString.Lazy as BSL
23import qualified Data.ByteString.FastBuilder as BB
20import qualified Data.ByteString.FastBuilder as BB
24import Streamly.Data.Fold as FL
21import Streamly.Data.Fold as FL
25import qualified Streamly.Internal.Data.Stream.IsStream as S
22import qualified Streamly.Internal.Data.Stream.IsStream as S
26import qualified Data.Binary as B
25import qualified Data.Binary as B
27import qualified Data.UUID as UUIDBase
26import qualified Data.UUID as UUIDBase
28import qualified Data.UUID.V4 as UUIDBase
27import qualified Data.UUID.V4 as UUIDBase
29−import Control.Monad
28+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as SA
32import qualified Network.RPC.Curryer.StreamlyAdditions as SA
29import qualified Network.RPC.Curryer.StreamlyAdditions as SA
33−--import Control.Monad
30+import Control.Monad
34import Data.Hashable
31import Data.Hashable
36−import qualified Network.ByteOrder as BO
39-- for toArrayS conversion
33-- for toArrayS conversion
40import qualified Data.ByteString.Internal as BSI
34import qualified Data.ByteString.Internal as BSI
41−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as Arr
35+import Foreign.ForeignPtr (plusForeignPtr)
42import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
36import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
43−import GHC.Ptr (minusPtr)
37+import GHC.Ptr (plusPtr)
45−-- define CURRYER_SHOW_BYTES 1
46−#if CURRYER_SHOW_BYTES == 1
47import Debug.Trace
39import Debug.Trace
50−traceBytes :: Applicative f => String -> BS.ByteString -> f ()
51−#if CURRYER_SHOW_BYTES == 1
52−traceBytes msg bs = traceShowM (msg, BS.length bs, bs)
54−traceBytes _ _ = pure ()
57−data Locking a = Locking (MVar ()) a
59−newLock :: a -> IO (Locking a)
62− pure (Locking lock x)
64−withLock :: Locking a -> (a -> IO b) -> IO b
65−withLock (Locking mvar v) m =
66− withMVar mvar $ \_ -> m v
68−lockless :: Locking a -> a
69−lockless (Locking _ a) = a
73−type BinaryMessage = BS.ByteString
41+data ClientHelloMessage = ClientHelloMessage Int Int
43+ deriving Serialise via WineryVariant ClientHelloMessage
45+data ServerHelloMessage = ServerHelloMessage { serverServices :: [String],
49+ deriving Serialise via WineryRecord ServerHelloMessage
51+--request-response token
53+--server-to-client and client-to-server async request (no response requested)
55+data Message a = Response UUID a
56+ | AsyncRequest UUID a
57+ | ResponseExpectedRequest UUID a
59+ | ExceptionResponse String
60+ deriving (Generic, Show)
61+ deriving Serialise via WineryVariant (Message a)
63+-- | The response type
64+data HandlerResponse a = NoResponse
68+ deriving Serialise via WineryVariant (HandlerResponse a)
75−--includes the fingerprint of the incoming data type (held in the BinaryMessage) to determine how to dispatch the message.
76−--add another envelope type for unencoded binary messages for any easy optimization for in-process communication
77−data Envelope = Envelope {
78− envFingerprint :: !Fingerprint,
79− envMessageType :: !MessageType,
81− envPayload :: !BinaryMessage
83− deriving (Generic, Show)
85−type TimeoutMicroseconds = Int
87−deriving instance Generic Fingerprint
88−deriving via WineryVariant Fingerprint instance Serialise Fingerprint
90−-- | Internal type used to mark envelope types.
91−data MessageType = RequestMessage TimeoutMicroseconds
93− | TimeoutResponseMessage
94− | ExceptionResponseMessage
95− deriving (Generic, Show)
96− deriving Serialise via WineryVariant MessageType
98−-- | A list of `RequestHandler`s.
99−type RequestHandlers serverState = [RequestHandler serverState]
101−-- | Data types for server-side request handlers, in synchronous (client waits for return value) and asynchronous (client does not wait for return value) forms.
102−data RequestHandler serverState where
103− -- | create a request handler with a response
104− RequestHandler :: forall a b serverState. (Serialise a, Serialise b) => (ConnectionState serverState -> a -> IO b) -> RequestHandler serverState
105− -- | create an asynchronous request handler where the client does not expect nor await a response
106− AsyncRequestHandler :: forall a serverState. Serialise a => (ConnectionState serverState -> a -> IO ()) -> RequestHandler serverState
108−-- | Server state sent in via `serve` and passed to `RequestHandler`s.
109−data ConnectionState a = ConnectionState {
110− connectionServerState :: a,
111− connectionSocket :: Locking Socket
114−-- | Used by server-side request handlers to send additional messages to the client. This is useful for sending asynchronous responses to the client outside of the normal request-response flow. The locking socket can be found in the ConnectionState when a request handler is called.
115−sendMessage :: Serialise a => Locking Socket -> a -> IO ()
116−sendMessage lockSock msg = do
117− requestID <- UUID <$> UUIDBase.nextRandom
119− Envelope (fingerprint msg) (RequestMessage timeout') requestID (serialise msg)
121− sendEnvelope env lockSock
123--avoid orphan instance
70--avoid orphan instance
124newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
71newtype UUID = UUID { _unUUID :: UUIDBase.UUID }
125 deriving (Show, Eq, B.Binary, Hashable)
72 deriving (Show, Eq, B.Binary, Hashable)
@@ -138,18 +85,12 @@ instance Serialise UUID where
138 x -> error $ "invalid schema element " <> show x
85 x -> error $ "invalid schema element " <> show x
139 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
86 decodeCurrent = B.decode . BSL.fromStrict <$> (decodeVarInt >>= getBytes)
141−-- | Errors from remote calls.
88+
142data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
89data ConnectionError = CodecError String -- show of WineryException from exception initiator which cannot otherwise be transmitted over a line due to dependencies on TypeReps
143 | TimeoutError
90 | TimeoutError
144− | ExceptionError String
145 deriving (Generic, Show, Eq)
91 deriving (Generic, Show, Eq)
146 deriving Serialise via WineryVariant ConnectionError
92 deriving Serialise via WineryVariant ConnectionError
148−data TimeoutException = TimeoutException
151−instance Exception TimeoutException
153type HostAddr = (Word8, Word8, Word8, Word8)
94type HostAddr = (Word8, Word8, Word8, Word8)
155allHostAddrs,localHostAddr :: HostAddr
96allHostAddrs,localHostAddr :: HostAddr
@@ -157,186 +98,111 @@ allHostAddrs = (0,0,0,0)
157localHostAddr = (127,0,0,1)
98localHostAddr = (127,0,0,1)
159-- Each message is length-prefixed by a 32-bit unsigned length.
100-- Each message is length-prefixed by a 32-bit unsigned length.
160−envelopeP :: Parser IO Word8 Envelope
101+messageBoundaryP :: Parser IO Word8 BS.ByteString
161−envelopeP = do
102+messageBoundaryP = do
162− let msgTypeP = (P.satisfy (== 0) *>
103+ --traceShowM "message!"
163− (RequestMessage . fromIntegral <$> word32P)) `P.alt`
104+ w4x8 <- PD.take 4 FL.toList
164− (P.satisfy (== 1) $> ResponseMessage) `P.alt`
105+ --traceShowM ("w4x8",w4x8)
165− (P.satisfy (== 2) $> TimeoutResponseMessage) `P.alt`
106+ let c = fromIntegral (fromOctets w4x8)
166− (P.satisfy (== 3) $> ExceptionResponseMessage)
107+ --traceShowM ("c", c)
167− lenPrefixedByteStringP = do
108+ vals <- PD.take c FL.toList
168− c <- fromIntegral <$> word32P
109+ let bytes = BS.pack vals
169− --streamly can't handle takeEQ 0, so add special handling
110+ traceShowM ("vals", BS.length bytes, bytes)
170− if c == 0 then
111+ pure bytes
173− fromArray <$> P.takeEQ c (Arr.writeN c)
174− Envelope <$> fingerprintP <*> msgTypeP <*> uuidP <*> lenPrefixedByteStringP
176−encodeEnvelope :: Envelope -> BS.ByteString
177−encodeEnvelope (Envelope (Fingerprint fp1 fp2) msgType msgId bs) =
178− fingerprintBs <> msgTypeBs <> msgIdBs <> lenPrefixedBs
180− fingerprintBs = BO.bytestring64 fp1 <> BO.bytestring64 fp2
181− msgTypeBs = case msgType of
182− RequestMessage timeoutms -> BS.singleton 0 <> BO.bytestring32 (fromIntegral timeoutms)
183− ResponseMessage -> BS.singleton 1
184− TimeoutResponseMessage -> BS.singleton 2
185− ExceptionResponseMessage -> BS.singleton 3
187− case UUIDBase.toWords (_unUUID msgId) of
188− (u1, u2, u3, u4) -> foldr ((<>) . BO.bytestring32) BS.empty [u1, u2, u3, u4]
189− msgLen = fromIntegral (BS.length bs)
190− lenPrefixedBs = BO.bytestring32 msgLen <> bs
192−fingerprintP :: Parser IO Word8 Fingerprint
194− Fingerprint <$> word64P <*> word64P
196−word64P :: Parser IO Word8 Word64
200− pure (BO.word64 (BS.pack b))
202−--parse a 32-bit integer from network byte order
203−word32P :: Parser IO Word8 Word32
206− w4x8 <- P.takeEQ 4 s
207− pure (BO.word32 (BS.pack w4x8))
209−-- uuid is encode as 4 32-bit words because of its convenient 32-bit tuple encoding
210−uuidP :: Parser IO Word8 UUID
216− --pure (UUID (UUIDBase.fromWords u1 u2 u3 u4))-}
217− --(UUID . UUIDBase.fromWords) <$> word32P <*> word32P <*> word32P <*> word32P
218− UUID . UUIDBase.fromWords u1 u2 u3 <$> word32P
220type NewConnectionHandler msg = IO (Maybe msg)
113type NewConnectionHandler msg = IO (Maybe msg)
222−type NewMessageHandler req resp = req -> IO resp
115+type NewMessageHandler msg resp = msg -> IO (HandlerResponse resp)
224−-- | Listen for new connections and handle requests which are passed the server state 's'. The MVar SockAddr can be be optionally used to know when the server is ready for processing requests.
117+serve :: (Show msg, Serialise msg, Serialise resp) =>
225−serve ::
118+ NewConnectionHandler resp ->
226− RequestHandlers s->
119+ NewMessageHandler msg resp ->
228 HostAddr ->
120 HostAddr ->
229 PortNumber ->
121 PortNumber ->
230 Maybe (MVar SockAddr) ->
122 Maybe (MVar SockAddr) ->
232−serve userMsgHandlers serverState hostaddr port mSockLock = do
124+serve connhandler msghandler hostaddr port mSockLock = do
234− handleSock sock = do
125+ let handleSock sock = do
235− lockingSocket <- newLock sock
126+ putStrLn "handleSock"
236− drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers serverState)
127+ let messageHandler msg = do
237−
128+ putStrLn $ "GOT MSG " ++ show msg
130+ Response{} -> putStrLn "client sent response"
131+ ResponseExpectedRequest requestID val -> do
132+ putStrLn "ResponseExp"
133+ resp <- msghandler val
135+ HandlerResponse responseVal -> sendMessage (Response requestID responseVal) sock
136+ NoResponse -> error "attempt to return non-response to expected response message"
137+ HandlerException _ -> error "TODO HandlerException"
138+ AsyncRequest _ val -> do
140+ void $ msghandler val
141+ --no response necessaryxs
142+ ExceptionResponse{} -> putStrLn "client sent exception response"
143+ -- allow the server to send an async welcome message to the new client, if necessary
144+ mResp <- connhandler
148+ requestID <- UUID <$> UUIDBase.nextRandom
149+ sendMessage (AsyncRequest requestID resp) sock
150+ drainSocketMessages sock messageHandler
238− S.serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & S.parallely . S.mapM (handleWithM handleSock) & S.drain
151+ serially (S.unfold (SA.acceptOnAddrWith [(ReuseAddr,1)] mSockLock) (hostaddr, port)) & parallely . S.mapM (handleWithM handleSock) & S.drain
239 pure True
152 pure True
241−openEnvelope :: forall s. (Serialise s, Typeable s) => Envelope -> Maybe s
154+--add callback to allow for responses via socket
242−openEnvelope (Envelope eprint _ _ bytes) =
155+type MessageHandler a = Message a -> IO ()
243− if eprint == fingerprint (undefined :: s) then
244− case deserialise bytes of
245− Left err -> error (show err)
250−matchEnvelope :: forall a b s. (Serialise a, Serialise b, Typeable b) =>
157+type AsyncMessageHandler a = a -> IO ()
252− (ConnectionState s -> a -> IO b) ->
253− Maybe (ConnectionState s -> a -> IO b, a)
254−matchEnvelope envelope dispatchf =
255− case openEnvelope envelope :: Maybe a of
257− Just decoded -> Just (dispatchf, decoded)
259−-- | Called by `serve` to process incoming envelope requests. Never returns, so use `async` to spin it off on another thread.
159+drainSocketMessages :: Serialise msg => Socket -> MessageHandler msg -> IO ()
260−serverEnvelopeHandler ::
262− -> RequestHandlers s
266−serverEnvelopeHandler _ _ _ (Envelope _ TimeoutResponseMessage _ _) = pure ()
267−serverEnvelopeHandler _ _ _ (Envelope _ ExceptionResponseMessage _ _) = pure ()
268−serverEnvelopeHandler _ _ _ (Envelope _ ResponseMessage _ _) = pure ()
269−serverEnvelopeHandler sockLock msgHandlers serverState envelope@(Envelope _ (RequestMessage timeoutms) msgId _) = do
270− --find first matching handler
271− let runTimeout :: IO b -> IO (Maybe b)
273− if timeoutms == 0 then
274− (Just <$> m) `catch` timeoutExcHandler
276− (timeout (fromIntegral timeoutms) m) `catch` timeoutExcHandler
277− --allow server-side function to throw TimeoutError which is caught here and becomes TimeoutError value
278− timeoutExcHandler :: TimeoutException -> IO (Maybe b)
279− timeoutExcHandler _ = pure Nothing
281− sState = ConnectionState {
282− connectionServerState = serverState,
283− connectionSocket = sockLock
286− firstMatcher (RequestHandler msghandler) Nothing =
287− case matchEnvelope envelope msghandler of
288− Nothing -> pure Nothing
289− Just (dispatchf, decoded) -> do
290− --TODO add exception handling
291− mResponse <- runTimeout (dispatchf sState decoded)
292− let envelopeResponse =
295− Envelope (fingerprint response) ResponseMessage msgId (serialise response)
297− Envelope (fingerprint TimeoutError) TimeoutResponseMessage msgId BS.empty
298− sendEnvelope envelopeResponse sockLock
300− firstMatcher (AsyncRequestHandler msghandler) Nothing =
301− case matchEnvelope envelope msghandler of
302− Nothing -> pure Nothing
303− Just (dispatchf, decoded) -> do
304− _ <- dispatchf sState decoded
306− firstMatcher _ acc = pure acc
307− eExc <- try $ foldM_ (flip firstMatcher) Nothing msgHandlers :: IO (Either SomeException ())
310− let env = Envelope (fingerprint (show exc)) ExceptionResponseMessage msgId (serialise (show exc)) in
311− sendEnvelope env sockLock
315−type EnvelopeHandler = Envelope -> IO ()
317−drainSocketMessages :: Socket -> EnvelopeHandler -> IO ()
318−drainSocketMessages sock envelopeHandler = do
160+drainSocketMessages sock msgHandler = do
319 let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
161 let sockStream = S.unfold readWithBufferOf (1024 * 4, sock)
320− S.drain $ S.serially $ S.parseMany envelopeP sockStream & S.mapM envelopeHandler
162+ handler bs = do
321−
163+ --print ("drain handler", bs)
322−--send length-tagged bytestring, perhaps should be in network byte order?
164+ case deserialise bs of
323−sendEnvelope :: Envelope -> Locking Socket -> IO ()
165+ Left err ->
324−sendEnvelope envelope sockLock = do
166+ print err
325− let envelopebytes = encodeEnvelope envelope
167+ Right val -> do
326− fullbytes = envelopebytes
168+ --putStrLn "deserialized value for msgHandler"
327− --Socket.sendAll syscalls send() on a loop until all the bytes are sent, so we need socket locking here to account for serialized messages of size > PIPE_BUF
169+ msgHandler val --add response function
328− withLock sockLock $ \socket' ->
170+ S.drain $ S.parseManyD messageBoundaryP sockStream & S.mapM handler
329− Socket.sendAll socket' fullbytes
171+
330− --traceShowM ("sendEnvelope", envelope)
172+fromOctets :: [Word8] -> Word32
331− traceBytes "sendEnvelope" fullbytes
173+fromOctets = foldl' accum 0
175+ accum a o = (a `shiftL` 8) .|. fromIntegral o
333−fingerprint :: Typeable a => a -> Fingerprint
177+octets :: Word32 -> [Word8]
334−fingerprint = typeRepFingerprint . typeOf
178+octets w =
179+ [ fromIntegral (w `shiftR` 24)
180+ , fromIntegral (w `shiftR` 16)
181+ , fromIntegral (w `shiftR` 8)
336−fromArray :: Arr.Array Word8 -> BSI.ByteString
185+--send length-tagged bytestring, perhaps should be in network byte order?
337−fromArray Arr.Array {..}
186+sendMessage :: Serialise a => a -> Socket -> IO ()
338− | aLen == 0 = mempty
187+sendMessage msg socket' = do
339− | otherwise = BSI.PS aStart 0 aLen
188+ putStrLn ("sending bytes: " ++ show (blen <> bytes))
189+ writeChunk socket' (toArrayS (blen <> bytes))
191+ bytes = serialise msg
192+ len = BS.length bytes
193+ blen = BS.pack (octets (fromIntegral len))
196+--from streamly-bytestring
197+-- | Convert a 'ByteString' to an array of 'Word8'. This function unwraps the
198+-- 'ByteString' and wraps it with 'Array' constructors and hence the operation
199+-- is performed in constant time.
200+{-# INLINE toArrayS #-}
201+toArrayS :: BS.ByteString -> SA.Array Word8
203+--toArrayS = SA.fromList . BS.unpack
204+toArrayS (BSI.PS fp off len) = SA.Array nfp endPtr
206+ nfp = fp `plusForeignPtr` off
341− aStartPtr = unsafeForeignPtrToPtr aStart
207+ endPtr = unsafeForeignPtrToPtr nfp `plusPtr` len `plusPtr` 1 -- ? why +1?
342− aLen = aEnd `minusPtr` aStartPtr
208+