msgpack-rpc 0.7.1.2 → 0.8.0
raw patch · 6 files changed
+336/−319 lines, 6 filesdep +asyncdep +hspecdep +monad-controldep −deepseqdep ~attoparsec-conduitdep ~basedep ~conduitPVP ok
version bump matches the API change (PVP)
Dependencies added: async, hspec, monad-control, msgpack-rpc, network-conduit
Dependencies removed: deepseq
Dependency ranges changed: attoparsec-conduit, base, conduit, mtl, network
API changes (from Hackage documentation)
- Network.MessagePackRpc.Client: connect :: String -> Int -> IO Connection
- Network.MessagePackRpc.Client: data Connection
- Network.MessagePackRpc.Client: disconnect :: Connection -> IO ()
- Network.MessagePackRpc.Client: instance OBJECT o => RpcType (IO o)
- Network.MessagePackRpc.Client: method :: RpcType a => String -> RpcMethod a
- Network.MessagePackRpc.Client: type RpcMethod a = Connection -> a
- Network.MessagePackRpc.Server: class RpcMethodType f
- Network.MessagePackRpc.Server: fun :: RpcMethodType f => f -> RpcMethod
- Network.MessagePackRpc.Server: instance (OBJECT o, RpcMethodType r) => RpcMethodType (o -> r)
- Network.MessagePackRpc.Server: instance OBJECT o => RpcMethodType (IO o)
- Network.MessagePackRpc.Server: toRpcMethod :: RpcMethodType f => f -> RpcMethod
+ Network.MessagePackRpc.Client: data ClientT m a
+ Network.MessagePackRpc.Client: instance (MonadIO m, MonadThrow m, OBJECT o) => RpcType (ClientT m o)
+ Network.MessagePackRpc.Client: instance Monad m => Monad (ClientT m)
+ Network.MessagePackRpc.Client: instance MonadIO m => MonadIO (ClientT m)
+ Network.MessagePackRpc.Client: instance MonadThrow m => MonadThrow (ClientT m)
+ Network.MessagePackRpc.Client: instance MonadTrans ClientT
+ Network.MessagePackRpc.Client: runClient :: (MonadIO m, MonadBaseControl IO m) => String -> Int -> ClientT m a -> m ()
+ Network.MessagePackRpc.Client: type Client = ClientT IO
+ Network.MessagePackRpc.Server: MethodT :: m a -> MethodT m a
+ Network.MessagePackRpc.Server: class MethodType f m | f -> m
+ Network.MessagePackRpc.Server: instance (MonadThrow m, MonadBaseControl IO m, OBJECT o) => MethodType (MethodT m o) m
+ Network.MessagePackRpc.Server: instance (OBJECT o, MethodType r m) => MethodType (o -> r) m
+ Network.MessagePackRpc.Server: instance Applicative m => Applicative (MethodT m)
+ Network.MessagePackRpc.Server: instance Exception ServerError
+ Network.MessagePackRpc.Server: instance Functor m => Functor (MethodT m)
+ Network.MessagePackRpc.Server: instance Monad m => Monad (MethodT m)
+ Network.MessagePackRpc.Server: instance MonadIO m => MonadIO (MethodT m)
+ Network.MessagePackRpc.Server: instance MonadTrans MethodT
+ Network.MessagePackRpc.Server: instance Show ServerError
+ Network.MessagePackRpc.Server: instance Typeable ServerError
+ Network.MessagePackRpc.Server: newtype MethodT m a
+ Network.MessagePackRpc.Server: toMethod :: MethodType f m => f -> RpcMethod m
+ Network.MessagePackRpc.Server: type Method = MethodT IO
+ Network.MessagePackRpc.Server: unMethodT :: MethodT m a -> m a
- Network.MessagePackRpc.Client: call :: RpcType a => Connection -> String -> a
+ Network.MessagePackRpc.Client: call :: RpcType a => String -> a
- Network.MessagePackRpc.Server: serve :: Int -> [(String, RpcMethod)] -> IO ()
+ Network.MessagePackRpc.Server: serve :: (MonadIO m, MonadThrow m, MonadBaseControl IO m) => Int -> [(String, RpcMethod m)] -> m ()
- Network.MessagePackRpc.Server: type RpcMethod = [Object] -> IO Object
+ Network.MessagePackRpc.Server: type RpcMethod m = [Object] -> m Object
Files
- Network/MessagePackRpc/Client.hs +128/−139
- Network/MessagePackRpc/Server.hs +118/−118
- msgpack-rpc.cabal +46/−38
- test.hs +44/−0
- test/Cli.hs +0/−13
- test/Serv.hs +0/−11
Network/MessagePackRpc/Client.hs view
@@ -1,139 +1,128 @@-{-# Language DeriveDataTypeable #-}------------------------------------------------------------------------ |--- Module : Network.MessagePackRpc.Client--- Copyright : (c) Hideyuki Tanaka, 2010--- License : BSD3------ Maintainer: tanaka.hideyuki@gmail.com--- Stability : experimental--- Portability: portable------ This module is client library of MessagePack-RPC.--- The specification of MessagePack-RPC is at <http://redmine.msgpack.org/projects/msgpack/wiki/RPCProtocolSpec>.------ A simple example:------ >import Network.MessagePackRpc.Client--- >--- >add :: RpcMethod (Int -> Int -> IO Int)--- >add = method "add"--- >--- >main = do--- > conn <- connect "127.0.0.1" 1234--- > print =<< add conn 123 456--------------------------------------------------------------------------module Network.MessagePackRpc.Client (- -- * RPC connection- Connection,- connect,- disconnect,- - -- * RPC error- RpcError(..),- - -- * Call RPC method- RpcMethod,- call,- method,- ) where--import Control.Concurrent.MVar-import Control.Exception-import Control.Monad-import qualified Data.ByteString.Lazy as BL-import qualified Data.Conduit as C-import qualified Data.Conduit.Binary as CB-import qualified Data.Conduit.Attoparsec as CA-import Data.Functor-import Data.MessagePack-import Data.Typeable-import Network-import System.IO-import System.Random---- | RPC connection type-data Connection- = Connection- { connHandle :: MVar Handle }---- | Connect to RPC server-connect :: String -- ^ Host name- -> Int -- ^ Port number- -> IO Connection -- ^ Connection-connect addr port = withSocketsDo $ do- h <- connectTo addr (PortNumber $ fromIntegral port)- mh <- newMVar h- return $ Connection- { connHandle = mh- }---- | Disconnect a connection-disconnect :: Connection -> IO ()-disconnect Connection { connHandle = mh } =- hClose =<< takeMVar mh---- | RPC error type-data RpcError- = ServerError Object -- ^ Server error- | ResultTypeError String -- ^ Result type mismatch- | ProtocolError String -- ^ Protocol error- deriving (Eq, Ord, Typeable)--instance Exception RpcError--instance Show RpcError where- show (ServerError err) =- "server error: " ++ show err- show (ResultTypeError err) =- "result type error: " ++ err- show (ProtocolError err) =- "protocol error: " ++ err--class RpcType r where- rpcc :: Connection -> String -> [Object] -> r--fromObject' :: OBJECT o => Object -> o-fromObject' o =- case tryFromObject o of- Left err -> throw $ ResultTypeError err- Right r -> r--instance OBJECT o => RpcType (IO o) where- rpcc c m args = return . fromObject' =<< rpcCall c m (reverse args)--instance (OBJECT o, RpcType r) => RpcType (o -> r) where- rpcc c m args arg = rpcc c m (toObject arg:args)--rpcCall :: Connection -> String -> [Object] -> IO Object-rpcCall Connection{ connHandle = mh } m args = withMVar mh $ \h -> do- msgid <- (`mod`2^(30::Int)) <$> randomIO :: IO Int- BL.hPutStr h $ pack (0 ::Int, msgid, m, args)- hFlush h- C.runResourceT $ CB.sourceHandle h C.$$ do- (rtype, rmsgid, rerror, rresult) <- CA.sinkParser get- when (rtype /= (1 :: Int)) $- throw $ ProtocolError $ "response type is not 1 (got " ++ show rtype ++ ")"- when (rmsgid /= msgid) $- throw $ ProtocolError $ "message id mismatch: expect " ++ show msgid ++ ", but got " ++ show rmsgid- case tryFromObject rerror of- Left _ ->- throw $ ServerError rerror- Right () ->- return rresult---- | Call an RPC Method-call :: RpcType a =>- Connection -- ^ Connection- -> String -- ^ Method name- -> a-call c m = rpcc c m []---- | Create an RPC Method (call c m == method m c)-method :: RpcType a => String -> RpcMethod a-method c m = call m c--type RpcMethod a = Connection -> a+{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} +{-# LANGUAGE GADTs, FlexibleContexts #-} + +------------------------------------------------------------------- +-- | +-- Module : Network.MessagePackRpc.Client +-- Copyright : (c) Hideyuki Tanaka, 2010-2012 +-- License : BSD3 +-- +-- Maintainer: Hideyuki Tanaka <tanaka.hideyuki@gmail.com> +-- Stability : experimental +-- Portability: portable +-- +-- This module is client library of MessagePack-RPC. +-- The specification of MessagePack-RPC is at +-- <http://redmine.msgpack.org/projects/msgpack/wiki/RPCProtocolSpec>. +-- +-- A simple example: +-- +-- > import Network.MessagePackRpc.Client +-- > +-- > add :: Int -> Int -> Client Int +-- > add = call "add" +-- > +-- > main = runClient "localhost" 5000 $ do +-- > ret <- add 123 456 +-- > liftIO $ print ret +-- +-------------------------------------------------------------------- + +module Network.MessagePackRpc.Client ( + -- * MessagePack Client type + ClientT, Client, + runClient, + + -- * Call RPC method + call, + + -- * RPC error + RpcError(..), + ) where + +import Control.Exception +import Control.Monad +import Control.Monad.Trans.Control +import Control.Monad.State.Strict as CMS +import qualified Data.ByteString as S +import Data.Conduit +import qualified Data.Conduit.Attoparsec as CA +import qualified Data.Conduit.Binary as CB +import Data.Conduit.Network +import Data.MessagePack as M +import Data.Typeable + +type Client = ClientT IO + +newtype ClientT m a + = ClientT { unClientT :: StateT (Connection m) m a } + deriving (Monad, MonadIO, MonadThrow) + +-- It can't derive by newtype deriving... +instance MonadTrans ClientT where + lift = ClientT . lift + +-- | RPC connection type +data Connection m where + Connection :: + !(ResumableSource m S.ByteString) + -> !(Sink S.ByteString m ()) + -> !Int + -> Connection m + +runClient :: (MonadIO m, MonadBaseControl IO m) + => String -> Int -> ClientT m a -> m () +runClient host port m = do + runTCPClient (ClientSettings port host) $ \src sink -> do + (rsrc, _) <- src $$+ return () + void $ evalStateT (unClientT m) (Connection rsrc sink 0) + +-- | RPC error type +data RpcError + = ServerError Object -- ^ Server error + | ResultTypeError String -- ^ Result type mismatch + | ProtocolError String -- ^ Protocol error + deriving (Show, Eq, Ord, Typeable) + +instance Exception RpcError + +class RpcType r where + rpcc :: String -> [Object] -> r + +instance (MonadIO m, MonadThrow m, OBJECT o) => RpcType (ClientT m o) where + rpcc m args = do + res <- rpcCall m (reverse args) + case tryFromObject res of + Left err -> monadThrow $ ResultTypeError err + Right r -> return r + +instance (OBJECT o, RpcType r) => RpcType (o -> r) where + rpcc m args arg = rpcc m (toObject arg:args) + +rpcCall :: (MonadIO m, MonadThrow m) => String -> [Object] -> ClientT m Object +rpcCall methodName args = ClientT $ do + Connection rsrc sink msgid <- CMS.get + (rsrc', (rtype, rmsgid, rerror, rresult)) <- lift $ do + CB.sourceLbs (pack (0 :: Int, msgid, methodName, args)) $$ sink + rsrc $$++ CA.sinkParser M.get + CMS.put $ Connection rsrc' sink (msgid + 1) + + when (rtype /= (1 :: Int)) $ + monadThrow $ ProtocolError $ + "invalid response type (expect 1, but got " ++ show rtype ++ ")" + when (rmsgid /= msgid) $ + monadThrow $ ProtocolError $ + "message id mismatch: expect " + ++ show msgid ++ ", but got " + ++ show rmsgid + case tryFromObject rerror of + Left _ -> + monadThrow $ ServerError rerror + Right () -> + return rresult + +-- | Call an RPC Method +call :: RpcType a + => String -- ^ Method name + -> a +call m = rpcc m []
Network/MessagePackRpc/Server.hs view
@@ -1,118 +1,118 @@----------------------------------------------------------------------- |--- Module : Network.MessagePackRpc.Server--- Copyright : (c) Hideyuki Tanaka, 2010--- License : BSD3------ Maintainer: tanaka.hideyuki@gmail.com--- Stability : experimental--- Portability: portable------ This module is server library of MessagePack-RPC.--- The specification of MessagePack-RPC is at <http://redmine.msgpack.org/projects/msgpack/wiki/RPCProtocolSpec>.------ A simple example:------ >import Network.MessagePackRpc.Server--- >--- >add :: Int -> Int -> IO Int--- >add x y = return $ x + y--- >--- >main =--- > serve 1234 [("add", fun add)]--------------------------------------------------------------------------module Network.MessagePackRpc.Server (- -- * RPC method types- RpcMethod,- RpcMethodType(..),- -- * Create RPC method- fun,- -- * Start RPC server- serve,- ) where--import Control.Applicative-import Control.Concurrent-import Control.DeepSeq-import Control.Exception as E-import Control.Monad-import Control.Monad.Trans-import qualified Data.ByteString.Lazy as BL-import qualified Data.Conduit as C-import qualified Data.Conduit.Binary as CB-import qualified Data.Conduit.Attoparsec as CA-import Data.Maybe-import Data.MessagePack-import Network-import System.IO--import Prelude hiding (catch)--type RpcMethod = [Object] -> IO Object--class RpcMethodType f where- toRpcMethod :: f -> RpcMethod--instance OBJECT o => RpcMethodType (IO o) where- toRpcMethod m = \[] -> toObject <$> m--instance (OBJECT o, RpcMethodType r) => RpcMethodType (o -> r) where- toRpcMethod f = \(x:xs) -> toRpcMethod (f $! fromObject' x) xs--fromObject' :: OBJECT o => Object -> o-fromObject' o =- case tryFromObject o of- Left err -> error $ "argument type error: " ++ err- Right r -> r---- | Create a RPC method from a Haskell function.-fun :: RpcMethodType f => f -> RpcMethod-fun = toRpcMethod---- | Start RPC server with a set of RPC methods.-serve :: Int -- ^ Port number- -> [(String, RpcMethod)] -- ^ list of (method name, RPC method)- -> IO ()-serve port methods = withSocketsDo $ do- sock <- listenOn (PortNumber $ fromIntegral port)- forever $ do- (h, host, hostport) <- accept sock- forkIO $- (processRequests h `finally` hClose h) `catches`- [ Handler $ \e ->- case e of- CA.ParseError ["demandInput"] _ -> return ()- _ -> hPutStrLn stderr $ host ++ ":" ++ show hostport ++ ": " ++ show e- , Handler $ \e ->- hPutStrLn stderr $ host ++ ":" ++ show hostport ++ ": " ++ show (e :: SomeException)]-- where- processRequests h =- C.runResourceT $ CB.sourceHandle h C.$$ forever $ processRequest h- - processRequest h = do- (rtype, msgid, method, args) <- CA.sinkParser get- liftIO $ do- resp <- try $ getResponse rtype method args- case resp of- Left err ->- BL.hPutStr h $ pack (1 :: Int, msgid :: Int, show (err :: SomeException), ())- Right ret ->- BL.hPutStr h $ pack (1 :: Int, msgid :: Int, (), ret)- hFlush h-- getResponse rtype method args = do- when (rtype /= (0 :: Int)) $- fail "request type is not 0"- - r <- callMethod (method :: String) (args :: [Object])- r `deepseq` return r- - callMethod methodName args =- case lookup methodName methods of- Nothing ->- fail $ "method '" ++ methodName ++ "' not found"- Just method ->- method args+{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-} +{-# LANGUAGE OverloadedStrings #-} + +------------------------------------------------------------------- +-- | +-- Module : Network.MessagePackRpc.Server +-- Copyright : (c) Hideyuki Tanaka, 2010-2012 +-- License : BSD3 +-- +-- Maintainer: tanaka.hideyuki@gmail.com +-- Stability : experimental +-- Portability: portable +-- +-- This module is server library of MessagePack-RPC. +-- The specification of MessagePack-RPC is at +-- <http://redmine.msgpack.org/projects/msgpack/wiki/RPCProtocolSpec>. +-- +-- A simple example: +-- +-- > import Network.MessagePackRpc.Server +-- > +-- > add :: Int -> Int -> Method Int +-- > add x y = return $ x + y +-- > +-- > main = serve 1234 [("add", toMethod add)] +-- +-------------------------------------------------------------------- + +module Network.MessagePackRpc.Server ( + -- * RPC method types + RpcMethod, MethodType(..), + MethodT(..), Method, + -- * Start RPC server + serve, + ) where + +import Control.Applicative +import Control.Exception as E +import Control.Monad +import Control.Monad.Trans +import Control.Monad.Trans.Control +import Data.Conduit +import Data.Conduit.Network +import qualified Data.Conduit.Binary as CB +import qualified Data.Conduit.Attoparsec as CA +import Data.Data +import Data.MessagePack + +type RpcMethod m = [Object] -> m Object + +type Request = (Int, Int, String, [Object]) +type Response = (Int, Int, Object, Object) + +data ServerError = ServerError String + deriving (Show, Typeable) + +instance Exception ServerError + +type Method = MethodT IO + +newtype MethodT m a = MethodT { unMethodT :: m a } + deriving (Functor, Applicative, Monad, MonadIO) + +instance MonadTrans MethodT where + lift = MethodT + +class MethodType f m | f -> m where + -- | Create a RPC method from a Hakell function + toMethod :: f -> RpcMethod m + +instance (MonadThrow m, MonadBaseControl IO m, OBJECT o) + => MethodType (MethodT m o) m where + toMethod m ls = case ls of + [] -> toObject <$> unMethodT m + _ -> monadThrow $ ServerError "argument error" + +instance (OBJECT o, MethodType r m) => MethodType (o -> r) m where + toMethod f = \(x:xs) -> toMethod (f $! fromObject' x) xs + +fromObject' :: OBJECT o => Object -> o +fromObject' o = + case tryFromObject o of + Left err -> error $ "argument type error: " ++ err + Right r -> r + +-- | Start RPC server with a set of RPC methods. +serve :: forall m . (MonadIO m, MonadThrow m, MonadBaseControl IO m) + => Int -- ^ Port number + -> [(String, RpcMethod m)] -- ^ list of (method name, RPC method) + -> m () +serve port methods = runTCPServer (ServerSettings port "*") $ \src sink -> do + (rsrc, _) <- src $$+ return () + processRequests rsrc sink + where + processRequests rsrc sink = do + (rsrc', res) <- rsrc $$++ do + req <- CA.sinkParser get + lift $ getResponse req + _ <- CB.sourceLbs (pack res) $$ sink + processRequests rsrc' sink + + getResponse :: Request -> m Response + getResponse (rtype, msgid, methodName, args) = do + when (rtype /= 0) $ + monadThrow $ ServerError $ "request type is not 0, got " ++ show rtype + ret <- callMethod methodName args + return (1, msgid, toObject (), ret) + + callMethod :: String -> [Object] -> m Object + callMethod methodName args = + case lookup methodName methods of + Nothing -> + monadThrow $ ServerError $ "method '" ++ methodName ++ "' not found" + Just method -> + method args
msgpack-rpc.cabal view
@@ -1,38 +1,46 @@-Name: msgpack-rpc-Version: 0.7.1.2-Synopsis: A MessagePack-RPC Implementation-Description: A MessagePack-RPC Implementation <http://msgpack.org/>-Homepage: http://msgpack.org/-License: BSD3-License-file: LICENSE-Author: Hideyuki Tanaka-Maintainer: Hideyuki Tanaka <tanaka.hideyuki@gmail.com>-Copyright: Copyright (c) 2010-2012, Hideyuki Tanaka-Category: Network-Stability: Experimental-Cabal-version: >=1.8-Build-type: Simple--Extra-source-files: test/Serv.hs- test/Cli.hs--Library- Build-depends: base == 4.*- , bytestring == 0.9.*- , text == 0.11.*- , network >= 2.2 && < 2.4- , random == 1.0.*- , mtl >= 2.0- , conduit >= 0.2 && < 0.5- , attoparsec-conduit- , deepseq >= 1.1 && < 1.4- , msgpack == 0.7.*-- Ghc-options: -Wall-- Exposed-modules: Network.MessagePackRpc.Server- Network.MessagePackRpc.Client--Source-repository head- Type: git- Location: git://github.com/msgpack/msgpack-haskell.git+name: msgpack-rpc +version: 0.8.0 +synopsis: A MessagePack-RPC Implementation +description: A MessagePack-RPC Implementation <http://msgpack.org/> +homepage: http://msgpack.org/ +license: BSD3 +license-file: LICENSE +author: Hideyuki Tanaka +maintainer: Hideyuki Tanaka <tanaka.hideyuki@gmail.com> +copyright: (c) 2010-2012, Hideyuki Tanaka +category: Network +stability: Experimental +cabal-version: >=1.8 +build-type: Simple + +source-repository head + type: git + location: git://github.com/msgpack/msgpack-haskell.git + +library + build-depends: base == 4.* + , bytestring == 0.9.* + , text == 0.11.* + , network >= 2.2 && < 2.4 + , random == 1.0.* + , mtl >= 2.0 + , monad-control >= 0.3 + , conduit >= 0.5 + , network-conduit >= 0.5 + , attoparsec-conduit >= 0.5 + , msgpack == 0.7.* + + exposed-modules: Network.MessagePackRpc.Server + Network.MessagePackRpc.Client + +test-suite msgpack-rpc-test + type: exitcode-stdio-1.0 + hs-source-dirs: dist + main-is: ../test.hs + + build-depends: base + , mtl + , network + , async >= 2.0 + , hspec >= 1.3 + , msgpack-rpc
+ test.hs view
@@ -0,0 +1,44 @@+import Control.Applicative+import Control.Concurrent.Async+import Control.Monad.Trans++import Test.Hspec++import Network (withSocketsDo)+import Network.MessagePackRpc.Server+import Network.MessagePackRpc.Client++port :: Int+port = 5000++main :: IO ()+main = withSocketsDo $ hspec $ do+ describe "add service" $ do+ it "correct" $ do+ server `race_` client++server :: IO ()+server =+ serve port+ [ ("add", toMethod add)+ , ("echo", toMethod echo)+ ]+ where+ add :: Int -> Int -> Method Int+ add x y = return $ x + y++ echo :: String -> Method String+ echo s = return $ "***" ++ s ++ "***"++client :: IO ()+client = runClient "localhost" port $ do+ r1 <- add 123 456+ liftIO $ r1 `shouldBe` 123 + 456+ r2 <- echo "hello"+ liftIO $ r2 `shouldBe` "***hello***"+ where+ add :: Int -> Int -> Client Int+ add = call "add"++ echo :: String -> Client String+ echo = call "echo"
− test/Cli.hs
@@ -1,13 +0,0 @@-import Network.MessagePackRpc.Client--add :: RpcMethod (Int -> Int -> IO Int)-add = method "add"--echo :: RpcMethod (String -> IO String)-echo = method "echo"--main :: IO ()-main = do- conn <- connect "localhost" 8081- print =<< add conn 123 456- print =<< echo conn "hoge"
− test/Serv.hs
@@ -1,11 +0,0 @@-import Network.MessagePackRpc.Server--add :: Int -> Int -> IO Int-add x y = return $ x+y--echo :: String -> IO String-echo s = return s--main :: IO ()-main = do- serve 8081 [ ("add", fun add), ("echo", fun echo) ]