blob: 31a7c2d7487bb4413e768ea5b4da62289dd057b3 [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import concurrent
from collections.abc import Iterator
from dubbo.classes import MethodDescriptor
from dubbo.compression import Compressor, Identity
from dubbo.constants import common_constants
from dubbo.extension import ExtensionError, extensionLoader
from dubbo.loggers import loggerFactory
from dubbo.protocol import Invoker, Result
from dubbo.protocol.invocation import Invocation, RpcInvocation
from dubbo.protocol.triple.call import TripleClientCall
from dubbo.protocol.triple.call.client_call import (
FutureToClientCallListenerAdapter,
ReadStreamToClientCallListenerAdapter,
)
from dubbo.protocol.triple.constants import TripleHeaderName, TripleHeaderValue
from dubbo.protocol.triple.metadata import RequestMetadata
from dubbo.protocol.triple.results import TriResult
from dubbo.protocol.triple.streams import (
TriClientWriteStream,
TriReadStream,
TriReadWriteStream,
)
from dubbo.remoting import Client
from dubbo.remoting.aio.exceptions import RemotingError
from dubbo.remoting.aio.http2.stream_handler import StreamClientMultiplexHandler
from dubbo.serialization import (
CustomDeserializer,
CustomSerializer,
DirectDeserializer,
DirectSerializer,
)
from dubbo.url import URL
from dubbo.utils import FunctionHelper
__all__ = ["TripleInvoker"]
_LOGGER = loggerFactory.get_logger()
class TripleInvoker(Invoker):
"""
Triple invoker.
"""
__slots__ = ["_url", "_client", "_stream_multiplexer", "_compression"]
def __init__(self, url: URL, client: Client, stream_multiplexer: StreamClientMultiplexHandler):
self._url = url
self._client = client
self._stream_multiplexer = stream_multiplexer
def invoke(self, invocation: RpcInvocation) -> Result:
"""
Invoke the invocation.
:param invocation: The invocation to invoke.
:type invocation: RpcInvocation
:return: The result of the invocation.
:rtype: Result
"""
future = concurrent.futures.Future()
result = TriResult(future)
if not self._client.is_connected():
result.set_exception(RemotingError("The client is not connected to the server."))
return result
# get method descriptor
method_descriptor: MethodDescriptor = invocation.get_attribute(common_constants.METHOD_DESCRIPTOR_KEY)
# get arg_serializer
arg_serializing_function = method_descriptor.get_arg_serializer()
arg_serializer = CustomSerializer(arg_serializing_function) if arg_serializing_function else DirectSerializer()
# get return_deserializer
return_deserializing_function = method_descriptor.get_return_deserializer()
return_deserializer = (
CustomDeserializer(return_deserializing_function) if return_deserializing_function else DirectDeserializer()
)
write_stream = TriClientWriteStream()
read_stream = TriReadStream()
# create listener
rpc_type = method_descriptor.get_rpc_type()
is_unary = not rpc_type.client_stream and not rpc_type.server_stream
if is_unary:
listener = FutureToClientCallListenerAdapter(future)
else:
read_stream = TriReadStream()
listener = ReadStreamToClientCallListenerAdapter(read_stream)
# Create a new TriClientCall
tri_client_call = TripleClientCall(
self._stream_multiplexer,
listener,
arg_serializer,
return_deserializer,
)
write_stream.set_call(tri_client_call)
if not is_unary:
write_stream = TriReadWriteStream(write_stream, read_stream)
# start the call
try:
metadata = self._create_metadata(invocation)
tri_client_call.start(metadata)
except ExtensionError as e:
result.set_exception(e)
return result
# write the message
if not rpc_type.client_stream:
# if the client call is not a stream, we send the message directly
FunctionHelper.call_func(write_stream.write, invocation.get_argument())
write_stream.done_writing()
else:
# try to get first argument and check if it is an iterable
args, _ = invocation.get_argument()
if args and isinstance(args[0], Iterator):
# if the argument is an iterator, we need to write the stream
for arg in args[0]:
write_stream.write(arg)
write_stream.done_writing()
# If the call is not unary, we need to return the stream
# server_stream -> return read_stream
# client_stream or bidirectional_stream -> return write_read_stream
if not is_unary:
if rpc_type.server_stream and not rpc_type.client_stream:
result.set_value(read_stream)
else:
result.set_value(write_stream)
return result
def _create_metadata(self, invocation: Invocation) -> RequestMetadata:
"""
Create the metadata.
:param invocation: The invocation.
:type invocation: Invocation
:return: The metadata.
:rtype: RequestMetadata
:raise ExtensionError: If the compressor is not supported.
"""
metadata = RequestMetadata()
# set service and method
metadata.service = invocation.get_service_name()
metadata.method = invocation.get_method_name()
# get scheme
metadata.scheme = (
TripleHeaderValue.HTTPS.value
if self._url.parameters.get(common_constants.SSL_ENABLED_KEY, False)
else TripleHeaderValue.HTTP.value
)
# get compressor
compression = self._url.parameters.get(common_constants.COMPRESSION_KEY, Identity.get_message_encoding())
if metadata.compressor.get_message_encoding() != compression:
try:
metadata.compressor = extensionLoader.get_extension(Compressor, compression)()
except ExtensionError as e:
_LOGGER.error("Unsupported compressor: %s", compression)
raise e
# get address
metadata.address = self._url.location
# TODO add more metadata
metadata.attachments[TripleHeaderName.TE.value] = TripleHeaderValue.TRAILERS.value
return metadata
def get_url(self) -> URL:
return self._url
def is_available(self) -> bool:
return self._client.is_connected()
def destroy(self) -> None:
self._client.close()
self._client = None
self._stream_multiplexer = None
self._url = None