async def serve(reader, writer): """Serve function. Don't use this outside asyncio.start_server. """ global _unpack_encoding, _unpack_params _logger.debug('enter serve: {}'.format(writer.get_extra_info('peername'))) conn = Connection(reader, writer, msgpack.Unpacker(encoding=_unpack_encoding, **_unpack_params)) while not conn.is_closed(): req = None try: req = await conn.recvall() except (RPCIOError, ConnectionError) as ie: print(f'server io error') break except Exception as e: print(f'unknow exception') import traceback traceback.print_exc() conn.reader.set_exception(e) raise e if not isinstance(req, tuple): try: await _send_error(conn, "Invalid protocol", -1) # skip the rest of iteration code after sending error continue except Exception as e: _logger.error("Error when receiving req: {}".format(str(e))) asyncio.create_task(handle_request(conn, req))
async def _open_connection(self): _logger.debug("connect to %s:%s...", *self.getpeername()) if self._host: reader, writer = await asyncio.open_connection(self._host, self._port, loop=self._loop) else: reader, writer = await asyncio.open_unix_connection(self._path, loop=self._loop) self._conn = Connection(reader, writer, msgpack.Unpacker(raw=False, **self._unpack_params)) _logger.debug("Connection to %s:%s established", *self.getpeername())
async def _open_connection(self): _logger.debug("connect to {}:{}...".format(self._host, self._port)) reader, writer = await asyncio.open_connection(self._host, self._port, loop=self._loop) self._conn = Connection( reader, writer, msgpack.Unpacker(encoding=self._unpack_encoding, **self._unpack_params)) _logger.debug("Connection to {}:{} established".format( self._host, self._port))
async def serve(reader, writer): """Serve function. Don't use this outside asyncio.start_server. """ global _unpack_encoding, _unpack_params _logger.debug('enter serve: {}'.format(writer.get_extra_info('peername'))) conn = Connection( reader, writer, msgpack.Unpacker(encoding=_unpack_encoding, **_unpack_params)) while not conn.is_closed(): req = None try: req = await conn.recvall(_timeout) except asyncio.TimeoutError as te: conn.reader.set_exception(te) except IOError as ie: break except Exception as e: conn.reader.set_exception(e) raise e if type(req) != tuple: try: await _send_error(conn, "Invalid protocol", -1) except Exception as e: _logger.error("Error when receiving req: {}".format(str(e))) return method = None msg_id = None args = None try: _logger.debug('parsing req: {}'.format(str(req))) (msg_id, method, args) = _parse_request(req) _logger.debug('parsing completed: {}'.format(str(req))) except Exception as e: _logger.error("Exception {} raised when _parse_request {}".format( str(e), req)) try: _logger.debug('calling method: {}'.format(str(method))) ret = method.__call__(*args) if asyncio.iscoroutine(ret): _logger.debug("start to wait_for") ret = await asyncio.wait_for(ret, _timeout) _logger.debug('calling {} completed. result: {}'.format( str(method), str(ret))) except Exception as e: await _send_error(conn, str(e), msg_id) else: _logger.debug('sending result: {}'.format(str(ret))) await _send_result(conn, ret, msg_id) _logger.debug('sending result {} completed'.format(str(ret)))
async def call(self, method, *args): """Calls a RPC method. :param str method: Method name. :param args: Method arguments. """ _logger.debug('creating request') req = self._create_request(method, args) if self._conn is None: _logger.debug("connect to {}:{}...".format(self._host, self._port)) reader, writer = await asyncio.open_connection(self._host, self._port, loop=self._loop) self._conn = Connection( reader, writer, msgpack.Unpacker(encoding=self._unpack_encoding, **self._unpack_params)) _logger.debug("Connection to {}:{} established".format( self._host, self._port)) try: _logger.debug('Sending req: {}'.format(req)) await self._conn.sendall(req, self._timeout) _logger.debug('Sending complete') except asyncio.TimeoutError as te: _logger.error("Write request to {}:{} timeout".format( self._host, self._port)) raise te except Exception as e: raise e response = None try: _logger.debug('receiving result from server') response = await self._conn.recvall(self._timeout) _logger.debug('receiving result completed') except asyncio.TimeoutError as te: _logger.error("Read request to {}:{} timeout".format( self._host, self._port)) self._conn.reader.set_exception(te) raise te except Exception as e: self._conn.reader.set_exception(e) raise e if response is None: raise IOError("Connection closed") if type(response) != tuple: logging.debug( 'Protocol error, received unexpected data: {}'.format( response)) raise RPCProtocolError('Invalid protocol') return self._parse_response(response)
class RPCClient: """RPC client. Usage: >>> from aiorpc.client import RPCClient >>> client = RPCClient('127.0.0.1', 6000) >>> import asyncio >>> loop = asyncio.get_event_loop() >>> loop.run_until_complete(client.call('sum', 1, 2)) :param str host: Hostname. :param int port: Port number. :param int path: Unix socket path. Either this one or host and port are required. :param int timeout: (optional) Socket timeout. :param str pack_encoding: (optional) Character encoding used to pack data using Messagepack. :param str unpack_encoding: (optional) Character encoding used to unpack data using Messagepack. :param dict pack_params: (optional) Parameters to pass to Messagepack Packer :param dict unpack_params: (optional) Parameters to pass to Messagepack Unpacker. """ def __init__(self, host=None, port=None, path=None, timeout=3, loop=None, pack_encoding='utf-8', unpack_encoding='utf-8', pack_params=None, unpack_params=None): self._host = host self._port = port self._path = path self._timeout = timeout self._loop = loop self._conn = None self._msg_id = 0 self._pack_encoding = pack_encoding self._pack_params = pack_params or dict() self._unpack_encoding = unpack_encoding self._unpack_params = unpack_params or dict(use_list=False) def getpeername(self): """Return the address of the remote endpoint.""" return (self._host, self._port) if self._host else ('unix', self._path) def close(self): try: self._conn.close() except AttributeError: pass async def _open_connection(self): _logger.debug("connect to %s:%s...", *self.getpeername()) if self._host: reader, writer = await asyncio.open_connection(self._host, self._port, loop=self._loop) else: reader, writer = await asyncio.open_unix_connection( self._path, loop=self._loop) self._conn = Connection( reader, writer, msgpack.Unpacker(encoding=self._unpack_encoding, **self._unpack_params)) _logger.debug("Connection to %s:%s established", *self.getpeername()) async def call(self, method, *args, _close=False): """Calls a RPC method. :param str method: Method name. :param args: Method arguments. :param _close: Close the connection at the end of the request. Defaults to false """ _logger.debug('creating request') req = self._create_request(method, args) if self._conn is None or self._conn.is_closed(): await self._open_connection() try: _logger.debug('Sending req: %s', req) await self._conn.sendall(req, self._timeout) _logger.debug('Sending complete') except asyncio.TimeoutError as te: _logger.error("Write request to %s:%s timeout", *self.getpeername()) raise te except Exception as e: raise e response = None try: _logger.debug('receiving result from server') response = await self._conn.recvall(self._timeout) _logger.debug('receiving result completed') except asyncio.TimeoutError as te: _logger.error("Read request to %s:%s timeout", *self.getpeername()) self._conn.reader.set_exception(te) raise te except Exception as e: self._conn.reader.set_exception(e) raise e if response is None: raise IOError("Connection closed") if not isinstance(response, tuple): logging.debug('Protocol error, received unexpected data: %r', response) raise RPCProtocolError('Invalid protocol') if _close: self.close() return self._parse_response(response) async def call_once(self, method, *args): """Call an RPC Method, then close the connection :param str method: Method name. :param args: Method arguments. :param _close: Close the connection at the end of the request. Defaults to false """ return await self.call(method, *args, _close=True) def _create_request(self, method, args): self._msg_id += 1 req = (MSGPACKRPC_REQUEST, self._msg_id, method, args) return msgpack.packb(req, encoding=self._pack_encoding, **self._pack_params) def _parse_response(self, response): if (len(response) != 4 or response[0] != MSGPACKRPC_RESPONSE): raise RPCProtocolError('Invalid protocol') (_, msg_id, error, result) = response if msg_id != self._msg_id: raise RPCError('Invalid Message ID') if error and len(error) == 2: if error[0] == 'MethodNotFoundError': raise MethodNotFoundError(error[1]) raise EnhancedRPCError(*error) elif error: raise RPCError(error) return result async def __aenter__(self): await self._open_connection() return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self._conn and not self._conn.is_closed(): logging.debug('Closing connection from context manager') self.close()
async def serve(reader, writer): """Serve function. Don't use this outside asyncio.start_server. """ global _unpack_encoding, _unpack_params _logger.debug('enter serve: %s', writer.get_extra_info('peername')) conn = Connection( reader, writer, msgpack.Unpacker(encoding=_unpack_encoding, **_unpack_params)) while not conn.is_closed(): req = None try: req = await conn.recvall(_timeout) except asyncio.TimeoutError: await asyncio.sleep(3) _logger.warning( "Client did not send any data before timeout. Closing connection..." ) conn.close() continue except IOError: break except Exception as e: conn.reader.set_exception(e) raise e if not isinstance(req, (tuple, list)): try: await _send_error(conn, "Invalid protocol", -1, None) # skip the rest of iteration code after sending error continue except Exception as e: _logger.error("Error when receiving req: %s", e) req_start = time.time() method = None msg_id = None args = None try: _logger.debug('parsing req: %s', req) msg_id, method, args, method_name = _parse_request(req) _logger.debug('parsing completed: %s', req) except Exception as e: _logger.exception("Exception raised in _parse_request(%s)", req) # skip the rest of iteration code since we already got an error continue # Execute the parsed request try: if not method: raise MethodNotFoundError(method_name) _logger.debug('calling method: %s', method) ret = method.__call__(*args) if asyncio.iscoroutine(ret): _logger.debug("start to wait_for") ret = await asyncio.wait_for(ret, _timeout) _logger.debug('calling %s completed. result: %s', method, ret) except Exception as e: _logger.exception("Caught Exception in `%s`", method_name) await _send_error(conn, type(e).__name__, str(e), msg_id) _logger.debug('sending exception %s completed', e) else: _logger.debug('sending result: %s', ret) await _send_result(conn, ret, msg_id) _logger.debug('sending result %s completed', ret) req_end = time.time() _logger.info("Method `%s` took %fms", method_name, (req_end - req_start) * 1000)