예제 #1
0
 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)
예제 #2
0
 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')
예제 #3
0
 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})')
예제 #4
0
 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)
예제 #5
0
 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)
예제 #6
0
    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)