summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--krpc.cabal6
-rw-r--r--src/Network/KRPC.hs195
-rw-r--r--src/Network/KRPC/Manager.hs179
-rw-r--r--src/Network/KRPC/Message.hs47
-rw-r--r--src/Network/KRPC/Method.hs61
5 files changed, 303 insertions, 185 deletions
diff --git a/krpc.cabal b/krpc.cabal
index aa081a54..bccdd6c3 100644
--- a/krpc.cabal
+++ b/krpc.cabal
@@ -37,7 +37,10 @@ library
37 , RecordWildCards 37 , RecordWildCards
38 hs-source-dirs: src 38 hs-source-dirs: src
39 exposed-modules: Network.KRPC 39 exposed-modules: Network.KRPC
40 , Network.KRPC.Message 40 Network.KRPC.Message
41 Network.KRPC.Method
42 Network.KRPC.Manager
43
41 build-depends: base == 4.* 44 build-depends: base == 4.*
42 , bytestring >= 0.10 45 , bytestring >= 0.10
43 , lifted-base >= 0.1.1 46 , lifted-base >= 0.1.1
@@ -45,6 +48,7 @@ library
45 , monad-control >= 0.3 48 , monad-control >= 0.3
46 , bencoding >= 0.4.3 49 , bencoding >= 0.4.3
47 , network >= 2.3 50 , network >= 2.3
51 , containers
48 52
49 if impl(ghc < 7.6) 53 if impl(ghc < 7.6)
50 build-depends: ghc-prim 54 build-depends: ghc-prim
diff --git a/src/Network/KRPC.hs b/src/Network/KRPC.hs
index a96d8da9..09d1c5b2 100644
--- a/src/Network/KRPC.hs
+++ b/src/Network/KRPC.hs
@@ -97,190 +97,23 @@
97{-# LANGUAGE GeneralizedNewtypeDeriving #-} 97{-# LANGUAGE GeneralizedNewtypeDeriving #-}
98{-# LANGUAGE FunctionalDependencies #-} 98{-# LANGUAGE FunctionalDependencies #-}
99module Network.KRPC 99module Network.KRPC
100 ( KRPC (..) 100 ( -- * Methods
101 Method
102 , KRPC (..)
101 103
102 -- * Exception 104 -- * RPC
103 , KError (..) 105 , handler
104 106 , query
105 -- * Method
106 , Method(..)
107 107
108 -- * Client 108 -- * Manager
109 , call 109 , MonadKRPC (..)
110 , newManager
111-- , closeManager
110 112
111 -- * Server 113 -- * Exceptions
112 , MethodHandler 114 , KError (..)
113 , handler
114 , server
115 ) where 115 ) where
116 116
117import Control.Applicative
118import Control.Exception.Lifted as Lifted
119import Control.Monad
120import Control.Monad.Trans.Control
121import Control.Monad.IO.Class
122import Data.BEncode as BE
123import Data.ByteString.Char8 as BC
124import Data.ByteString.Lazy as BL
125import Data.List as L
126import Data.Monoid
127import Data.String
128import Data.Typeable
129import Network
130import Network.Socket
131import Network.Socket.ByteString as BS
132
133import Network.KRPC.Message 117import Network.KRPC.Message
134 118import Network.KRPC.Method
135 119import Network.KRPC.Manager \ No newline at end of file
136class (BEncode req, BEncode resp) => KRPC req resp | req -> resp where
137 method :: Method req resp
138
139-- | Method datatype used to describe name, parameters and return
140-- values of procedure. Client use a method to /invoke/, server
141-- /implements/ the method to make the actual work.
142--
143-- We use the following fantom types to ensure type-safiety:
144--
145-- * param: Type of method parameters. Ordinary Tuple type used
146-- to specify more than one parameter, so for example @Method
147-- (Int, Int) result@ will take two arguments.
148--
149-- * result: Type of return value of the method. Similarly,
150-- tuple used to specify more than one return value, so for
151-- exsample @Method (Foo, Bar) (Bar, Foo)@ will take two arguments
152-- and return two values.
153--
154newtype Method param result = Method MethodName
155 deriving (Eq, Ord, IsString, BEncode)
156
157instance (Typeable a, Typeable b) => Show (Method a b) where
158 showsPrec _ = showsMethod
159
160showsMethod :: forall a. forall b. Typeable a => Typeable b
161 => Method a b -> ShowS
162showsMethod (Method name) =
163 shows name <>
164 showString " :: " <>
165 shows paramsTy <>
166 showString " -> " <>
167 shows valuesTy
168 where
169 impossible = error "KRPC.showsMethod: impossible"
170 paramsTy = typeOf (impossible :: a)
171 valuesTy = typeOf (impossible :: b)
172
173{-----------------------------------------------------------------------
174-- Client
175-----------------------------------------------------------------------}
176
177sendMessage :: BEncode msg => msg -> SockAddr -> Socket -> IO ()
178sendMessage msg addr sock = sendManyTo sock (BL.toChunks (encode msg)) addr
179{-# INLINE sendMessage #-}
180
181maxMsgSize :: Int
182--maxMsgSize = 512 -- release: size of payload of one udp packet
183maxMsgSize = 64 * 1024 -- bench: max UDP MTU
184{-# INLINE maxMsgSize #-}
185
186recvResponse :: Socket -> IO (Either KError KResponse)
187recvResponse sock = do
188 (raw, _) <- BS.recvFrom sock maxMsgSize
189 return $ case decode raw of
190 Right resp -> Right resp
191 Left decE -> Left $ case decode raw of
192 Right kerror -> kerror
193 _ -> KError ProtocolError (BC.pack decE) undefined
194
195withRemote :: (MonadBaseControl IO m, MonadIO m) => (Socket -> m a) -> m a
196withRemote = bracket (liftIO (socket AF_INET6 Datagram defaultProtocol))
197 (liftIO . sClose)
198{-# SPECIALIZE withRemote :: (Socket -> IO a) -> IO a #-}
199
200getResult :: BEncode result => Socket -> IO result
201getResult sock = do
202 KResponse {..} <- either throw return =<< recvResponse sock
203 case fromBEncode respVals of
204 Left msg -> throw $ KError ProtocolError (BC.pack msg) respId
205 Right r -> return r
206
207-- | Makes remote procedure call. Throws RPCException on any error
208-- occurred.
209call :: forall req resp host.
210 (MonadBaseControl IO host, MonadIO host, KRPC req resp)
211 => SockAddr -> req -> host resp
212call addr arg = liftIO $ withRemote $ \sock -> do
213 sendMessage (KQuery (toBEncode arg) name undefined) addr sock
214 getResult sock
215 where
216 Method name = method :: Method req resp
217
218{-----------------------------------------------------------------------
219-- Server
220-----------------------------------------------------------------------}
221
222type HandlerBody remote = SockAddr -> KQuery -> remote (Either KError KResponse)
223
224-- | Procedure signature and implementation binded up.
225type MethodHandler remote = (MethodName, HandlerBody remote)
226
227-- | Similar to '==>@' but additionally pass caller address.
228handler :: forall (remote :: * -> *) (req :: *) (resp :: *).
229 (KRPC req resp, Monad remote)
230 => (SockAddr -> req -> remote resp) -> MethodHandler remote
231handler body = (name, newbody)
232 where
233 Method name = method :: Method req resp
234
235 {-# INLINE newbody #-}
236 newbody addr KQuery {..} =
237 case fromBEncode queryArgs of
238 Left e -> return $ Left $ KError ProtocolError (BC.pack e) queryId
239 Right a -> do
240 r <- body addr a
241 return $ Right $ KResponse (toBEncode r) queryId
242
243sockAddrFamily :: SockAddr -> Family
244sockAddrFamily (SockAddrInet _ _ ) = AF_INET
245sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6
246sockAddrFamily (SockAddrUnix _ ) = AF_UNIX
247
248-- | Run server using a given port. Method invocation should be done manually.
249remoteServer :: (MonadBaseControl IO remote, MonadIO remote)
250 => SockAddr -- ^ Port number to listen.
251 -> (SockAddr -> KQuery -> remote (Either KError KResponse))
252 -> remote ()
253remoteServer servAddr action = bracket (liftIO bindServ) (liftIO . sClose) loop
254 where
255 bindServ = do
256 let family = sockAddrFamily servAddr
257 sock <- socket family Datagram defaultProtocol
258 when (family == AF_INET6) $ do
259 setSocketOption sock IPv6Only 0
260 bindSocket sock servAddr
261 return sock
262
263 loop sock = forever $ do
264 (bs, addr) <- liftIO $ BS.recvFrom sock maxMsgSize
265 reply <- handleMsg bs addr
266 liftIO $ sendMessage reply addr sock
267 where
268 handleMsg bs addr = case decode bs of
269 Right query -> (either toBEncode toBEncode <$> action addr query)
270 `Lifted.catch` (return . toBEncode . (`serverError` undefined ))
271 Left decodeE -> return $ toBEncode $
272 KError ProtocolError (BC.pack decodeE) undefined
273
274-- | Run RPC server on specified port by using list of handlers.
275-- Server will dispatch procedure specified by callee, but note that
276-- it will not create new thread for each connection.
277--
278server :: (MonadBaseControl IO remote, MonadIO remote)
279 => SockAddr -- ^ Port used to accept incoming connections.
280 -> [MethodHandler remote] -- ^ Method table.
281 -> remote ()
282server servAddr handlers = do
283 remoteServer servAddr $ \addr q @ KQuery {..} -> do
284 case L.lookup queryMethod handlers of
285 Nothing -> return $ Left $ KError MethodUnknown queryMethod queryId
286 Just m -> m addr q
diff --git a/src/Network/KRPC/Manager.hs b/src/Network/KRPC/Manager.hs
new file mode 100644
index 00000000..9aa1bea7
--- /dev/null
+++ b/src/Network/KRPC/Manager.hs
@@ -0,0 +1,179 @@
1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3module Network.KRPC.Manager
4 ( MonadKRPC (..)
5 , newManager
6 , query
7 , handler
8 ) where
9
10import Control.Applicative
11import Control.Arrow
12import Control.Concurrent
13--import Control.Exception hiding (Handler)
14import Control.Exception.Lifted as Lifted hiding (Handler)
15import Control.Monad
16import Control.Monad.Trans.Control
17import Control.Monad.IO.Class
18import Data.BEncode as BE
19import Data.ByteString.Char8 as BC
20import Data.ByteString.Lazy as BL
21import Data.IORef
22import Data.List as L
23import Data.Map as M
24import Network.KRPC.Message
25import Network.KRPC.Method
26import Network.Socket
27import Network.Socket.ByteString as BS
28
29
30type KResult = Either KError KResponse
31
32type TransactionCounter = IORef Int
33type CallId = (TransactionId, SockAddr)
34type CallRes = MVar KResult
35type PendingCalls = IORef (Map CallId CallRes)
36
37type HandlerBody m = SockAddr -> BValue -> m (BE.Result BValue)
38type Handler m = (MethodName, HandlerBody m)
39
40data Manager m = Manager
41 { sock :: !Socket
42 , transactionCounter :: {-# UNPACK #-} !TransactionCounter
43 , pendingCalls :: {-# UNPACK #-} !PendingCalls
44 , handlers :: [Handler m]
45 }
46
47class (MonadBaseControl IO m, MonadIO m) => MonadKRPC m where
48 getManager :: m (Manager a)
49
50sockAddrFamily :: SockAddr -> Family
51sockAddrFamily (SockAddrInet _ _ ) = AF_INET
52sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6
53sockAddrFamily (SockAddrUnix _ ) = AF_UNIX
54
55seedTransaction :: Int
56seedTransaction = 0
57
58newManager :: SockAddr -> IO (Manager a)
59newManager servAddr = do
60 sock <- bindServ
61 tran <- newIORef seedTransaction
62 calls <- newIORef M.empty
63 return $ Manager sock tran calls []
64 where
65 bindServ = do
66 let family = sockAddrFamily servAddr
67 sock <- socket family Datagram defaultProtocol
68 when (family == AF_INET6) $ do
69 setSocketOption sock IPv6Only 0
70 bindSocket sock servAddr
71 return sock
72
73sendMessage :: MonadIO m => BEncode a => Socket -> SockAddr -> a -> m ()
74sendMessage sock addr a =
75 liftIO $ sendManyTo sock (BL.toChunks (BE.encode a)) addr
76
77{-----------------------------------------------------------------------
78-- Client
79-----------------------------------------------------------------------}
80
81genTransactionId :: TransactionCounter -> IO TransactionId
82genTransactionId ref = do
83 cur <- atomicModifyIORef' ref $ \ cur -> (succ cur, cur)
84 return $ BC.pack (show cur)
85
86registerQuery :: CallId -> PendingCalls -> IO CallRes
87registerQuery cid ref = do
88 ares <- newEmptyMVar
89 atomicModifyIORef' ref $ \ m -> (M.insert cid ares m, ())
90 return ares
91
92unregisterQuery :: CallId -> PendingCalls -> IO ()
93unregisterQuery cid ref = do
94 atomicModifyIORef' ref $ \ m -> (M.delete cid m, ())
95
96queryResponse :: BEncode a => CallRes -> IO a
97queryResponse ares = do
98 res <- readMVar ares
99 case res of
100 Left e -> throwIO e
101 Right (KResponse {..}) ->
102 case fromBEncode respVals of
103 Left e -> throwIO (KError ProtocolError (BC.pack e) respId)
104 Right a -> return a
105
106query :: forall m a b. (MonadKRPC m, KRPC a b) => SockAddr -> a -> m b
107query addr params = do
108 Manager {..} <- getManager
109 liftIO $ do
110 tid <- genTransactionId transactionCounter
111 let Method name = method :: Method a b
112 let q = KQuery (toBEncode params) name tid
113 ares <- registerQuery (tid, addr) pendingCalls
114 sendMessage sock addr q
115 `onException` unregisterQuery (tid, addr) pendingCalls
116 queryResponse ares
117
118{-----------------------------------------------------------------------
119-- Handlers
120-----------------------------------------------------------------------}
121
122handler :: forall m a b. (KRPC a b, MonadKRPC m)
123 => (SockAddr -> a -> m b) -> Handler m
124handler body = (name, wrapper)
125 where
126 Method name = method :: Method a b
127 wrapper addr args =
128 case fromBEncode args of
129 Left e -> return $ Left e
130 Right a -> (Right . toBEncode) <$> body addr a
131
132runHandler :: MonadKRPC m => HandlerBody m -> SockAddr -> KQuery -> m KResult
133runHandler handler addr KQuery {..} = wrapper `Lifted.catch` failback
134 where
135 wrapper = ((`decodeError` queryId) +++ (`KResponse` queryId))
136 <$> handler addr queryArgs
137 failback e = return $ Left $ serverError e queryId
138
139dispatchHandler :: MonadKRPC m => KQuery -> SockAddr -> m KResult
140dispatchHandler q @ KQuery {..} addr = do
141 Manager {..} <- getManager
142 case L.lookup queryMethod handlers of
143 Nothing -> return $ Left $ unknownMethod queryMethod queryId
144 Just handler -> runHandler handler addr q
145
146{-----------------------------------------------------------------------
147-- Listener
148-----------------------------------------------------------------------}
149
150handleQuery :: MonadKRPC m => KQuery -> SockAddr -> m ()
151handleQuery q addr = do
152 Manager {..} <- getManager
153 res <- dispatchHandler q addr
154 sendMessage sock addr $ either toBEncode toBEncode res
155
156handleResponse :: MonadKRPC m => KResult -> SockAddr -> m ()
157handleResponse result addr = do
158 Manager {..} <- getManager
159 mcall <- undefined (addr, respId) pendingCalls
160 case mcall of
161 Nothing -> return ()
162 Just ares -> liftIO $ putMVar ares result
163
164handleMessage :: MonadKRPC m => KMessage -> SockAddr -> m ()
165handleMessage (Q q) = handleQuery q
166handleMessage (R r) = handleResponse (Right r)
167handleMessage (E e) = handleResponse (Left e)
168
169maxMsgSize :: Int
170maxMsgSize = 64 * 1024
171
172listener :: MonadKRPC m => m ()
173listener = do
174 Manager {..} <- getManager
175 forever $ do
176 (bs, addr) <- liftIO $ BS.recvFrom sock maxMsgSize
177 case BE.decode bs of
178 Left e -> liftIO $ sendMessage sock addr $ unknownMessage e
179 Right m -> handleMessage m addr
diff --git a/src/Network/KRPC/Message.hs b/src/Network/KRPC/Message.hs
index a70c2ea9..3bbfb1db 100644
--- a/src/Network/KRPC/Message.hs
+++ b/src/Network/KRPC/Message.hs
@@ -19,10 +19,17 @@
19{-# LANGUAGE DefaultSignatures #-} 19{-# LANGUAGE DefaultSignatures #-}
20{-# LANGUAGE DeriveDataTypeable #-} 20{-# LANGUAGE DeriveDataTypeable #-}
21module Network.KRPC.Message 21module Network.KRPC.Message
22 ( -- * Error 22 ( -- * Transaction
23 ErrorCode (..) 23 TransactionId
24 , unknownTransaction
25
26 -- * Error
27 , ErrorCode (..)
24 , KError(..) 28 , KError(..)
25 , serverError 29 , serverError
30 , decodeError
31 , unknownMethod
32 , unknownMessage
26 33
27 -- * Query 34 -- * Query
28 , KQuery(..) 35 , KQuery(..)
@@ -30,6 +37,9 @@ module Network.KRPC.Message
30 37
31 -- * Response 38 -- * Response
32 , KResponse(..) 39 , KResponse(..)
40
41 -- * Message
42 , KMessage (..)
33 ) where 43 ) where
34 44
35import Control.Applicative 45import Control.Applicative
@@ -47,6 +57,9 @@ import Data.Typeable
47-- are enough as they cover 2^16 outstanding queries. 57-- are enough as they cover 2^16 outstanding queries.
48type TransactionId = ByteString 58type TransactionId = ByteString
49 59
60unknownTransaction :: TransactionId
61unknownTransaction = ""
62
50{----------------------------------------------------------------------- 63{-----------------------------------------------------------------------
51-- Error messages 64-- Error messages
52-----------------------------------------------------------------------} 65-----------------------------------------------------------------------}
@@ -120,6 +133,15 @@ instance Exception KError
120serverError :: SomeException -> TransactionId -> KError 133serverError :: SomeException -> TransactionId -> KError
121serverError e = KError ServerError (BC.pack (show e)) 134serverError e = KError ServerError (BC.pack (show e))
122 135
136decodeError :: String -> TransactionId -> KError
137decodeError msg = KError ProtocolError (BC.pack msg)
138
139unknownMethod :: MethodName -> TransactionId -> KError
140unknownMethod = KError MethodUnknown
141
142unknownMessage :: String -> KError
143unknownMessage msg = KError ProtocolError (BC.pack msg) ""
144
123{----------------------------------------------------------------------- 145{-----------------------------------------------------------------------
124-- Query messages 146-- Query messages
125-----------------------------------------------------------------------} 147-----------------------------------------------------------------------}
@@ -183,4 +205,23 @@ instance BEncode KResponse where
183 fromBEncode = fromDict $ do 205 fromBEncode = fromDict $ do
184 lookAhead $ match "y" (BString "r") 206 lookAhead $ match "y" (BString "r")
185 KResponse <$>! "r" <*>! "t" 207 KResponse <$>! "r" <*>! "t"
186 {-# INLINE fromBEncode #-} \ No newline at end of file 208 {-# INLINE fromBEncode #-}
209
210{-----------------------------------------------------------------------
211-- Summed messages
212-----------------------------------------------------------------------}
213
214data KMessage
215 = Q KQuery
216 | R KResponse
217 | E KError
218
219instance BEncode KMessage where
220 toBEncode (Q q) = toBEncode q
221 toBEncode (R r) = toBEncode r
222 toBEncode (E e) = toBEncode e
223
224 fromBEncode b =
225 Q <$> fromBEncode b
226 <|> R <$> fromBEncode b
227 <|> E <$> fromBEncode b
diff --git a/src/Network/KRPC/Method.hs b/src/Network/KRPC/Method.hs
new file mode 100644
index 00000000..54aa8ef0
--- /dev/null
+++ b/src/Network/KRPC/Method.hs
@@ -0,0 +1,61 @@
1{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE MultiParamTypeClasses #-}
3{-# LANGUAGE FunctionalDependencies #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6module Network.KRPC.Method
7 ( Method (..)
8 , KRPC (..)
9 ) where
10
11import Data.BEncode (BEncode)
12import Data.Monoid
13import Data.String
14import Data.Typeable
15import Network.KRPC.Message
16
17
18-- | Method datatype used to describe name, parameters and return
19-- values of procedure. Client use a method to /invoke/, server
20-- /implements/ the method to make the actual work.
21--
22-- We use the following fantom types to ensure type-safiety:
23--
24-- * param: Type of method parameters. Ordinary Tuple type used
25-- to specify more than one parameter, so for example @Method
26-- (Int, Int) result@ will take two arguments.
27--
28-- * result: Type of return value of the method. Similarly,
29-- tuple used to specify more than one return value, so for
30-- exsample @Method (Foo, Bar) (Bar, Foo)@ will take two arguments
31-- and return two values.
32--
33newtype Method param result = Method MethodName
34 deriving (Eq, Ord, IsString, BEncode)
35
36instance (Typeable a, Typeable b) => Show (Method a b) where
37 showsPrec _ = showsMethod
38
39showsMethod :: forall a. forall b. Typeable a => Typeable b
40 => Method a b -> ShowS
41showsMethod (Method name) =
42 shows name <>
43 showString " :: " <>
44 shows paramsTy <>
45 showString " -> " <>
46 shows valuesTy
47 where
48 impossible = error "KRPC.showsMethod: impossible"
49 paramsTy = typeOf (impossible :: a)
50 valuesTy = typeOf (impossible :: b)
51
52-- | Example:
53-- @
54-- data Ping = Ping Text deriving BEncode
55-- data Pong = Pong Text deriving BEncode
56--
57-- instance KRPC Ping Pong where
58-- method = "ping"
59-- @
60class (BEncode req, BEncode resp) => KRPC req resp | req -> resp where
61 method :: Method req resp