-
Notifications
You must be signed in to change notification settings - Fork 78
Expand file tree
/
Copy pathServer.hs
More file actions
169 lines (149 loc) · 5.26 KB
/
Server.hs
File metadata and controls
169 lines (149 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-------------------------------------------------------------------
-- |
-- Module : Network.MessagePackRpc.Server
-- Copyright : (c) Hideyuki Tanaka, 2010-2015
-- 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.MessagePack.Server
-- >
-- > add :: Int -> Int -> Server Int
-- > add x y = return $ x + y
-- >
-- > main = serve 1234 [ method "add" add ]
--
--------------------------------------------------------------------
module Network.MessagePack.Server (
-- * RPC method types
Method, MethodType(..),
ServerT(..), Server,
-- * Build a method
method,
-- * Start RPC server
serve,
serveUnix,
-- * RPC server settings
ServerSettings,
serverSettings,
U.ServerSettingsUnix,
-- * Getters & setters
SN.serverSettingsUnix,
SN.getReadBufferSize,
SN.setReadBufferSize,
getAfterBind,
setAfterBind,
getPort,
setPort,
) where
import Conduit (MonadUnliftIO)
import Control.Applicative
import Control.Monad
import Control.Monad.Catch
import Control.Monad.Trans
import Control.Monad.Trans.Control
import Data.Binary
import Data.ByteString (ByteString)
import Data.Conduit
import qualified Data.Conduit.Binary as CB
import Data.Conduit.Network
import qualified Data.Conduit.Network.Unix as U
import Data.Conduit.Serialization.Binary
import Data.List
import Data.MessagePack
import Data.MessagePack.Result
import qualified Data.Streaming.Network as SN
import Data.Typeable
-- ^ MessagePack RPC method
data Method m
= Method
{ methodName :: String
, methodBody :: [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
newtype ServerT m a = ServerT { runServerT :: m a }
deriving (Functor, Applicative, Monad, MonadIO)
instance MonadTrans ServerT where
lift = ServerT
type Server = ServerT IO
class Monad m => MethodType m f where
-- | Create a RPC method from a Hakell function
toBody :: f -> [Object] -> m Object
instance (Functor m, MonadThrow m, MessagePack o) => MethodType m (ServerT m o) where
toBody m ls = case ls of
[] -> toObject <$> runServerT m
_ -> throwM $ ServerError "argument number error"
instance (MonadThrow m, MessagePack o, MethodType m r) => MethodType m (o -> r) where
toBody f (x: xs) =
case fromObject x of
Error e -> throwM $ ServerError e
Success r -> toBody (f r) xs
-- | Build a method
method :: MethodType m f
=> String -- ^ Method name
-> f -- ^ Method body
-> Method m
method name body = Method name $ toBody body
-- | Start an RPC server with a set of RPC methods on a TCP socket.
serve :: (MonadBaseControl IO m, MonadUnliftIO m, MonadIO m, MonadCatch m, MonadThrow m)
=> ServerSettings -- ^ settings
-> [Method m] -- ^ list of methods
-> m ()
serve settings methods = runGeneralTCPServer settings $ \ad -> do
(rsrc, _) <- appSource ad $$+ return ()
(_ :: Either ParseError ()) <- try $ processRequests methods rsrc (appSink ad)
return ()
-- | Start an RPC server with a set of RPC methods on a Unix domain socket.
serveUnix :: (MonadBaseControl IO m, MonadIO m, MonadCatch m, MonadThrow m)
=> U.ServerSettingsUnix
-> [Method m] -- ^ list of methods
-> m ()
serveUnix settings methods = liftBaseWith $ \run ->
U.runUnixServer settings $ \ad -> void . run $ do
(rsrc, _) <- appSource ad $$+ return ()
(_ :: Either ParseError ()) <- try $ processRequests methods rsrc (appSink ad)
return ()
processRequests :: (MonadThrow m)
=> [Method m] -- ^ list of methods
-> SealedConduitT () ByteString m ()
-> ConduitT ByteString Void m a
-> m b
processRequests methods rsrc sink = do
(rsrc', res) <- rsrc $$++ do
obj <- sinkGet get
case fromObject obj of
Error err -> throwM $ ServerError $ "invalid request: " ++ err
Success req -> lift $ getResponse (req :: Request)
_ <- runConduit $ CB.sourceLbs (pack res) .| sink
processRequests methods rsrc' sink
where
getResponse (rtype, msgid, methodName, args) = do
when (rtype /= 0) $
throwM $ ServerError $ "request type is not 0, got " ++ show rtype
ret <- callMethod methodName args
return ((1, msgid, toObject (), ret) :: Response)
callMethod name args =
case find ((== name) . methodName) methods of
Nothing ->
throwM $ ServerError $ "method '" ++ name ++ "' not found"
Just m ->
methodBody m args