@@ -64,6 +64,7 @@ def __init__(self, data=None):
6464 super ().__init__ (0 , "Unknown error" , data )
6565
6666Request = namedtuple ("Request" , ["method" , "params" , "id" ], defaults = [{}, None ])
67+ Response = namedtuple ("Response" , ["id" , "result" , "error" ], defaults = [None , {}, {}])
6768Method = namedtuple ("Method" , ["callback" , "signature" , "immediate" , "sensitive_params" ])
6869
6970
@@ -79,7 +80,7 @@ def anonymise_sensitive_params(params, sensitive_params):
7980
8081 return params
8182
82- class Server ():
83+ class Connection ():
8384 def __init__ (self , reader , writer , encoder = json .JSONEncoder ()):
8485 self ._active = True
8586 self ._reader = StreamLineReader (reader )
@@ -89,6 +90,8 @@ def __init__(self, reader, writer, encoder=json.JSONEncoder()):
8990 self ._notifications = {}
9091 self ._task_manager = TaskManager ("jsonrpc server" )
9192 self ._write_lock = asyncio .Lock ()
93+ self ._last_request_id = 0
94+ self ._requests_futures = {}
9295
9396 def register_method (self , name , callback , immediate , sensitive_params = False ):
9497 """
@@ -114,6 +117,47 @@ def register_notification(self, name, callback, immediate, sensitive_params=Fals
114117 """
115118 self ._notifications [name ] = Method (callback , inspect .signature (callback ), immediate , sensitive_params )
116119
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+
117161 async def run (self ):
118162 while self ._active :
119163 try :
@@ -143,15 +187,40 @@ def _eof(self):
143187
144188 def _handle_input (self , data ):
145189 try :
146- request = self ._parse_request (data )
190+ message = self ._parse_message (data )
147191 except JsonRpcError as error :
148192 self ._send_error (None , error )
149193 return
150194
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 )
155224
156225 def _handle_notification (self , request ):
157226 method = self ._notifications .get (request .method )
@@ -211,13 +280,17 @@ async def handle():
211280 self ._task_manager .create_task (handle (), request .method )
212281
213282 @staticmethod
214- def _parse_request (data ):
283+ def _parse_message (data ):
215284 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" :
218287 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+
221294 except json .JSONDecodeError :
222295 raise ParseError ()
223296 except TypeError :
@@ -254,58 +327,39 @@ def _send_error(self, request_id, error):
254327
255328 self ._send (response )
256329
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 )
276338
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 ):
282340 notification = {
283341 "jsonrpc" : "2.0" ,
284342 "method" : method ,
285343 "params" : params
286344 }
287- self ._log (method , params , sensitive_params )
288345 self ._send (notification )
289346
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 )
299354
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 )
307359
308360 @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