def test_repr(self): p = BoltResponse(SofaHeader(a='1', b='2'), b"cdefgab", ptype=PTYPE.ONEWAY, request_id=0, cmdcode=CMDCODE.HEARTBEAT, respstatus=1) print(p) s = p.to_stream() pr = BoltResponse.from_stream(s) self.assertNotEqual(id(p), id(pr)) self.assertEqual(p.header, pr.header) self.assertEqual(p.content, pr.content) self.assertEqual(p.cmdcode, pr.cmdcode) self.assertEqual(p.request_id, pr.request_id) print(pr) p = BoltRequest(SofaHeader(a='1', b='2'), b"jklmnhi", ptype=PTYPE.ONEWAY, request_id=0, cmdcode=CMDCODE.HEARTBEAT, timeout=-1) print(p) s = p.to_stream() pr = BoltRequest.from_stream(s) self.assertNotEqual(id(p), id(pr)) self.assertEqual(p.header, pr.header) self.assertEqual(p.content, pr.content) self.assertEqual(p.cmdcode, pr.cmdcode) self.assertEqual(p.request_id, pr.request_id) print(pr)
def handle(self): try: fixed_header_bs = self._readexactly(BoltRequest.bolt_header_size()) header = BoltRequest.bolt_header_from_stream(fixed_header_bs) call_type = header['ptype'] cmdcode = header['cmdcode'] class_name = self._readexactly(header['class_len']) bs = self._readexactly(header['header_len']) sofa_header = SofaHeader.from_bytes(bs) body = self._readexactly(header['content_len']) if self.server.ready: self.server.ready.set() request_id = header['request_id'] if cmdcode == CMDCODE.HEARTBEAT: self.wfile.write(HeartbeatResponse.response_to(request_id).to_stream()) self.wfile.flush() return if cmdcode == CMDCODE.RESPONSE: raise ClientError("wrong cmdcode:[{}]".format(cmdcode)) if class_name != "com.alipay.sofa.rpc.core.request.SofaRequest".encode(): raise ClientError("wrong class_name:[{}]".format(class_name)) service = sofa_header.get('sofa_head_target_service') or sofa_header.get('service') if not service: self.wfile.write(FailResponse.response_to(request_id, RESPSTATUS.CLIENT_SEND_ERROR).to_stream()) self.wfile.flush() logger.error("Missing service name in sofa header [{}]".format(sofa_header)) return method = sofa_header.get('sofa_head_method_name') if not method: self.wfile.write(FailResponse.response_to(request_id, RESPSTATUS.CLIENT_SEND_ERROR).to_stream()) self.wfile.flush() logger.error("Missing method name in sofa header [{}]".format(sofa_header)) return spanctx = tracer.extract(opentracing.Format.TEXT_MAP, sofa_header) ret = self.handle_request(spanctx, service, method, body) self.wfile.write(ret) self.wfile.flush() except OSError as e: if e.errno != ECONNRESET: raise except Exception as e: logger.error(traceback.format_exc())
def invoke_async(self, interface, method, content, *, spanctx, callback=None, timeout_ms=None, **headers): """ call callback if callback is a callable, otherwise return a future Callback should recv a bytes object as the only argument, which is the response pkg's content """ header = SofaHeader.build_header(spanctx, interface, method, **headers) pkg = BoltRequest.new_request(header, content, timeout_ms=timeout_ms or -1) fut = asyncio.run_coroutine_threadsafe(self.invoke(pkg), loop=self._loop) if callable(callback): fut.add_done_callback( self.callback_wrapper( callback, timeout_ms / 1000 if timeout_ms else None)) return fut return fut
def _raw_invoke(self, interface, method_name, content, spanctx=None, target_app="", uid="", timeout_ms=None, bolt_ptype=PTYPE.REQUEST, **sofa_headers_extra): """ :param content: :param service: :param target_app: :param uid: :param rpc_trace_context: preserved, rpc_trace_context object, should be expanded as a dict like {'rpc_trace_context.traceId': 'xxxxx', ...} :param kwargs: """ # FIXME spanctx might be None here logger.debug("Calling interface {}, spanctx: {}".format(interface, spanctx.baggage)) header = SofaHeader.build_header(spanctx, interface, method_name, target_app=target_app, uid=uid, **sofa_headers_extra) p = BoltRequest.new_request(header, content, timeout_ms=timeout_ms or -1, ptype=bolt_ptype) for i in range(3): # try three times, to avoid connection failure try: conn = self._get_pool(interface).get_conn() conn.send(p.to_stream()) except SocketValueError as e: logger.info("Call to interface {} failed, requiest_id: {}, " "retried: {}, reason: {}".format(interface, p.request_id, i, e)) continue except Exception as e: logger.error("Call to interface {} failed, requiest_id: {}, " "retried: {}, reason: {}".format(interface, p.request_id, i, e)) break else: break logger.debug("Called interface {}, request_id: {}".format(interface, p.request_id)) return p.request_id, conn
def _raw_invoke(self, interface, method_name, content, spanctx=None, target_app="", uid="", timeout_ms=None, bolt_ptype=PTYPE.REQUEST, **sofa_headers_extra): """ :param content: :param service: :param target_app: :param uid: :param rpc_trace_context: preserved, rpc_trace_context object, should be expanded as a dict like {'rpc_trace_context.traceId': 'xxxxx', ...} :param kwargs: """ header = SofaHeader.build_header(spanctx, interface, method_name, target_app=target_app, uid=uid, **sofa_headers_extra) p = BoltRequest.new_request(header, content, timeout_ms=timeout_ms or -1, ptype=bolt_ptype) conn = self._get_pool(interface).get_conn() conn.send(p.to_stream()) return p.request_id, conn
def invoke_oneway(self, interface, method, content, *, spanctx, **headers): header = SofaHeader.build_header(spanctx, interface, method, **headers) pkg = BoltRequest.new_request( header, content, timeout_ms=-1, codec=self._get_serialize_protocol(interface)) asyncio.run_coroutine_threadsafe(self.invoke(pkg), loop=self._loop)
def invoke_sync(self, interface, method, content, *, spanctx, timeout_ms, **headers): """blocking call to interface, returns responsepkg.content(as bytes)""" assert isinstance(timeout_ms, (int, float)) header = SofaHeader.build_header(spanctx, interface, method, **headers) pkg = BoltRequest.new_request(header, content, timeout_ms=timeout_ms) fut = asyncio.run_coroutine_threadsafe(self.invoke(pkg), loop=self._loop) try: ret = fut.result(timeout=timeout_ms / 1000) except (TimeoutError, CancelledError) as e: logger.error("call to [{}:{}] timeout/cancelled. {}".format(interface, method, e)) raise return ret.content
def test_from_stream(self): bs = b'\x01\x00\x00\x02\x01\x00\x00\x00\x84\x0b\x00\x00\x00\x2e\x00\x00\x00\x00\x00\x03\x63\x6f\x6d\x2e\x61\x6c\x69\x70\x61\x79\x2e\x73\x6f\x66\x61\x2e\x72\x70\x63\x2e\x63\x6f\x72\x65\x2e\x72\x65\x73\x70\x6f\x6e\x73\x65\x2e\x53\x6f\x66\x61\x52\x65\x73\x70\x6f\x6e\x73\x65\x0a\x01\x61' p = BoltResponse.from_stream(bs) self.assertEqual(p.body_len, len(bs) - p.bolt_header_size()) print(p.content) re = SampleServicePbResult_pb2.SampleServicePbResult() re.ParseFromString(p.content) # print(p) print(re.result) self.assertEqual(re.result, 'a') bs = b'\x01\x01\x00\x01\x01\x00\x00\x00\x6d\x0b\x00\x00\x00\x64\x00\x2c\x02\xe6\x00\x00\x00\x03\x63\x6f\x6d\x2e\x61\x6c\x69\x70\x61\x79\x2e\x73\x6f\x66\x61\x2e\x72\x70\x63\x2e\x63\x6f\x72\x65\x2e\x72\x65\x71\x75\x65\x73\x74\x2e\x53\x6f\x66\x61\x52\x65\x71\x75\x65\x73\x74\x00\x00\x00\x18\x73\x6f\x66\x61\x5f\x68\x65\x61\x64\x5f\x74\x61\x72\x67\x65\x74\x5f\x73\x65\x72\x76\x69\x63\x65\x00\x00\x00\x3b\x63\x6f\x6d\x2e\x61\x6c\x69\x70\x61\x79\x2e\x72\x70\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x73\x65\x72\x76\x69\x63\x65\x2e\x66\x61\x63\x61\x64\x65\x2e\x70\x62\x2e\x53\x61\x6d\x70\x6c\x65\x53\x65\x72\x76\x69\x63\x65\x50\x62\x3a\x31\x2e\x30\x00\x00\x00\x1b\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x52\x70\x63\x49\x64\x00\x00\x00\x01\x30\x00\x00\x00\x1f\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x7a\x70\x72\x6f\x78\x79\x54\x69\x6d\x65\x6f\x75\x74\x00\x00\x00\x03\x31\x30\x30\x00\x00\x00\x1d\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x54\x72\x61\x63\x65\x49\x64\x00\x00\x00\x1e\x30\x62\x61\x36\x31\x36\x61\x31\x31\x35\x32\x33\x32\x35\x31\x38\x31\x39\x31\x31\x34\x31\x30\x30\x32\x31\x38\x37\x33\x38\x00\x00\x00\x1f\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x43\x61\x6c\x6c\x65\x72\x49\x64\x63\x00\x00\x00\x03\x64\x65\x76\x00\x00\x00\x1e\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x43\x61\x6c\x6c\x65\x72\x49\x70\x00\x00\x00\x0d\x31\x31\x2e\x31\x36\x36\x2e\x32\x32\x2e\x31\x36\x31\x00\x00\x00\x1e\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x50\x65\x6e\x41\x74\x74\x72\x73\x00\x00\x00\x00\x00\x00\x00\x1b\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x7a\x70\x72\x6f\x78\x79\x55\x49\x44\x00\x00\x00\x00\x00\x00\x00\x20\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x43\x61\x6c\x6c\x65\x72\x5a\x6f\x6e\x65\x00\x00\x00\x05\x47\x5a\x30\x30\x42\x00\x00\x00\x14\x73\x6f\x66\x61\x5f\x68\x65\x61\x64\x5f\x74\x61\x72\x67\x65\x74\x5f\x61\x70\x70\x00\x00\x00\x04\x62\x61\x72\x31\x00\x00\x00\x07\x73\x65\x72\x76\x69\x63\x65\x00\x00\x00\x3b\x63\x6f\x6d\x2e\x61\x6c\x69\x70\x61\x79\x2e\x72\x70\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x73\x65\x72\x76\x69\x63\x65\x2e\x66\x61\x63\x61\x64\x65\x2e\x70\x62\x2e\x53\x61\x6d\x70\x6c\x65\x53\x65\x72\x76\x69\x63\x65\x50\x62\x3a\x31\x2e\x30\x00\x00\x00\x19\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x45\x6c\x61\x73\x74\x69\x63\x00\x00\x00\x01\x46\x00\x00\x00\x1d\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x79\x73\x50\x65\x6e\x41\x74\x74\x72\x73\x00\x00\x00\x00\x00\x00\x00\x22\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x7a\x70\x72\x6f\x78\x79\x54\x61\x72\x67\x65\x74\x5a\x6f\x6e\x65\x00\x00\x00\x00\x00\x00\x00\x1f\x72\x70\x63\x5f\x74\x72\x61\x63\x65\x5f\x63\x6f\x6e\x74\x65\x78\x74\x2e\x73\x6f\x66\x61\x43\x61\x6c\x6c\x65\x72\x41\x70\x70\x00\x00\x00\x03\x66\x6f\x6f\x00\x00\x00\x15\x73\x6f\x66\x61\x5f\x68\x65\x61\x64\x5f\x6d\x65\x74\x68\x6f\x64\x5f\x6e\x61\x6d\x65\x00\x00\x00\x05\x68\x65\x6c\x6c\x6f\x0a\x01\x61' p = BoltRequest.from_stream(bs) print(p.header) re = SampleServicePbRequest_pb2.SampleServicePbRequest() re.ParseFromString(p.content) print(re) self.assertEqual(re.name, "a")
async def _handler_connection(self, reader, writer): """ Full duplex model Only recv request here, run a new coro to process and send back response in same connection. """ logger.info("connection created.") first_req = True while True: try: try: fixed_header_bs = await reader.readexactly( BoltRequest.bolt_header_size()) except asyncio.IncompleteReadError: if first_req: # just connected, do nothing. most likely L4 health checks from mosn/upstream await self._close_writer(writer) break # break the loop raise first_req = False logger.debug( "received bolt header({})".format(fixed_header_bs)) header = BoltRequest.bolt_header_from_stream(fixed_header_bs) call_type = header['ptype'] cmdcode = header['cmdcode'] class_name = await reader.readexactly(header['class_len']) logger.debug("received classname({})".format(class_name)) bs = await reader.readexactly(header['header_len']) logger.debug("received sofa header({})".format(bs)) sofa_header = SofaHeader.from_bytes(bs) body = await reader.readexactly(header['content_len']) logger.debug("received sofa body({})".format(body)) if cmdcode == CMDCODE.HEARTBEAT: logger.info("received heartbeat, request_id={}".format( header['request_id'])) asyncio.ensure_future( self._write_msg( writer, HeartbeatResponse.response_to( header['request_id']).to_stream())) continue if cmdcode == CMDCODE.RESPONSE: raise ClientError("wrong cmdcode:[{}]".format(cmdcode)) if class_name != "com.alipay.sofa.rpc.core.request.SofaRequest".encode( ): raise ClientError( "wrong class_name:[{}]".format(class_name)) logger.debug("dispatching request[{}]".format( header['request_id'])) asyncio.ensure_future( self._dispatch(call_type, header['request_id'], sofa_header, body, timeout_ms=header['timeout'], codec=header['codec'], writer=writer)) except ClientError as e: logger.error(str(e) + " returning CLIENT_SEND_ERROR") await self._write_msg( writer, FailResponse.response_to( header['request_id'], RESPSTATUS.CLIENT_SEND_ERROR).to_stream()) continue except ProtocolError as e: logger.error(str(e) + " returning CODEC_EXCEPTION") await self._write_msg( writer, FailResponse.response_to( header['request_id'], RESPSTATUS.CODEC_EXCEPTION).to_stream()) continue except EOFError as e: logger.warning("Connection closed by remote: {}".format(e)) try: await writer.drain( ) # clean the buffer, avoid backpressure writer.write( FailResponse.response_to( header['request_id'], RESPSTATUS.CONNECTION_CLOSED).to_stream()) except: pass await self._close_writer(writer) break except Exception: logger.error("Unknow exception, close connection") logger.error(traceback.format_exc()) try: await writer.drain( ) # clean the buffer, avoid backpressure writer.write( FailResponse.response_to( header['request_id'], RESPSTATUS.UNKNOWN).to_stream()) except: pass await self._close_writer(writer) break
def _handler_connection(self, reader, writer): """ Full duplex model Only recv request here, run a new coro to process and send back response in same connection. """ first_req = True while True: try: try: fixed_header_bs = yield from reader.readexactly( BoltRequest.bolt_header_size()) except asyncio.streams.IncompleteReadError: if first_req: raise first_req = False header = BoltRequest.bolt_header_from_stream(fixed_header_bs) call_type = header['ptype'] cmdcode = header['cmdcode'] class_name = yield from reader.readexactly(header['class_len']) bs = yield from reader.readexactly(header['header_len']) sofa_header = SofaHeader.from_bytes(bs) body = yield from reader.readexactly(header['content_len']) if cmdcode == CMDCODE.HEARTBEAT: asyncio.ensure_future( self._write_msg( writer, HeartbeatResponse.response_to( header['request_id']).to_stream())) continue if cmdcode == CMDCODE.RESPONSE: raise ClientError("wrong cmdcode:[{}]".format(cmdcode)) if class_name != "com.alipay.sofa.rpc.core.request.SofaRequest".encode( ): raise ClientError( "wrong class_name:[{}]".format(class_name)) asyncio.ensure_future( self._dispatch(call_type, header['request_id'], sofa_header, body, timeout_ms=header['timeout'], writer=writer)) except ClientError as e: logger.error(str(e)) yield from self._write_msg( writer, FailResponse.response_to( header['request_id'], RESPSTATUS.CLIENT_SEND_ERROR).to_stream()) continue except ProtocolError as e: logger.error(str(e)) yield from self._write_msg( writer, FailResponse.response_to( header['request_id'], RESPSTATUS.CODEC_EXCEPTION).to_stream()) continue except EOFError as e: logger.info("Connection closed: {}".format(e)) try: yield from writer.drain( ) # clean the buffer, avoid backpressure writer.write( FailResponse.response_to( header['request_id'], RESPSTATUS.CONNECTION_CLOSED).to_stream()) except: pass try: writer.write_eof() yield from writer.drain() except: pass writer.close() break except Exception: logger.error(traceback.format_exc()) try: yield from writer.drain( ) # clean the buffer, avoid backpressure writer.write( FailResponse.response_to( header['request_id'], RESPSTATUS.UNKNOWN).to_stream()) except: pass try: writer.write_eof() yield from writer.drain() except: pass writer.close() break