packages feed

curryer-rpc 0.3.8 → 0.4.0

raw patch · 9 files changed

+173/−53 lines, 9 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Network.RPC.Curryer.Server: type HostAddr = (Word8, Word8, Word8, Word8)
- Network.RPC.Curryer.StreamlyAdditions: acceptorOnAddr :: MonadIO m => [(SocketOption, Int)] -> Maybe (MVar SockAddr) -> Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket
+ Network.RPC.Curryer.Client: connectIPv4 :: ClientAsyncRequestHandlers -> HostAddressTuple -> PortNumber -> IO Connection
+ Network.RPC.Curryer.Client: connectIPv6 :: ClientAsyncRequestHandlers -> HostAddressTuple6 -> PortNumber -> IO Connection
+ Network.RPC.Curryer.Client: connectUnixDomain :: ClientAsyncRequestHandlers -> FilePath -> IO Connection
+ Network.RPC.Curryer.Server: defaultSocketOptions :: [(SocketOption, Int)]
+ Network.RPC.Curryer.Server: localHostAddr6 :: HostAddressTuple6
+ Network.RPC.Curryer.Server: serveIPv4 :: RequestHandlers s -> s -> HostAddressTuple -> PortNumber -> Maybe (MVar SockAddr) -> IO Bool
+ Network.RPC.Curryer.Server: serveIPv6 :: RequestHandlers s -> s -> HostAddressTuple6 -> PortNumber -> Maybe (MVar SockAddr) -> IO Bool
+ Network.RPC.Curryer.Server: serveUnixDomain :: RequestHandlers s -> s -> FilePath -> Maybe (MVar SockAddr) -> IO Bool
+ Network.RPC.Curryer.Server: type HostAddressTuple = (Word8, Word8, Word8, Word8)
+ Network.RPC.Curryer.Server: type HostAddressTuple6 = (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16)
+ Network.RPC.Curryer.StreamlyAdditions: acceptorOnSockSpec :: MonadIO m => SockSpec -> Maybe (MVar SockAddr) -> Unfold m SockAddr Socket
- Network.RPC.Curryer.Client: connect :: ClientAsyncRequestHandlers -> HostAddr -> PortNumber -> IO Connection
+ Network.RPC.Curryer.Client: connect :: ClientAsyncRequestHandlers -> SockSpec -> SockAddr -> IO Connection
- Network.RPC.Curryer.Server: allHostAddrs :: HostAddr
+ Network.RPC.Curryer.Server: allHostAddrs :: HostAddressTuple
- Network.RPC.Curryer.Server: localHostAddr :: HostAddr
+ Network.RPC.Curryer.Server: localHostAddr :: HostAddressTuple
- Network.RPC.Curryer.Server: serve :: RequestHandlers s -> s -> HostAddr -> PortNumber -> Maybe (MVar SockAddr) -> IO Bool
+ Network.RPC.Curryer.Server: serve :: RequestHandlers s -> s -> SockSpec -> SockAddr -> Maybe (MVar SockAddr) -> IO Bool

Files

