@@ -30,7 +30,7 @@ class RemoteError(Exception):
30
30
31
31
class IncompatibleServer (Exception ):
32
32
"""Raised by the client when attempting to connect to a server that does
33
- not have the expected type .
33
+ not have the expected target .
34
34
35
35
"""
36
36
pass
@@ -62,59 +62,71 @@ class Client:
62
62
hostname or a IPv4 or IPv6 address (see
63
63
``socket.create_connection`` in the Python standard library).
64
64
:param port: TCP port to use.
65
- :param expected_id_type: Server type to expect. ``IncompatibleServer`` is
66
- raised when the types do not match. Use ``None`` to accept any server
67
- type.
65
+ :param target_name: Target name to select. ``IncompatibleServer`` is
66
+ raised if the target does not exist.
67
+ Use ``None`` to skip selecting a target. The list of targets can then
68
+ be retrieved using ``get_rpc_id`` and then one can be selected later
69
+ using ``select_rpc_target``.
68
70
69
71
"""
70
- def __init__ (self , host , port , expected_id_type ):
71
- self .socket = socket .create_connection ((host , port ))
72
- self .socket .sendall (_init_string )
73
- self ._identify (expected_id_type )
72
+ def __init__ (self , host , port , target_name ):
73
+ self ._socket = socket .create_connection ((host , port ))
74
+ self ._socket .sendall (_init_string )
75
+
76
+ server_identification = self ._recv ()
77
+ self ._target_names = server_identification ["targets" ]
78
+ self ._id_parameters = server_identification ["parameters" ]
79
+ if target_name is not None :
80
+ self .select_rpc_target (target_name )
81
+
82
+ def select_rpc_target (self , target_name ):
83
+ """Selects a RPC target by name. This function should be called
84
+ exactly once if the object was created with ``target_name=None``.
85
+
86
+ """
87
+ if target_name not in self ._target_names :
88
+ raise IncompatibleServer
89
+ self ._socket .sendall ((target_name + "\n " ).encode ())
74
90
75
91
def get_rpc_id (self ):
76
- """Returns a dictionary containing the identification information of
77
- the server.
92
+ """Returns a tuple (target_names, id_parameters) containing the
93
+ identification information of the server.
78
94
79
95
"""
80
- return self ._server_identification
96
+ return ( self ._target_names , self . _id_parameters )
81
97
82
98
def close_rpc (self ):
83
99
"""Closes the connection to the RPC server.
84
100
85
101
No further method calls should be done after this method is called.
86
102
87
103
"""
88
- self .socket .close ()
104
+ self ._socket .close ()
89
105
90
- def _send_recv (self , obj ):
106
+ def _send (self , obj ):
91
107
line = pyon .encode (obj ) + "\n "
92
- self .socket .sendall (line .encode ())
108
+ self ._socket .sendall (line .encode ())
93
109
94
- buf = self .socket .recv (4096 ).decode ()
110
+ def _recv (self ):
111
+ buf = self ._socket .recv (4096 ).decode ()
95
112
while "\n " not in buf :
96
- more = self .socket .recv (4096 )
113
+ more = self ._socket .recv (4096 )
97
114
if not more :
98
115
break
99
116
buf += more .decode ()
100
117
obj = pyon .decode (buf )
101
118
102
119
return obj
103
120
104
- def _identify (self , expected_id_type ):
105
- obj = {"action" : "identify" }
106
- self ._server_identification = self ._send_recv (obj )
107
- if (expected_id_type is not None
108
- and self ._server_identification ["type" ] != expected_id_type ):
109
- raise IncompatibleServer
110
-
111
121
def _do_rpc (self , name , args , kwargs ):
112
122
obj = {"action" : "call" , "name" : name , "args" : args , "kwargs" : kwargs }
113
- obj = self ._send_recv (obj )
114
- if obj ["result" ] == "ok" :
123
+ self ._send (obj )
124
+
125
+ obj = self ._recv ()
126
+ if obj ["status" ] == "ok" :
115
127
return obj ["ret" ]
116
- elif obj ["result " ] == "error " :
117
- raise RemoteError (obj ["message" ] + " \n " + obj [ "traceback" ] )
128
+ elif obj ["status " ] == "failed " :
129
+ raise RemoteError (obj ["message" ])
118
130
else :
119
131
raise ValueError
120
132
@@ -134,18 +146,16 @@ class Server(AsyncioServer):
134
146
simple cases: it allows new connections to be be accepted even when the
135
147
previous client failed to properly shut down its connection.
136
148
137
- :param target: Object providing the RPC methods to be exposed to the
138
- client.
139
- :param id_type: A string identifying the server type. Clients use it to
140
- verify that they are connected to the proper server.
149
+ :param targets: A dictionary of objects providing the RPC methods to be
150
+ exposed to the client. Keys are names identifying each object.
151
+ Clients select one of these objects using its name upon connection.
141
152
:param id_parameters: An optional human-readable string giving more
142
153
information about the parameters of the server.
143
154
144
155
"""
145
- def __init__ (self , target , id_type , id_parameters = None ):
156
+ def __init__ (self , targets , id_parameters = None ):
146
157
AsyncioServer .__init__ (self )
147
- self .target = target
148
- self .id_type = id_type
158
+ self .targets = targets
149
159
self .id_parameters = id_parameters
150
160
151
161
@asyncio .coroutine
@@ -154,42 +164,49 @@ def _handle_connection_cr(self, reader, writer):
154
164
line = yield from reader .readline ()
155
165
if line != _init_string :
156
166
return
167
+
168
+ obj = {
169
+ "targets" : sorted (self .targets .keys ()),
170
+ "parameters" : self .id_parameters
171
+ }
172
+ line = pyon .encode (obj ) + "\n "
173
+ writer .write (line .encode ())
174
+ line = yield from reader .readline ()
175
+ if not line :
176
+ return
177
+ target_name = line .decode ()[:- 1 ]
178
+ try :
179
+ target = self .targets [target_name ]
180
+ except KeyError :
181
+ return
182
+
157
183
while True :
158
184
line = yield from reader .readline ()
159
185
if not line :
160
186
break
161
187
obj = pyon .decode (line .decode ())
162
- action = obj ["action" ]
163
- if action == "call" :
164
- try :
165
- method = getattr (self .target , obj ["name" ])
166
- ret = method (* obj ["args" ], ** obj ["kwargs" ])
167
- obj = {"result" : "ok" , "ret" : ret }
168
- except Exception as e :
169
- obj = {"result" : "error" ,
170
- "message" : type (e ).__name__ + ": " + str (e ),
171
- "traceback" : traceback .format_exc ()}
172
- line = pyon .encode (obj ) + "\n "
173
- writer .write (line .encode ())
174
- elif action == "identify" :
175
- obj = {"type" : self .id_type }
176
- if self .id_parameters is not None :
177
- obj ["parameters" ] = self .id_parameters
178
- line = pyon .encode (obj ) + "\n "
179
- writer .write (line .encode ())
188
+ try :
189
+ method = getattr (target , obj ["name" ])
190
+ ret = method (* obj ["args" ], ** obj ["kwargs" ])
191
+ obj = {"status" : "ok" , "ret" : ret }
192
+ except Exception :
193
+ obj = {"status" : "failed" ,
194
+ "message" : traceback .format_exc ()}
195
+ line = pyon .encode (obj ) + "\n "
196
+ writer .write (line .encode ())
180
197
finally :
181
198
writer .close ()
182
199
183
200
184
- def simple_server_loop (target , id_type , host , port , id_parameters = None ):
201
+ def simple_server_loop (targets , host , port , id_parameters = None ):
185
202
"""Runs a server until an exception is raised (e.g. the user hits Ctrl-C).
186
203
187
204
See ``Server`` for a description of the parameters.
188
205
189
206
"""
190
207
loop = asyncio .get_event_loop ()
191
208
try :
192
- server = Server (target , id_type , id_parameters )
209
+ server = Server (targets , id_parameters )
193
210
loop .run_until_complete (server .start (host , port ))
194
211
try :
195
212
loop .run_forever ()
0 commit comments