def send_later(self, route: str, headers: dict = None, body: any = None, reply_to: str = None, me=True, seconds: float = 1.0) -> None: self.util.validate_service_name(route, True) if isinstance(seconds, float) or isinstance(seconds, int): if seconds > 0: if headers is None and body is None: raise ValueError( 'Unable to send because both headers and body are missing' ) event = EventEnvelope().set_to(route) if headers is not None: if not isinstance(headers, dict): raise ValueError('headers must be dict') for h in headers: event.set_header(str(h), str(headers[h])) if body is not None: event.set_body(body) if reply_to is not None: if not isinstance(reply_to, str): raise ValueError('reply_to must be str') # encode 'me' in the "call back" if replying to this instance event.set_reply_to(reply_to, me) self.platform.send_event_later(event, seconds) else: raise ValueError('delay in seconds must be larger than zero') else: raise ValueError('delay in seconds must be int or float')
def exists(self, routes: any): if isinstance(routes, str): single_route = routes if self.has_route(single_route): return True if self.cloud_ready(): event = EventEnvelope() event.set_to(self.SERVICE_QUERY).set_header( 'type', 'find').set_header('route', single_route) result = self.request(event, 8.0) if isinstance(result, EventEnvelope): if result.get_body() is not None: return result.get_body() if isinstance(routes, list): if len(routes) > 0: remote_routes = list() for r in routes: if not self.platform.has_route(r): remote_routes.append(r) if len(remote_routes) == 0: return True if self.platform.cloud_ready(): # tell service query to use the route list in body event = EventEnvelope() event.set_to(self.SERVICE_QUERY).set_header('type', 'find') event.set_header('route', '*').set_body(routes) result = self.request(event, 8.0) if isinstance( result, EventEnvelope) and result.get_body() is not None: return result.get_body() return False
def request(self, route: str, timeout_seconds: float, headers: dict = None, body: any = None, correlation_id: str = None) -> EventEnvelope: self.util.validate_service_name(route, True) if headers is None and body is None: raise ValueError( 'Unable to make RPC call because both headers and body are missing' ) timeout_value = self.util.get_float(timeout_seconds) if timeout_value <= 0: raise ValueError( "timeout value in seconds must be positive number") event = EventEnvelope().set_to(route) if headers is not None: if not isinstance(headers, dict): raise ValueError('headers must be dict') for h in headers: event.set_header(h, str(headers[h])) if body is not None: event.set_body(body) if correlation_id is not None: event.set_correlation_id(str(correlation_id)) return self.platform.request(event, timeout_seconds)
def send_payload(self, data: dict): payload = msgpack.packb(data, use_bin_type=True) payload_len = len(payload) if 'type' in data and data[ 'type'] == 'event' and 'event' in data and payload_len > self.max_ws_payload: evt = data['event'] if 'id' in evt: msg_id = evt['id'] total = int(payload_len / self.max_ws_payload) if payload_len > total: total += 1 buffer = io.BytesIO(payload) count = 0 for i in range(total): count += 1 block = EventEnvelope() block.set_header('id', msg_id) block.set_header('count', count) block.set_header('total', total) block.set_body(buffer.read(self.max_ws_payload)) block_map = dict() block_map['type'] = 'block' block_map['block'] = block.to_map() block_payload = msgpack.packb(block_map, use_bin_type=True) envelope = EventEnvelope() envelope.set_to(self.OUTGOING_WS_PATH).set_header( 'type', 'bytes').set_body(block_payload) self.platform.send_event(envelope) else: envelope = EventEnvelope() envelope.set_to(self.OUTGOING_WS_PATH).set_header( 'type', 'bytes').set_body(payload) self.platform.send_event(envelope)
def request(self, route: str, timeout_seconds: float, headers: dict = None, body: any = None, correlation_id: str = None) -> EventEnvelope: self.util.validate_service_name(route, True) if headers is None and body is None: raise ValueError( 'Unable to make RPC call because both headers and body are missing' ) timeout_value = self.util.get_float(timeout_seconds) if timeout_value <= 0: raise ValueError( 'timeout value in seconds must be positive number') event = EventEnvelope().set_to(route) if headers is not None: if not isinstance(headers, dict): raise ValueError('headers must be dict') for h in headers: event.set_header(h, str(headers[h])) if body is not None: event.set_body(body) if correlation_id is not None: event.set_correlation_id(str(correlation_id)) response = self.platform.request(event, timeout_seconds) if isinstance(response, EventEnvelope): if response.get_tag('exception') is None: return response else: raise AppException(response.get_status(), response.get_body()) raise ValueError( f'Expect response is EventEnvelope, actual: ({response})')
def hello(headers: dict, body: any, instance: int): # regular function signature (headers: dict, body: any, instance: int) Platform().log.info("#"+str(instance)+" "+str(headers)+" body="+str(body)) # to set status, headers and body, return them in an event envelope result = EventEnvelope().set_header('hello', 'world').set_body(body) for h in headers: result.set_header(h, headers[h]) return result
def hello(headers: dict, body: any, instance: int): # regular function signature (headers: dict, body: any, instance: int) log.info(f'#{instance} {headers} body={body}') # to set status, headers and body, return them in an event envelope result = EventEnvelope().set_header('hello', 'world').set_body(body) for h in headers: result.set_header(h, headers[h]) return result
def broadcast(self, route: str, headers: dict = None, body: any = None) -> None: self.util.validate_service_name(route) if headers is None and body is None: raise ValueError('Unable to broadcast because both headers and body are missing') event = EventEnvelope().set_to(route) if headers is not None: if not isinstance(headers, dict): raise ValueError('headers must be dict') for h in headers: event.set_header(h, str(headers[h])) if body is not None: event.set_body(body) self.platform.send_event(event, True)
def logger(self, event: EventEnvelope): if isinstance(event, EventEnvelope): self.log.info('trace=' + str(event.get_headers()) + ', annotations=' + str(event.get_body())) # forward to user provided distributed trace logger if any current_time = time.time() if self._dt_last_check is None or current_time - self._dt_last_check > 5.0: self._dt_last_check = current_time self._dt_found = self.platform.exists(self._dt_processor) if self._dt_found: te = EventEnvelope() te.set_to(self._dt_processor).set_body(event.get_body()) for h in event.get_headers(): te.set_header(h, event.get_header(h)) self.platform.send_event(te)
def logger(self, event: EventEnvelope): if isinstance(event, EventEnvelope): self.log.info( f'trace={event.get_headers()}, annotations={event.get_body()}') if self.platform.is_trace_supported(): # forward to user provided distributed trace logger if any current_time = time.time() if self._dt_last_check is None or current_time - self._dt_last_check > 5.0: self._dt_last_check = current_time self._dt_found = self.platform.exists(self._dt_processor) if self._dt_found: trace_event = EventEnvelope() trace_event.set_to(self._dt_processor).set_body( {'annotations': event.get_body()}) for h in event.get_headers(): trace_event.set_header(h, event.get_header(h)) self.platform.send_event(trace_event)
def handle_event(self, event, instance): headers = dict() if 'headers' not in event else event['headers'] body = None if 'body' not in event else event['body'] result = None error_code = None error_msg = None # start distributed tracing if the event contains trace_id and trace_path if 'trace_id' in event and 'trace_path' in event: self.platform.start_tracing(self.route, trace_id=event['trace_id'], trace_path=event['trace_path']) else: self.platform.start_tracing(self.route) # execute user function begin = end = time.time() try: if instance == 0: # service is an interceptor. e.g. inbox for RPC call self.user_function(EventEnvelope().from_map(event)) elif instance == -1: # service is a singleton result = self.user_function(headers, body) else: # service with multiple instances result = self.user_function(headers, body, instance) end = time.time() except AppException as e: error_code = e.get_status() error_msg = e.get_message() except ValueError as e: error_code = 400 error_msg = str(e) except Exception as e: error_code = 500 error_msg = str(e) # execution time is rounded to 3 decimal points exec_time = float(format((end - begin) * 1000, '.3f')) if error_code: if 'reply_to' in event: # set exception as result result = EventEnvelope().set_status(error_code).set_body( error_msg) else: self.log.warn("Unhandled exception for " + self.route + " - code=" + str(error_code) + ", message=" + error_msg) # # interceptor should not send regular response because it will forward the request to another function. # However, if error_code exists, the system will send the exception response. # This allows interceptor to simply throw exception to indicate an error case. # if 'reply_to' in event and (error_code or not self.interceptor): reply_to = event['reply_to'] # in case this is a RPC call from within if reply_to.startswith('->'): reply_to = reply_to[2:] response = EventEnvelope().set_to(reply_to) if not error_code: response.set_exec_time(exec_time, False) if 'extra' in event: response.set_extra(event['extra']) if 'cid' in event: response.set_correlation_id(event['cid']) if 'trace_id' in event and 'trace_path' in event: response.set_trace(event['trace_id'], event['trace_path']) if isinstance(result, EventEnvelope): for h in result.get_headers(): response.set_header(h, result.get_header(h)) response.set_body(result.get_body()) response.set_status(result.get_status()) else: response.set_body(result) try: self.platform.send_event(response.set_from(self.route)) except Exception as e: self.log.warn("Event dropped because " + str(e)) # send tracing info to distributed trace logger trace_info = self.platform.stop_tracing() if self.tracing and trace_info is not None and isinstance(trace_info, TraceInfo) \ and trace_info.get_id() is not None and trace_info.get_path() is not None: dt = EventEnvelope().set_to(self.DISTRIBUTED_TRACING).set_body( trace_info.get_annotations()) dt.set_header('origin', self.platform.get_origin()) dt.set_header('id', trace_info.get_id()).set_header( 'path', trace_info.get_path()) dt.set_header('service', self.route).set_header('start', trace_info.get_start_time()) if not error_code: dt.set_header('success', 'true') dt.set_header('exec_time', exec_time) else: dt.set_header('success', 'false') dt.set_header('status', error_code) dt.set_header('exception', error_msg) self.platform.send_event(dt) self._loop.call_soon_threadsafe(self._ack)