Changelog.markdown view
@@ -1,3 +1,7 @@+# v0.4.0 (2025-03-29)++* add ability to serve and connect to IPv6 and Unix domain sockets (#5)+ # v0.3.8 (2024-12-20)  * add support for winery 1.5 but don't require it
bench/Basic.hs view
@@ -15,10 +15,10 @@ main = do   --start server shared across benchmarks- gather stats on round-trip requests-responses   portReadyMVar <- newEmptyMVar-  server <- async (serve benchmarkServerRequestHandlers () localHostAddr 0 (Just portReadyMVar))+  server <- async (serveIPv4 benchmarkServerRequestHandlers () localHostAddr 0 (Just portReadyMVar))   --wait for server to be ready   (SockAddrInet port _) <- takeMVar portReadyMVar-  clientConn <- connect [] localHostAddr port+  clientConn <- connectIPv4 [] localHostAddr port    _ <- syncWaitRequest clientConn 100   let syncWait ms = nfIO (syncWaitRequest clientConn ms)
curryer-rpc.cabal view
@@ -1,5 +1,5 @@ Name: curryer-rpc-Version: 0.3.8+Version: 0.4.0 License: PublicDomain Build-Type: Simple Homepage: https://github.com/agentm/curryer
examples/SimpleKeyValueClient.hs view
@@ -23,7 +23,7 @@ main = do   opts <- getRecord "SimpleKeyValueClient"   -- connect to the remote server (in this case on the localhost address)-  conn <- connect [] localHostAddr 8765+  conn <- connectIPv4 [] localHostAddr 8765   case opts of     Get k -> do       --call the remote function and validate the result
examples/SimpleKeyValueServer.hs view
@@ -19,7 +19,7 @@ main :: IO () main = do   kvmap <- M.newIO-  void $ serve kvRequestHandlers kvmap localHostAddr 8765 Nothing+  void $ serveIPv4 kvRequestHandlers kvmap localHostAddr 8765 Nothing  -- setup incoming request handlers to operate on the server's state kvRequestHandlers :: RequestHandlers (M.Map String String)
src/Network/RPC/Curryer/Client.hs view
@@ -1,8 +1,9 @@ {-# LANGUAGE RankNTypes, ScopedTypeVariables, GADTs #-} module Network.RPC.Curryer.Client where import Network.RPC.Curryer.Server-import Network.Socket as Socket-import qualified Streamly.Network.Inet.TCP as TCP+import Network.Socket as Socket (Socket, PortNumber, SockAddr(..), close, Family(..), SocketType(..), tupleToHostAddress, tupleToHostAddress6)+import Streamly.Internal.Network.Socket (SockSpec(..))+import qualified Streamly.Internal.Network.Socket as SINS import Codec.Winery import Control.Concurrent.Async import qualified Data.UUID.V4 as UUIDBase@@ -27,15 +28,57 @@  type ClientAsyncRequestHandlers = [ClientAsyncRequestHandler] +-- | Connect to a remote server over IPv4. Wraps `connect`.+connectIPv4 ::+  ClientAsyncRequestHandlers ->+  HostAddressTuple ->+  PortNumber ->+  IO Connection+connectIPv4 asyncHandlers hostaddr portnum =+  connect asyncHandlers sockSpec sockAddr+  where+    sockSpec = SINS.SockSpec { sockFamily = AF_INET,+                               sockType = Stream,+                               sockProto = 0,+                               sockOpts = [] }+    sockAddr = SockAddrInet portnum (tupleToHostAddress hostaddr)++-- | Connect to a remote server over IPv6. Wraps `connect`.+connectIPv6 ::+  ClientAsyncRequestHandlers ->+  HostAddressTuple6 ->+  PortNumber ->+  IO Connection+connectIPv6 asyncHandlers hostaddr portnum =+  connect asyncHandlers sockSpec sockAddr  +  where+    sockSpec = SINS.SockSpec { sockFamily = AF_INET6,+                               sockType = Stream,+                               sockProto = 0,+                               sockOpts = [] }+    sockAddr = SockAddrInet6 portnum 0 (tupleToHostAddress6 hostaddr) 0++connectUnixDomain ::+  ClientAsyncRequestHandlers ->+  FilePath ->+  IO Connection+connectUnixDomain asyncHandlers socketPath =+  connect asyncHandlers sockSpec sockAddr+  where+    sockSpec = SINS.SockSpec { sockFamily = AF_UNIX,+                               sockType = Stream,+                               sockProto = 0,+                               sockOpts = [] }+    sockAddr = SockAddrUnix socketPath+ -- | Connects to a remote server with specific async callbacks registered. connect ::    ClientAsyncRequestHandlers ->-  HostAddr ->-  PortNumber ->+  SINS.SockSpec ->+  SockAddr ->   IO Connection-connect asyncHandlers hostAddr portNum = do-  sock <- TCP.connect hostAddr portNum-  Socket.setSocketOption sock NoDelay 1+connect asyncHandlers sockSpec sockAddr = do+  sock <- SINS.connect sockSpec sockAddr   syncmap <- STMMap.newIO   asyncThread <- async (clientAsync sock syncmap asyncHandlers)   sockLock <- newLock sock@@ -145,3 +188,4 @@       fprint = fingerprint msg   sendEnvelope envelope (_conn_sockLock conn)   pure (Right ())+
src/Network/RPC/Curryer/Server.hs view
@@ -169,15 +169,19 @@  instance Exception TimeoutException   -type HostAddr = (Word8, Word8, Word8, Word8)- type BParser a = Parser Word8 IO a -allHostAddrs,localHostAddr :: HostAddr+type HostAddressTuple = (Word8, Word8, Word8, Word8)+type HostAddressTuple6 = (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16)+                        +allHostAddrs,localHostAddr :: HostAddressTuple allHostAddrs = (0,0,0,0) localHostAddr = (127,0,0,1) +localHostAddr6 :: HostAddressTuple6+localHostAddr6 = (0,0,0,0,0,0,0,1) + msgTypeP :: BParser MessageType msgTypeP = (P.satisfy (== 0) *>              (RequestMessage . fromIntegral <$> word32P)) <|>@@ -261,19 +265,59 @@  type NewMessageHandler req resp = req -> IO resp +defaultSocketOptions :: [(SocketOption, Int)]+defaultSocketOptions = [(ReuseAddr, 1), (NoDelay, 1)]++-- | Listen for new connections and handle requests on an IPv4 address. Wraps `serve1.+serveIPv4 :: RequestHandlers s -> s -> HostAddressTuple -> PortNumber -> Maybe (MVar SockAddr) -> IO Bool+serveIPv4 handlers state hostaddr port mSockLock =+  serve handlers state sockSpec sockAddr mSockLock+  where+    sockAddr = SockAddrInet port (tupleToHostAddress hostaddr)+    sockSpec = SockSpec { sockFamily = AF_INET,+                          sockType = Stream,+                          sockProto = 0,+                          sockOpts = defaultSocketOptions+                        }++-- | Listen for IPv6 RPC requests. Wraps `serve`.+serveIPv6 :: RequestHandlers s -> s -> HostAddressTuple6 -> PortNumber -> Maybe (MVar SockAddr) -> IO Bool+serveIPv6 handlers state hostaddr port mSockLock =+  serve handlers state sockSpec sockAddr mSockLock+  where+    flowInfo = 0+    scopeInfo = 0+    sockAddr = SockAddrInet6 port flowInfo (tupleToHostAddress6 hostaddr) scopeInfo+    sockSpec = SockSpec { sockFamily = AF_INET6,+                          sockType = Stream,+                          sockProto = 0,+                          sockOpts = defaultSocketOptions+                          }++-- | Listen for Unix domain socket RPC requests. Wraps `serve`.+serveUnixDomain :: RequestHandlers s -> s -> FilePath -> Maybe (MVar SockAddr) -> IO Bool+serveUnixDomain handlers state socketPath mSockLock =+  serve handlers state sockSpec sockAddr mSockLock+  where+    sockSpec = SockSpec { sockFamily = AF_UNIX,+                          sockType = Stream,+                          sockProto = 0,+                          sockOpts = [] }+    sockAddr = SockAddrUnix socketPath+   -- | 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. serve ::           RequestHandlers s->          s ->-         HostAddr ->-         PortNumber ->+         SockSpec ->+         SockAddr ->           Maybe (MVar SockAddr) ->          IO Bool-serve userMsgHandlers serverState hostaddr port mSockLock = do+serve userMsgHandlers serverState sockSpec sockAddr mSockLock = do   let handleSock sock = do         lockingSocket <- newLock sock         drainSocketMessages sock (serverEnvelopeHandler lockingSocket userMsgHandlers serverState)-  Stream.unfold (SA.acceptorOnAddr [(ReuseAddr, 1), (NoDelay, 1)] mSockLock) (hostaddr, port) +  Stream.unfold (SA.acceptorOnSockSpec sockSpec mSockLock) sockAddr    & Stream.parMapM id handleSock    & Stream.fold FL.drain   pure True
src/Network/RPC/Curryer/StreamlyAdditions.hs view
@@ -1,32 +1,28 @@ {-# LANGUAGE CPP #-} module Network.RPC.Curryer.StreamlyAdditions where import Control.Monad.IO.Class-import Network.Socket (Socket, PortNumber, SocketOption, SockAddr(..), maxListenQueue, Family(..), SocketType(..), defaultProtocol, tupleToHostAddress, withSocketsDo, socket, setSocketOption, bind, getSocketName)+import Network.Socket (Socket, SockAddr(..), maxListenQueue, withSocketsDo, socket, setSocketOption, bind, getSocketName) import qualified Network.Socket as Net import Control.Exception (onException) import Control.Concurrent.MVar-import Data.Word import qualified Streamly.Internal.Data.Unfold as UF import Streamly.Network.Socket hiding (acceptor) import qualified Streamly.Internal.Data.Stream as D import Streamly.Internal.Data.Unfold (Unfold(..))+import Control.Monad (when)+--import Streamly.Internal.Network.Socket as INS -acceptorOnAddr+acceptorOnSockSpec     :: MonadIO m-    => [(SocketOption, Int)]+    => SockSpec     -> Maybe (MVar SockAddr)-    -> Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket-acceptorOnAddr opts mLock = UF.lmap f (acceptor mLock)+    -> Unfold m SockAddr Socket+acceptorOnSockSpec sockSpec mLock = UF.lmap f (acceptor mLock)     where-    f (addr, port) =+    f sockAddr' =         (maxListenQueue-        , SockSpec-            { sockFamily = AF_INET-            , sockType = Stream-            , sockProto = defaultProtocol -- TCP-            , sockOpts = opts-            }-        , SockAddrInet port (tupleToHostAddress addr)+        , sockSpec+        , sockAddr'         )  acceptor :: MonadIO m => Maybe (MVar SockAddr) -> Unfold m (Int, SockSpec, SockAddr) Socket@@ -60,7 +56,7 @@     where      use sock = do-        mapM_ (uncurry (setSocketOption sock)) (sockOpts sockSpec)+        when (not (null (sockOpts sockSpec))) $ mapM_ (uncurry (setSocketOption sock)) (sockOpts sockSpec)         bind sock addr-        Net.listen sock listenQLen        +        Net.listen sock listenQLen         
test/Curryer/Test/Basic.hs view
@@ -30,6 +30,8 @@   ,testCase "multi-threaded client" testMultithreadedClient   ,testCase "server state" testServerState   ,testCase "request handler throws timeout" testRequestHandlerThrowTimeout+  ,testCase "ipv6 server" testIPv6Server+  ,testCase "unix domain socket server" testUnixDomainSocketServer   ]  @@ -102,10 +104,10 @@ testSimpleCall = do   readyVar <- newEmptyMVar         -  server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))   --wait for server to be ready   (SockAddrInet port _) <- takeMVar readyVar-  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   replicateM_ 5 $ do --make five AddTwo calls to shake out parallelism bugs     x <- call conn (AddTwoNumbersReq 1 1)     assertEqual "server request+response" (Right (2 :: Int)) x@@ -121,9 +123,9 @@   let clientAsyncHandlers =         [ClientAsyncRequestHandler (\(AsyncHelloReq s) ->                                         putMVar receivedAsyncMessageVar s)]-  server <- async (serve (testServerRequestHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))+  server <- async (serveIPv4 (testServerRequestHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))   (SockAddrInet port _) <- takeMVar portReadyVar-  conn <- connect clientAsyncHandlers localHostAddr port+  conn <- connectIPv4 clientAsyncHandlers localHostAddr port   Right () <- asyncCall conn (AsyncHelloReq "welcome")   asyncMessage <- takeMVar receivedAsyncMessageVar   assertEqual "async message" "welcome" asyncMessage@@ -140,10 +142,10 @@   portReadyVar <- newEmptyMVar   receivedAsyncMessageVar <- newEmptyMVar   -  server <- async (serve (testServerRequestHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))+  server <- async (serveIPv4 (testServerRequestHandlers (Just receivedAsyncMessageVar)) emptyServerState localHostAddr 0 (Just portReadyVar))   (SockAddrInet port _) <- takeMVar portReadyVar -  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   --send an async message, wait for an async response to confirm receipt   Right () <- asyncCall conn (TestCallMeBackReq "hi server")   asyncMessage <- takeMVar receivedAsyncMessageVar@@ -155,10 +157,10 @@ testSyncClientCallTimeout = do   readyVar <- newEmptyMVar         -  server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))   --wait for server to be ready   (SockAddrInet port _) <- takeMVar readyVar-  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   x <- callTimeout @_ @Int (Just 500) conn (DelayMicrosecondsReq 1000)   assertEqual "client sync timeout" (Left TimeoutError) x   close conn@@ -168,10 +170,10 @@ testSyncException = do   readyVar <- newEmptyMVar         -  server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))   (SockAddrInet port _) <- takeMVar readyVar -  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   ret <- call conn ThrowServerSideExceptionReq   case ret of     Left (ExceptionError actualExc) ->@@ -187,9 +189,9 @@ testMultithreadedClient = do   readyVar <- newEmptyMVar         -  server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))   (SockAddrInet port _) <- takeMVar readyVar-  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   let bigString = replicate (1024 * 1000) 'x'   replicateM_ 10 $ do     ret <- call conn (RoundtripStringReq bigString)@@ -207,9 +209,9 @@                                              modifyTVar (connectionServerState sState) (+ 1))                                              ]   serverState <- newTVarIO @Int 1-  server <- async (serve serverHandlers serverState localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 serverHandlers serverState localHostAddr 0 (Just readyVar))   (SockAddrInet port _) <- takeMVar readyVar-  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   ret <- call conn ChangeServerState   assertEqual "server ret" (Right ()) ret   serverState' <- readTVarIO serverState@@ -226,9 +228,9 @@                                           throw TimeoutException >> pure (1 :: Int)                                           )                                              ]-  server <- async (serve serverHandlers () localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 serverHandlers () localHostAddr 0 (Just readyVar))   (SockAddrInet port _) <- takeMVar readyVar-  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   ret <- call @_ @Int conn ThrowTimeout   assertEqual "handler timeout exception" (Left TimeoutError) ret   close conn@@ -244,12 +246,42 @@ testComplexADT = do   let arg = SomeTextCon (pack "sduofhsldkfsldkfjsldkfjsdlkfjsdlfksdjlfksjdflksdjflksdjfjlsdjlfjdklskfjlsdlfk") (SomeIntCon 0 (EndCon))   readyVar <- newEmptyMVar-  server <- async (serve (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))+  server <- async (serveIPv4 (testServerRequestHandlers Nothing) emptyServerState localHostAddr 0 (Just readyVar))   --wait for server to be ready   (SockAddrInet port _) <- takeMVar readyVar-  conn <- connect [] localHostAddr port+  conn <- connectIPv4 [] localHostAddr port   replicateM_ 5 $ do --make five AddTwo calls to shake out parallelism bugs     res <- call conn (RoundtripSomeADTReq arg)     assertEqual "complex ADT" (Right arg) res   close conn   cancel server++testIPv6Server :: Assertion+testIPv6Server = do+  readyVar <- newEmptyMVar+        +  server <- async (serveIPv6 (testServerRequestHandlers Nothing) emptyServerState localHostAddr6 0 (Just readyVar))+  --wait for server to be ready+  (SockAddrInet6 port _ _ _) <- takeMVar readyVar+  conn <- connectIPv6 [] localHostAddr6 port+  replicateM_ 5 $ do --make five AddTwo calls to shake out parallelism bugs+    x <- call conn (AddTwoNumbersReq 1 1)+    assertEqual "server request+response" (Right (2 :: Int)) x+  close conn+  cancel server+  +testUnixDomainSocketServer :: Assertion+testUnixDomainSocketServer = do+  readyVar <- newEmptyMVar+  let socketPath = "/tmp/curryertest.sock"+  server <- async (serveUnixDomain (testServerRequestHandlers Nothing) emptyServerState socketPath (Just readyVar))++  --wait for server to be ready+  (SockAddrUnix socketPath') <- takeMVar readyVar+  conn <- connectUnixDomain [] socketPath'+  replicateM_ 5 $ do --make five AddTwo calls to shake out parallelism bugs+    x <- call conn (AddTwoNumbersReq 1 1)+    assertEqual "server request+response" (Right (2 :: Int)) x+  close conn+  cancel server+