Example #1
0
class ThriftArgScheme(object):
    """Handler registration and serialization for Thrift.

    Use :py:func:`tchannel.thrift.load` to parse your Thrift IDL and compile
    it into a module dynamically.

    .. code:: python

        from tchannel import thrift

        keyvalue = thrift.load('keyvalue.thrift', service='keyvalue')

    To register a Thrift handler, use the ``register()`` decorator, providing
    a reference to the compiled service as an argument. The name of the
    service method should match the name of the decorated function.

    .. code:: python

        tchannel = TChannel(...)

        @tchannel.thrift.register(keyvalue.KeyValue)
        def setValue(request):
            data[request.body.key] = request.body.value

    Use methods on the compiled service to generate requests to remote
    services and execute them via ``TChannel.thrift()``.

    .. code:: python

        response = yield tchannel.thrift(
            keyvalue.KeyValue.setValue(key='foo', value='bar')
        )
    """

    NAME = THRIFT

    def __init__(self, tchannel):
        self._tchannel = tchannel
        self.tracer = ClientTracer(channel=tchannel)

    @gen.coroutine
    def __call__(
        self,
        request,
        headers=None,
        timeout=None,
        retry_on=None,
        retry_limit=None,
        shard_key=None,
        trace=None,
        hostport=None,
        routing_delegate=None,
        caller_name=None,
    ):
        """Make a Thrift TChannel request.

        Returns a ``Response`` containing the return value of the Thrift
        call (if any). If the remote server responded with a Thrift exception,
        that exception is raised.

        :param string request:
            Request obtained by calling a method on service objects generated
            by :py:func:`tchannel.thrift.load`.
        :param dict headers:
            Dictionary of header key-value pairs.
        :param float timeout:
            How long to wait (in seconds) before raising a ``TimeoutError`` -
            this defaults to ``tchannel.glossary.DEFAULT_TIMEOUT``.
        :param string retry_on:
            What events to retry on - valid values can be found in
            ``tchannel.retry``.
        :param int retry_limit:
            How many attempts should be made (in addition to the initial
            attempt) to re-send this request when retryable error conditions
            (specified by ``retry_on``) are encountered.

            Defaults to ``tchannel.retry.DEFAULT_RETRY_LIMIT`` (4).

            Note that the maximum possible time elapsed for a request is thus
            ``(retry_limit + 1) * timeout``.
        :param string shard_key:
            Set the ``sk`` transport header for Ringpop request routing.
        :param int trace:
            Flags for tracing.
        :param string hostport:
            A 'host:port' value to use when making a request directly to a
            TChannel service, bypassing Hyperbahn. This value takes precedence
            over the ``hostport`` specified to
            :py:func:`tchannel.thrift.load`.
        :param routing_delegate:
            Name of a service to which the request router should forward the
            request instead of the service specified in the call req.
        :param caller_name:
            Name of the service making the request. Defaults to the name
            provided when the TChannel was instantiated.

        :rtype: Response
        """
        if not headers:
            headers = {}

        span, headers = self.tracer.start_span(service=request.service,
                                               endpoint=request.endpoint,
                                               headers=headers,
                                               hostport=hostport,
                                               encoding='thrift')

        yield self._tchannel._dep_tchannel.event_emitter.fire(
            EventType.before_serialize_request_headers,
            headers,
            request.service,
        )
        serializer = request.get_serializer()

        # serialize
        try:
            headers = serializer.serialize_header(headers=headers)
        except (AttributeError, TypeError):
            raise ValueError(
                'headers must be a map[string]string (a shallow dict'
                ' where keys and values are strings)')

        body = serializer.serialize_body(request.call_args)

        # TODO There's only one yield. Drop in favor of future+callback.
        response = yield self._tchannel.call(
            scheme=self.NAME,
            service=request.service,
            arg1=request.endpoint,
            arg2=headers,
            arg3=body,
            timeout=timeout,
            retry_on=retry_on,
            retry_limit=retry_limit,
            hostport=hostport or request.hostport,
            shard_key=shard_key,
            trace=trace,
            tracing_span=span,  # span is finished in PeerClientOperation.send
            routing_delegate=routing_delegate,
            caller_name=caller_name,
        )

        response.headers = serializer.deserialize_header(
            headers=response.headers)
        body = serializer.deserialize_body(body=response.body)

        response.body = request.read_body(body)
        raise gen.Return(response)

    def register(self, thrift_module, **kwargs):
        # dat circular import
        from tchannel.thrift import rw as thriftrw

        if isinstance(thrift_module, thriftrw.Service):
            # Dirty hack to support thriftrw and old API
            return thriftrw.register(
                # TODO drop deprecated tchannel
                self._tchannel._dep_tchannel._handler,
                thrift_module,
                **kwargs)
        else:
            return self._tchannel.register(scheme=self.NAME,
                                           endpoint=thrift_module,
                                           **kwargs)
