diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Network/KRPC.hs | 217 | ||||
-rw-r--r-- | src/Network/KRPC/Protocol.hs | 28 |
2 files changed, 55 insertions, 190 deletions
diff --git a/src/Network/KRPC.hs b/src/Network/KRPC.hs index f891d5a0..d295a965 100644 --- a/src/Network/KRPC.hs +++ b/src/Network/KRPC.hs | |||
@@ -93,45 +93,45 @@ | |||
93 | {-# LANGUAGE ExplicitForAll #-} | 93 | {-# LANGUAGE ExplicitForAll #-} |
94 | {-# LANGUAGE KindSignatures #-} | 94 | {-# LANGUAGE KindSignatures #-} |
95 | {-# LANGUAGE ScopedTypeVariables #-} | 95 | {-# LANGUAGE ScopedTypeVariables #-} |
96 | {-# LANGUAGE DeriveGeneric #-} | 96 | {-# LANGUAGE MultiParamTypeClasses #-} |
97 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
98 | {-# LANGUAGE FunctionalDependencies #-} | ||
97 | module Network.KRPC | 99 | module Network.KRPC |
98 | ( -- * Method | 100 | ( KRPC (..) |
99 | Method(..) | 101 | |
100 | , method | 102 | -- * Exception |
101 | , idM | 103 | , KError (..) |
104 | |||
105 | -- * Method | ||
106 | , Method(..) | ||
102 | 107 | ||
103 | -- * Client | 108 | -- * Client |
104 | , call | 109 | , call |
105 | 110 | ||
106 | -- * Server | 111 | -- * Server |
107 | , MethodHandler | 112 | , MethodHandler |
108 | , (==>) | 113 | , handler |
109 | , (==>@) | ||
110 | , server | 114 | , server |
111 | |||
112 | -- * Internal | ||
113 | , call_ | ||
114 | , withRemote | ||
115 | ) where | 115 | ) where |
116 | 116 | ||
117 | import Control.Applicative | ||
118 | import Control.Exception | 117 | import Control.Exception |
119 | import Control.Monad.Trans.Control | 118 | import Control.Monad.Trans.Control |
120 | import Control.Monad.IO.Class | 119 | import Control.Monad.IO.Class |
121 | import Data.BEncode as BE | 120 | import Data.BEncode as BE |
122 | import Data.BEncode.BDict as BE | ||
123 | import Data.BEncode.Types as BE | ||
124 | import Data.ByteString.Char8 as BC | 121 | import Data.ByteString.Char8 as BC |
125 | import Data.List as L | 122 | import Data.List as L |
126 | import Data.Monoid | 123 | import Data.Monoid |
124 | import Data.String | ||
127 | import Data.Typeable | 125 | import Data.Typeable |
128 | import Network | 126 | import Network |
129 | import Network.Socket | 127 | import Network.Socket |
130 | import GHC.Generics | ||
131 | 128 | ||
132 | import Network.KRPC.Protocol | 129 | import Network.KRPC.Protocol |
133 | 130 | ||
134 | 131 | ||
132 | class (BEncode req, BEncode resp) => KRPC req resp | req -> resp where | ||
133 | method :: Method req resp | ||
134 | |||
135 | -- | Method datatype used to describe name, parameters and return | 135 | -- | Method datatype used to describe name, parameters and return |
136 | -- values of procedure. Client use a method to /invoke/, server | 136 | -- values of procedure. Client use a method to /invoke/, server |
137 | -- /implements/ the method to make the actual work. | 137 | -- /implements/ the method to make the actual work. |
@@ -147,187 +147,62 @@ import Network.KRPC.Protocol | |||
147 | -- exsample @Method (Foo, Bar) (Bar, Foo)@ will take two arguments | 147 | -- exsample @Method (Foo, Bar) (Bar, Foo)@ will take two arguments |
148 | -- and return two values. | 148 | -- and return two values. |
149 | -- | 149 | -- |
150 | -- To pass raw dictionaries you should specify empty param list: | 150 | newtype Method param result = Method MethodName |
151 | -- | 151 | deriving (Eq, Ord, IsString, BEncode) |
152 | -- > method "my_method" [] [] :: Method BEncode BEncode | ||
153 | -- | ||
154 | -- In this case you should handle dictionary extraction by hand, both | ||
155 | -- in client and server. | ||
156 | -- | ||
157 | data Method param result = Method { | ||
158 | -- | Name used in query. | ||
159 | methodName :: MethodName | ||
160 | |||
161 | -- | Name of each parameter in /right to left/ order. | ||
162 | , methodParams :: [ParamName] | ||
163 | |||
164 | -- | Name of each return value in /right to left/ order. | ||
165 | , methodVals :: [ValName] | ||
166 | } deriving (Eq, Ord, Generic) | ||
167 | |||
168 | instance BEncode (Method a b) | ||
169 | 152 | ||
170 | instance (Typeable a, Typeable b) => Show (Method a b) where | 153 | instance (Typeable a, Typeable b) => Show (Method a b) where |
171 | showsPrec _ = showsMethod | 154 | showsPrec _ = showsMethod |
172 | 155 | ||
173 | showsMethod | 156 | showsMethod :: forall a. forall b. Typeable a => Typeable b |
174 | :: forall a. forall b. | 157 | => Method a b -> ShowS |
175 | Typeable a => Typeable b | 158 | showsMethod (Method name) = |
176 | => Method a b -> ShowS | 159 | shows name <> |
177 | showsMethod Method {..} = | ||
178 | showString (BC.unpack methodName) <> | ||
179 | showString " :: " <> | 160 | showString " :: " <> |
180 | showsTuple methodParams paramsTy <> | 161 | shows paramsTy <> |
181 | showString " -> " <> | 162 | showString " -> " <> |
182 | showsTuple methodVals valuesTy | 163 | shows valuesTy |
183 | where | ||
184 | paramsTy = typeOf (error "KRPC.showsMethod: impossible" :: a) | ||
185 | valuesTy = typeOf (error "KRPC.showsMethod: impossible" :: b) | ||
186 | |||
187 | showsTuple ns ty | ||
188 | = showChar '(' | ||
189 | <> mconcat (L.intersperse (showString ", ") $ | ||
190 | L.zipWith showsTyArgName ns (detuple ty)) | ||
191 | <> showChar ')' | ||
192 | |||
193 | showsTyArgName ns ty | ||
194 | = showString (BC.unpack ns) | ||
195 | <> showString " :: " | ||
196 | <> showString (show ty) | ||
197 | |||
198 | detuple tyRep | ||
199 | | L.null args = [tyRep] | ||
200 | | otherwise = args | ||
201 | where | ||
202 | args = typeRepArgs tyRep | ||
203 | |||
204 | |||
205 | -- | Identity procedure signature. Could be used for echo | ||
206 | -- servers. Implemented as: | ||
207 | -- | ||
208 | -- > idM = method "id" ["x"] ["y"] | ||
209 | -- | ||
210 | idM :: Method a a | ||
211 | idM = method "id" ["x"] ["y"] | ||
212 | {-# INLINE idM #-} | ||
213 | |||
214 | -- | Makes method signature. Note that order of parameters and return | ||
215 | -- values are not important as long as corresponding names and types | ||
216 | -- are match. For exsample this is the equal definitions: | ||
217 | -- | ||
218 | -- > methodA : Method (Foo, Bar) (Baz, Quux) | ||
219 | -- > methodA = method "mymethod" ["a", "b"] ["c", "d"] | ||
220 | -- | ||
221 | -- > methodA : Method (Bar, Foo) (Quux, Baz) | ||
222 | -- > methodB = method "mymethod" ["b", "a"] ["d", "c"] | ||
223 | -- | ||
224 | method :: MethodName -> [ParamName] -> [ValName] -> Method param result | ||
225 | method = Method | ||
226 | {-# INLINE method #-} | ||
227 | |||
228 | lookupKey :: ParamName -> BDict -> Result BValue | ||
229 | lookupKey x = maybe (Left ("not found key " ++ BC.unpack x)) Right . BE.lookup x | ||
230 | |||
231 | extractArgs :: [ParamName] -> BDict -> Result BValue | ||
232 | extractArgs [] d = Right $ if BE.null d then BList [] else BDict d | ||
233 | extractArgs [x] d = lookupKey x d | ||
234 | extractArgs xs d = BList <$> mapM (`lookupKey` d) xs | ||
235 | {-# INLINE extractArgs #-} | ||
236 | |||
237 | zipBDict :: [BKey] -> [BValue] -> BDict | ||
238 | zipBDict (k : ks) (v : vs) = Cons k v (zipBDict ks vs) | ||
239 | zipBDict _ _ = Nil | ||
240 | |||
241 | injectVals :: [ParamName] -> BValue -> BDict | ||
242 | injectVals [] (BList []) = BE.empty | ||
243 | injectVals [] (BDict d ) = d | ||
244 | injectVals [] be = invalidParamList [] be | ||
245 | injectVals [p] arg = BE.singleton p arg | ||
246 | injectVals ps (BList as) = zipBDict ps as | ||
247 | injectVals ps be = invalidParamList ps be | ||
248 | {-# INLINE injectVals #-} | ||
249 | |||
250 | invalidParamList :: [ParamName] -> BValue -> a | ||
251 | invalidParamList pl be | ||
252 | = error $ "KRPC invalid parameter list: " ++ show pl ++ "\n" ++ | ||
253 | "while procedure args are: " ++ show be | ||
254 | |||
255 | queryCall :: BEncode param => Socket -> SockAddr | ||
256 | -> Method param result -> param -> IO () | ||
257 | queryCall sock addr m arg = sendMessage q addr sock | ||
258 | where | 164 | where |
259 | q = kquery (methodName m) (injectVals (methodParams m) (toBEncode arg)) | 165 | impossible = error "KRPC.showsMethod: impossible" |
166 | paramsTy = typeOf (impossible :: a) | ||
167 | valuesTy = typeOf (impossible :: b) | ||
260 | 168 | ||
261 | getResult :: BEncode result => Socket -> Method param result -> IO result | ||
262 | getResult sock m = do | ||
263 | resp <- recvResponse sock | ||
264 | case resp of | ||
265 | Left e -> throw e | ||
266 | Right (respVals -> dict) -> do | ||
267 | case fromBEncode =<< extractArgs (methodVals m) dict of | ||
268 | Right vals -> return vals | ||
269 | Left e -> throw (ProtocolError (BC.pack e)) | ||
270 | 169 | ||
170 | getResult :: BEncode result => Socket -> IO result | ||
171 | getResult sock = do | ||
172 | resp <- either throw (return . respVals) =<< recvResponse sock | ||
173 | either (throw . ProtocolError . BC.pack) return $ fromBEncode resp | ||
271 | 174 | ||
272 | -- | Makes remote procedure call. Throws RPCException on any error | 175 | -- | Makes remote procedure call. Throws RPCException on any error |
273 | -- occurred. | 176 | -- occurred. |
274 | call :: (MonadBaseControl IO host, MonadIO host) | 177 | call :: forall req resp host. |
275 | => (BEncode param, BEncode result) | 178 | (MonadBaseControl IO host, MonadIO host, KRPC req resp) |
276 | => SockAddr -- ^ Address of callee. | 179 | => SockAddr -> req -> host resp |
277 | -> Method param result -- ^ Procedure to call. | 180 | call addr arg = liftIO $ withRemote $ \sock -> do |
278 | -> param -- ^ Arguments passed by callee to procedure. | 181 | sendMessage (KQuery name (toBEncode arg)) addr sock |
279 | -> host result -- ^ Values returned by callee from the procedure. | 182 | getResult sock |
280 | call addr m arg = liftIO $ withRemote $ \sock -> do call_ sock addr m arg | 183 | where |
281 | 184 | Method name = method :: Method req resp | |
282 | -- | The same as 'call' but use already opened socket. | ||
283 | call_ :: (MonadBaseControl IO host, MonadIO host) | ||
284 | => (BEncode param, BEncode result) | ||
285 | => Socket -- ^ Socket to use | ||
286 | -> SockAddr -- ^ Address of callee. | ||
287 | -> Method param result -- ^ Procedure to call. | ||
288 | -> param -- ^ Arguments passed by callee to procedure. | ||
289 | -> host result -- ^ Values returned by callee from the procedure. | ||
290 | call_ sock addr m arg = liftIO $ do | ||
291 | queryCall sock addr m arg | ||
292 | getResult sock m | ||
293 | |||
294 | 185 | ||
295 | type HandlerBody remote = SockAddr -> KQuery -> remote (Either KError KResponse) | 186 | type HandlerBody remote = SockAddr -> KQuery -> remote (Either KError KResponse) |
296 | 187 | ||
297 | -- | Procedure signature and implementation binded up. | 188 | -- | Procedure signature and implementation binded up. |
298 | type MethodHandler remote = (MethodName, HandlerBody remote) | 189 | type MethodHandler remote = (MethodName, HandlerBody remote) |
299 | 190 | ||
300 | -- we can safely erase types in (==>) | ||
301 | -- | Assign method implementation to the method signature. | ||
302 | (==>) :: forall (remote :: * -> *) (param :: *) (result :: *). | ||
303 | (BEncode param, BEncode result) | ||
304 | => Monad remote | ||
305 | => Method param result -- ^ Signature. | ||
306 | -> (param -> remote result) -- ^ Implementation. | ||
307 | -> MethodHandler remote -- ^ Handler used by server. | ||
308 | {-# INLINE (==>) #-} | ||
309 | m ==> body = m ==>@ const body | ||
310 | infix 1 ==> | ||
311 | |||
312 | -- | Similar to '==>@' but additionally pass caller address. | 191 | -- | Similar to '==>@' but additionally pass caller address. |
313 | (==>@) :: forall (remote :: * -> *) (param :: *) (result :: *). | 192 | handler :: forall (remote :: * -> *) (req :: *) (resp :: *). |
314 | (BEncode param, BEncode result) | 193 | (KRPC req resp, Monad remote) |
315 | => Monad remote | 194 | => (SockAddr -> req -> remote resp) -> MethodHandler remote |
316 | => Method param result -- ^ Signature. | 195 | handler body = (name, newbody) |
317 | -> (SockAddr -> param -> remote result) -- ^ Implementation. | ||
318 | -> MethodHandler remote -- ^ Handler used by server. | ||
319 | {-# INLINE (==>@) #-} | ||
320 | m ==>@ body = (methodName m, newbody) | ||
321 | where | 196 | where |
197 | Method name = method :: Method req resp | ||
198 | |||
322 | {-# INLINE newbody #-} | 199 | {-# INLINE newbody #-} |
323 | newbody addr q = | 200 | newbody addr q = |
324 | case fromBEncode =<< extractArgs (methodParams m) (queryArgs q) of | 201 | case fromBEncode (queryArgs q) of |
325 | Left e -> return (Left (ProtocolError (BC.pack e))) | 202 | Left e -> return (Left (ProtocolError (BC.pack e))) |
326 | Right a -> do | 203 | Right a -> do |
327 | r <- body addr a | 204 | r <- body addr a |
328 | return (Right (kresponse (injectVals (methodVals m) (toBEncode r)))) | 205 | return (Right (KResponse (toBEncode r))) |
329 | |||
330 | infix 1 ==>@ | ||
331 | 206 | ||
332 | -- | Run RPC server on specified port by using list of handlers. | 207 | -- | Run RPC server on specified port by using list of handlers. |
333 | -- Server will dispatch procedure specified by callee, but note that | 208 | -- Server will dispatch procedure specified by callee, but note that |
diff --git a/src/Network/KRPC/Protocol.hs b/src/Network/KRPC/Protocol.hs index adc02b5f..5b072d79 100644 --- a/src/Network/KRPC/Protocol.hs +++ b/src/Network/KRPC/Protocol.hs | |||
@@ -26,15 +26,13 @@ module Network.KRPC.Protocol | |||
26 | , mkKError | 26 | , mkKError |
27 | 27 | ||
28 | -- * Query | 28 | -- * Query |
29 | , KQuery(queryMethod, queryArgs) | 29 | , KQuery(..) |
30 | , MethodName | 30 | , MethodName |
31 | , ParamName | 31 | , ParamName |
32 | , kquery | ||
33 | 32 | ||
34 | -- * Response | 33 | -- * Response |
35 | , KResponse(respVals) | 34 | , KResponse(..) |
36 | , ValName | 35 | , ValName |
37 | , kresponse | ||
38 | 36 | ||
39 | , sendMessage | 37 | , sendMessage |
40 | , recvResponse | 38 | , recvResponse |
@@ -134,16 +132,16 @@ type ParamName = ByteString | |||
134 | -- | 132 | -- |
135 | -- > { "y" : "q", "q" : "<method_name>", "a" : [<arg1>, <arg2>, ...] } | 133 | -- > { "y" : "q", "q" : "<method_name>", "a" : [<arg1>, <arg2>, ...] } |
136 | -- | 134 | -- |
137 | data KQuery = KQuery { | 135 | data KQuery = KQuery |
138 | queryMethod :: !MethodName | 136 | { queryMethod :: !MethodName |
139 | , queryArgs :: BDict | 137 | , queryArgs :: !BValue |
140 | } deriving (Show, Read, Eq, Ord, Typeable) | 138 | } deriving (Show, Read, Eq, Ord, Typeable) |
141 | 139 | ||
142 | instance BEncode KQuery where | 140 | instance BEncode KQuery where |
143 | {-# SPECIALIZE instance BEncode KQuery #-} | 141 | {-# SPECIALIZE instance BEncode KQuery #-} |
144 | {-# INLINE toBEncode #-} | 142 | {-# INLINE toBEncode #-} |
145 | toBEncode (KQuery m args) = toDict $ | 143 | toBEncode (KQuery m args) = toDict $ |
146 | "a" .=! BDict args | 144 | "a" .=! args |
147 | .: "q" .=! m | 145 | .: "q" .=! m |
148 | .: "y" .=! ("q" :: ByteString) | 146 | .: "y" .=! ("q" :: ByteString) |
149 | .: endDict | 147 | .: endDict |
@@ -157,11 +155,6 @@ instance BEncode KQuery where | |||
157 | 155 | ||
158 | fromBEncode _ = decodingError "KQuery" | 156 | fromBEncode _ = decodingError "KQuery" |
159 | 157 | ||
160 | kquery :: MethodName -> BDict -> KQuery | ||
161 | kquery = KQuery | ||
162 | {-# INLINE kquery #-} | ||
163 | |||
164 | |||
165 | type ValName = ByteString | 158 | type ValName = ByteString |
166 | 159 | ||
167 | -- | KResponse used to signal that callee successufully process a | 160 | -- | KResponse used to signal that callee successufully process a |
@@ -173,8 +166,9 @@ type ValName = ByteString | |||
173 | -- | 166 | -- |
174 | -- > { "y" : "r", "r" : [<val1>, <val2>, ...] } | 167 | -- > { "y" : "r", "r" : [<val1>, <val2>, ...] } |
175 | -- | 168 | -- |
176 | newtype KResponse = KResponse { respVals :: BDict } | 169 | newtype KResponse = KResponse |
177 | deriving (Show, Read, Eq, Ord, Typeable) | 170 | { respVals :: BValue |
171 | } deriving (Show, Read, Eq, Ord, Typeable) | ||
178 | 172 | ||
179 | instance BEncode KResponse where | 173 | instance BEncode KResponse where |
180 | {-# INLINE toBEncode #-} | 174 | {-# INLINE toBEncode #-} |
@@ -190,10 +184,6 @@ instance BEncode KResponse where | |||
190 | 184 | ||
191 | fromBEncode _ = decodingError "KDict" | 185 | fromBEncode _ = decodingError "KDict" |
192 | 186 | ||
193 | kresponse :: BDict -> KResponse | ||
194 | kresponse = KResponse | ||
195 | {-# INLINE kresponse #-} | ||
196 | |||
197 | sockAddrFamily :: SockAddr -> Family | 187 | sockAddrFamily :: SockAddr -> Family |
198 | sockAddrFamily (SockAddrInet _ _ ) = AF_INET | 188 | sockAddrFamily (SockAddrInet _ _ ) = AF_INET |
199 | sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6 | 189 | sockAddrFamily (SockAddrInet6 _ _ _ _) = AF_INET6 |