@@ -64,6 +64,7 @@ def __init__(self, data=None):
64
64
super ().__init__ (0 , "Unknown error" , data )
65
65
66
66
Request = namedtuple ("Request" , ["method" , "params" , "id" ], defaults = [{}, None ])
67
+ Response = namedtuple ("Response" , ["id" , "result" , "error" ], defaults = [None , {}, {}])
67
68
Method = namedtuple ("Method" , ["callback" , "signature" , "immediate" , "sensitive_params" ])
68
69
69
70
@@ -79,7 +80,7 @@ def anonymise_sensitive_params(params, sensitive_params):
79
80
80
81
return params
81
82
82
- class Server ():
83
+ class Connection ():
83
84
def __init__ (self , reader , writer , encoder = json .JSONEncoder ()):
84
85
self ._active = True
85
86
self ._reader = StreamLineReader (reader )
@@ -89,6 +90,8 @@ def __init__(self, reader, writer, encoder=json.JSONEncoder()):
89
90
self ._notifications = {}
90
91
self ._task_manager = TaskManager ("jsonrpc server" )
91
92
self ._write_lock = asyncio .Lock ()
93
+ self ._last_request_id = 0
94
+ self ._requests_futures = {}
92
95
93
96
def register_method (self , name , callback , immediate , sensitive_params = False ):
94
97
"""
@@ -114,6 +117,47 @@ def register_notification(self, name, callback, immediate, sensitive_params=Fals
114
117
"""
115
118
self ._notifications [name ] = Method (callback , inspect .signature (callback ), immediate , sensitive_params )
116
119
120
+ async def send_request (self , method , params , sensitive_params ):
121
+ """
122
+ Send request
123
+
124
+ :param method:
125
+ :param params:
126
+ :param sensitive_params: list of parameters that are anonymized before logging; \
127
+ if False - no params are considered sensitive, if True - all params are considered sensitive
128
+ """
129
+ self ._last_request_id += 1
130
+ request_id = str (self ._last_request_id )
131
+
132
+ loop = asyncio .get_running_loop ()
133
+ future = loop .create_future ()
134
+ self ._requests_futures [self ._last_request_id ] = (future , sensitive_params )
135
+
136
+ logging .info (
137
+ "Sending request: id=%s, method=%s, params=%s" ,
138
+ request_id , method , anonymise_sensitive_params (params , sensitive_params )
139
+ )
140
+
141
+ self ._send_request (request_id , method , params )
142
+ return await future
143
+
144
+ def send_notification (self , method , params , sensitive_params = False ):
145
+ """
146
+ Send notification
147
+
148
+ :param method:
149
+ :param params:
150
+ :param sensitive_params: list of parameters that are anonymized before logging; \
151
+ if False - no params are considered sensitive, if True - all params are considered sensitive
152
+ """
153
+
154
+ logging .info (
155
+ "Sending notification: method=%s, params=%s" ,
156
+ method , anonymise_sensitive_params (params , sensitive_params )
157
+ )
158
+
159
+ self ._send_notification (method , params )
160
+
117
161
async def run (self ):
118
162
while self ._active :
119
163
try :
@@ -143,15 +187,40 @@ def _eof(self):
143
187
144
188
def _handle_input (self , data ):
145
189
try :
146
- request = self ._parse_request (data )
190
+ message = self ._parse_message (data )
147
191
except JsonRpcError as error :
148
192
self ._send_error (None , error )
149
193
return
150
194
151
- if request .id is not None :
152
- self ._handle_request (request )
153
- else :
154
- self ._handle_notification (request )
195
+ if isinstance (message , Request ):
196
+ if message .id is not None :
197
+ self ._handle_request (message )
198
+ else :
199
+ self ._handle_notification (message )
200
+ elif isinstance (message , Response ):
201
+ self ._handle_response (message )
202
+
203
+ def _handle_response (self , response ):
204
+ request_future = self ._requests_futures .get (int (response .id ))
205
+ if request_future is None :
206
+ response_type = "response" if response .result is not None else "error"
207
+ logging .warning ("Received %s for unknown request: %s" , response_type , response .id )
208
+ return
209
+
210
+ future , sensitive_params = request_future
211
+
212
+ if response .error :
213
+ error = JsonRpcError (
214
+ response .error .setdefault ("code" , 0 ),
215
+ response .error .setdefault ("message" , "" ),
216
+ response .error .setdefault ("data" , None )
217
+ )
218
+ self ._log_error (response , error , sensitive_params )
219
+ future .set_exception (error )
220
+ return
221
+
222
+ self ._log_response (response , sensitive_params )
223
+ future .set_result (response .result )
155
224
156
225
def _handle_notification (self , request ):
157
226
method = self ._notifications .get (request .method )
@@ -211,13 +280,17 @@ async def handle():
211
280
self ._task_manager .create_task (handle (), request .method )
212
281
213
282
@staticmethod
214
- def _parse_request (data ):
283
+ def _parse_message (data ):
215
284
try :
216
- jsonrpc_request = json .loads (data , encoding = "utf-8" )
217
- if jsonrpc_request .get ("jsonrpc" ) != "2.0" :
285
+ jsonrpc_message = json .loads (data , encoding = "utf-8" )
286
+ if jsonrpc_message .get ("jsonrpc" ) != "2.0" :
218
287
raise InvalidRequest ()
219
- del jsonrpc_request ["jsonrpc" ]
220
- return Request (** jsonrpc_request )
288
+ del jsonrpc_message ["jsonrpc" ]
289
+ if "result" in jsonrpc_message .keys () or "error" in jsonrpc_message .keys ():
290
+ return Response (** jsonrpc_message )
291
+ else :
292
+ return Request (** jsonrpc_message )
293
+
221
294
except json .JSONDecodeError :
222
295
raise ParseError ()
223
296
except TypeError :
@@ -254,58 +327,39 @@ def _send_error(self, request_id, error):
254
327
255
328
self ._send (response )
256
329
257
- @staticmethod
258
- def _log_request (request , sensitive_params ):
259
- params = anonymise_sensitive_params (request .params , sensitive_params )
260
- if request .id is not None :
261
- logging .info ("Handling request: id=%s, method=%s, params=%s" , request .id , request .method , params )
262
- else :
263
- logging .info ("Handling notification: method=%s, params=%s" , request .method , params )
264
-
265
- class NotificationClient ():
266
- def __init__ (self , writer , encoder = json .JSONEncoder ()):
267
- self ._writer = writer
268
- self ._encoder = encoder
269
- self ._methods = {}
270
- self ._task_manager = TaskManager ("notification client" )
271
- self ._write_lock = asyncio .Lock ()
272
-
273
- def notify (self , method , params , sensitive_params = False ):
274
- """
275
- Send notification
330
+ def _send_request (self , request_id , method , params ):
331
+ request = {
332
+ "jsonrpc" : "2.0" ,
333
+ "method" : method ,
334
+ "id" : request_id ,
335
+ "params" : params
336
+ }
337
+ self ._send (request )
276
338
277
- :param method:
278
- :param params:
279
- :param sensitive_params: list of parameters that are anonymized before logging; \
280
- if False - no params are considered sensitive, if True - all params are considered sensitive
281
- """
339
+ def _send_notification (self , method , params ):
282
340
notification = {
283
341
"jsonrpc" : "2.0" ,
284
342
"method" : method ,
285
343
"params" : params
286
344
}
287
- self ._log (method , params , sensitive_params )
288
345
self ._send (notification )
289
346
290
- async def close (self ):
291
- self ._task_manager .cancel ()
292
- await self ._task_manager .wait ()
293
-
294
- def _send (self , data ):
295
- async def send_task (data_ ):
296
- async with self ._write_lock :
297
- self ._writer .write (data_ )
298
- await self ._writer .drain ()
347
+ @staticmethod
348
+ def _log_request (request , sensitive_params ):
349
+ params = anonymise_sensitive_params (request .params , sensitive_params )
350
+ if request .id is not None :
351
+ logging .info ("Handling request: id=%s, method=%s, params=%s" , request .id , request .method , params )
352
+ else :
353
+ logging .info ("Handling notification: method=%s, params=%s" , request .method , params )
299
354
300
- try :
301
- line = self ._encoder .encode (data )
302
- data = (line + "\n " ).encode ("utf-8" )
303
- logging .debug ("Sending %d byte of data" , len (data ))
304
- self ._task_manager .create_task (send_task (data ), "send" )
305
- except TypeError as error :
306
- logging .error ("Failed to parse outgoing message: %s" , str (error ))
355
+ @staticmethod
356
+ def _log_response (response , sensitive_params ):
357
+ result = anonymise_sensitive_params (response .result , sensitive_params )
358
+ logging .info ("Handling response: id=%s, result=%s" , response .id , result )
307
359
308
360
@staticmethod
309
- def _log (method , params , sensitive_params ):
310
- params = anonymise_sensitive_params (params , sensitive_params )
311
- logging .info ("Sending notification: method=%s, params=%s" , method , params )
361
+ def _log_error (response , error , sensitive_params ):
362
+ data = anonymise_sensitive_params (error .data , sensitive_params )
363
+ logging .info ("Handling error: id=%s, code=%s, description=%s, data=%s" ,
364
+ response .id , error .code , error .message , data
365
+ )
0 commit comments