Example #2
0
    def send(
        self, arg1, arg2, arg3,
        headers=None,
        retry_limit=None,
        ttl=None,
    ):
        """Make a request to the Peer.

        :param arg1:
            String or Stream containing the contents of arg1. If None, an empty
            stream is used.
        :param arg2:
            String or Stream containing the contents of arg2. If None, an empty
            stream is used.
        :param arg3:
            String or Stream containing the contents of arg3. If None, an empty
            stream is used.
        :param headers:
            Headers will be put in the message as protocol header.
        :param retry_limit:
           Maximum number of retries will perform on the message. If the number
           is 0, it means no retry.
        :param ttl:
            Timeout for each request (second).
        :return:
            Future that contains the response from the peer.
        """

        # find a peer connection
        # If we can't find available peer at the first time, we throw
        # NoAvailablePeerError. Later during retry, if we can't find available
        # peer, we throw exceptions from retry not NoAvailablePeerError.
        peer, connection = yield self._get_peer_connection()

        arg1, arg2, arg3 = (
            maybe_stream(arg1), maybe_stream(arg2), maybe_stream(arg3)
        )

        if retry_limit is None:
            retry_limit = DEFAULT_RETRY_LIMIT

        ttl = ttl or DEFAULT_TIMEOUT
        # hack to get endpoint from arg_1 for trace name
        arg1.close()
        endpoint = yield read_full(arg1)

        # set default transport headers
        headers = headers or {}
        for k, v in self.headers.iteritems():
            headers.setdefault(k, v)

        if self.tracing_span is None:
            tracer = ClientTracer(channel=self.tchannel)
            self.tracing_span, _ = tracer.start_span(
                service=self.service, endpoint=endpoint,
                hostport=self._hostport, encoding=self.headers.get('as')
            )

        request = Request(
            service=self.service,
            argstreams=[InMemStream(endpoint), arg2, arg3],
            id=connection.writer.next_message_id(),
            headers=headers,
            endpoint=endpoint,
            ttl=ttl,
            tracing=tracing.span_to_tracing_field(self.tracing_span)
        )

        # only retry on non-stream request
        if request.is_streaming_request or self._hostport:
            retry_limit = 0

        if request.is_streaming_request:
            request.ttl = 0

        try:
            with self.tracing_span:  # to ensure span is finished
                response = yield self.send_with_retry(
                    request, peer, retry_limit, connection
                )
        except Exception as e:
            # event: on_exception
            self.tchannel.event_emitter.fire(
                EventType.on_exception, request, e,
            )
            raise

        log.debug("Got response %s", response)

        raise gen.Return(response)
Example #3
0
    def send(
        self, arg1, arg2, arg3,
        headers=None,
        retry_limit=None,
        ttl=None,
    ):
        """Make a request to the Peer.

        :param arg1:
            String or Stream containing the contents of arg1. If None, an empty
            stream is used.
        :param arg2:
            String or Stream containing the contents of arg2. If None, an empty
            stream is used.
        :param arg3:
            String or Stream containing the contents of arg3. If None, an empty
            stream is used.
        :param headers:
            Headers will be put in the message as protocol header.
        :param retry_limit:
           Maximum number of retries will perform on the message. If the number
           is 0, it means no retry.
        :param ttl:
            Timeout for each request (second).
        :return:
            Future that contains the response from the peer.
        """

        # find a peer connection
        # If we can't find available peer at the first time, we throw
        # NoAvailablePeerError. Later during retry, if we can't find available
        # peer, we throw exceptions from retry not NoAvailablePeerError.
        peer, connection = yield self._get_peer_connection()

        arg1, arg2, arg3 = (
            maybe_stream(arg1), maybe_stream(arg2), maybe_stream(arg3)
        )

        if retry_limit is None:
            retry_limit = DEFAULT_RETRY_LIMIT

        ttl = ttl or DEFAULT_TIMEOUT
        # hack to get endpoint from arg_1 for trace name
        arg1.close()
        endpoint = yield read_full(arg1)

        # set default transport headers
        headers = headers or {}
        for k, v in self.headers.items():
            headers.setdefault(k, v)

        if self.tracing_span is None:
            tracer = ClientTracer(channel=self.tchannel)
            self.tracing_span, _ = tracer.start_span(
                service=self.service, endpoint=endpoint,
                hostport=self._hostport, encoding=self.headers.get('as')
            )

        request = Request(
            service=self.service,
            argstreams=[InMemStream(endpoint), arg2, arg3],
            id=connection.writer.next_message_id(),
            headers=headers,
            endpoint=endpoint,
            ttl=ttl,
            tracing=tracing.span_to_tracing_field(self.tracing_span)
        )

        # only retry on non-stream request
        if request.is_streaming_request or self._hostport:
            retry_limit = 0

        if request.is_streaming_request:
            request.ttl = 0

        try:
            with self.tracing_span:  # to ensure span is finished
                response = yield self.send_with_retry(
                    request, peer, retry_limit, connection
                )
        except Exception as e:
            # event: on_exception
            exc_info = sys.exc_info()
            yield self.tchannel.event_emitter.fire(
                EventType.on_exception, request, e,
            )
            six.reraise(*exc_info)

        log.debug("Got response %s", response)

        raise gen.Return(response)
Example #4
0
class ThriftArgScheme(object):
    """Handler registration and serialization for Thrift.

    Use :py:func:`tchannel.thrift.load` to parse your Thrift IDL and compile
    it into a module dynamically.

    .. code:: python

        from tchannel import thrift

        keyvalue = thrift.load('keyvalue.thrift', service='keyvalue')

    To register a Thrift handler, use the ``register()`` decorator, providing
    a reference to the compiled service as an argument. The name of the
    service method should match the name of the decorated function.

    .. code:: python

        tchannel = TChannel(...)

        @tchannel.thrift.register(keyvalue.KeyValue)
        def setValue(request):
            data[request.body.key] = request.body.value

    Use methods on the compiled service to generate requests to remote
    services and execute them via ``TChannel.thrift()``.

    .. code:: python

        response = yield tchannel.thrift(
            keyvalue.KeyValue.setValue(key='foo', value='bar')
        )
    """

    NAME = THRIFT

    def __init__(self, tchannel):
        self._tchannel = tchannel
        self.tracer = ClientTracer(channel=tchannel)

    @gen.coroutine
    def __call__(
        self,
        request,
        headers=None,
        timeout=None,
        retry_on=None,
        retry_limit=None,
        shard_key=None,
        trace=None,
        hostport=None,
        routing_delegate=None,
        caller_name=None,
    ):
        """Make a Thrift TChannel request.

        Returns a ``Response`` containing the return value of the Thrift
        call (if any). If the remote server responded with a Thrift exception,
        that exception is raised.

        :param string request:
            Request obtained by calling a method on service objects generated
            by :py:func:`tchannel.thrift.load`.
        :param dict headers:
            Dictionary of header key-value pairs.
        :param float timeout:
            How long to wait (in seconds) before raising a ``TimeoutError`` -
            this defaults to ``tchannel.glossary.DEFAULT_TIMEOUT``.
        :param string retry_on:
            What events to retry on - valid values can be found in
            ``tchannel.retry``.
        :param int retry_limit:
            How many attempts should be made (in addition to the initial
            attempt) to re-send this request when retryable error conditions
            (specified by ``retry_on``) are encountered.

            Defaults to ``tchannel.retry.DEFAULT_RETRY_LIMIT`` (4).

            Note that the maximum possible time elapsed for a request is thus
            ``(retry_limit + 1) * timeout``.
        :param string shard_key:
            Set the ``sk`` transport header for Ringpop request routing.
        :param int trace:
            Flags for tracing.
        :param string hostport:
            A 'host:port' value to use when making a request directly to a
            TChannel service, bypassing Hyperbahn. This value takes precedence
            over the ``hostport`` specified to
            :py:func:`tchannel.thrift.load`.
        :param routing_delegate:
            Name of a service to which the request router should forward the
            request instead of the service specified in the call req.
        :param caller_name:
            Name of the service making the request. Defaults to the name
            provided when the TChannel was instantiated.

        :rtype: Response
        """
        if not headers:
            headers = {}

        span, headers = self.tracer.start_span(
            service=request.service, endpoint=request.endpoint,
            headers=headers, hostport=hostport, encoding='thrift'
        )

        serializer = request.get_serializer()

        # serialize
        try:
            headers = serializer.serialize_header(headers=headers)
        except (AttributeError, TypeError):
            raise ValueError(
                'headers must be a map[string]string (a shallow dict'
                ' where keys and values are strings)'
            )

        body = serializer.serialize_body(request.call_args)

        # TODO There's only one yield. Drop in favor of future+callback.
        response = yield self._tchannel.call(
            scheme=self.NAME,
            service=request.service,
            arg1=request.endpoint,
            arg2=headers,
            arg3=body,
            timeout=timeout,
            retry_on=retry_on,
            retry_limit=retry_limit,
            hostport=hostport or request.hostport,
            shard_key=shard_key,
            trace=trace,
            tracing_span=span,  # span is finished in PeerClientOperation.send
            routing_delegate=routing_delegate,
            caller_name=caller_name,
        )

        response.headers = serializer.deserialize_header(
            headers=response.headers
        )
        body = serializer.deserialize_body(body=response.body)

        response.body = request.read_body(body)
        raise gen.Return(response)

    def register(self, thrift_module, **kwargs):
        # dat circular import
        from tchannel.thrift import rw as thriftrw

        if isinstance(thrift_module, thriftrw.Service):
            # Dirty hack to support thriftrw and old API
            return thriftrw.register(
                # TODO drop deprecated tchannel
                self._tchannel._dep_tchannel._handler,
                thrift_module,
                **kwargs
            )
        else:
            return self._tchannel.register(
                scheme=self.NAME,
                endpoint=thrift_module,
                **kwargs
            )