From 47f7be2fd6137dc01433d9cd0e37204b0d350686 Mon Sep 17 00:00:00 2001 From: GuoHao Date: Wed, 29 Jun 2022 14:19:48 +0800 Subject: [PATCH] [3.0-Triple] Support reflection api (#10168) * Add proto * Update compiler * Add v1Alpha for compatibility * Abstract builtin service export * Try add get fd by name * get service by symbol now works * Add type * Support extension * Ignore reflection code scan * Remove v1 --- .../apache/dubbo/gen/AbstractGenerator.java | 66 ++++-- .../main/resources/Dubbo3TripleStub.mustache | 1 + .../apache/dubbo/rpc/stub/StubSuppliers.java | 1 - .../tri/service/ReflectionV1AlphaService.java | 202 ++++++++++++++++++ .../tri/service/SchemaDescriptorRegistry.java | 90 ++++++++ .../tri/service/TriBuiltinService.java | 25 ++- .../src/main/proto/reflectionV1Alpha.proto | 144 +++++++++++++ pom.xml | 1 + 8 files changed, 500 insertions(+), 30 deletions(-) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto diff --git a/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java b/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java index 587e9c310f..8d95ecdf87 100644 --- a/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java +++ b/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java @@ -72,22 +72,25 @@ public abstract class AbstractGenerator extends Generator { } @Override - public List generateFiles(PluginProtos.CodeGeneratorRequest request) throws GeneratorException { + public List generateFiles( + PluginProtos.CodeGeneratorRequest request) throws GeneratorException { final ProtoTypeMap typeMap = ProtoTypeMap.of(request.getProtoFileList()); List protosToGenerate = request.getProtoFileList().stream() - .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName())) - .collect(Collectors.toList()); + .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName())) + .collect(Collectors.toList()); List services = findServices(protosToGenerate, typeMap); return generateFiles(services); } - private List findServices(List protos, ProtoTypeMap typeMap) { + private List findServices(List protos, + ProtoTypeMap typeMap) { List contexts = new ArrayList<>(); protos.forEach(fileProto -> { - for (int serviceNumber = 0; serviceNumber < fileProto.getServiceCount(); serviceNumber++) { + for (int serviceNumber = 0; serviceNumber < fileProto.getServiceCount(); + serviceNumber++) { ServiceContext serviceContext = buildServiceContext( fileProto.getService(serviceNumber), typeMap, @@ -96,8 +99,12 @@ public abstract class AbstractGenerator extends Generator { ); serviceContext.protoName = fileProto.getName(); serviceContext.packageName = extractPackageName(fileProto); + if (!Strings.isNullOrEmpty(fileProto.getOptions().getJavaOuterClassname())) { + serviceContext.outerClassName = fileProto.getOptions().getJavaOuterClassname(); + } serviceContext.commonPackageName = extractCommonPackageName(fileProto); - serviceContext.multipleFiles = fileProto.getOptions() != null && fileProto.getOptions().getJavaMultipleFiles(); + serviceContext.multipleFiles = + fileProto.getOptions() != null && fileProto.getOptions().getJavaMultipleFiles(); contexts.add(serviceContext); } }); @@ -121,15 +128,18 @@ public abstract class AbstractGenerator extends Generator { return Strings.nullToEmpty(proto.getPackage()); } - private ServiceContext buildServiceContext(ServiceDescriptorProto serviceProto, ProtoTypeMap typeMap, List locations, int serviceNumber) { + private ServiceContext buildServiceContext(ServiceDescriptorProto serviceProto, + ProtoTypeMap typeMap, List locations, int serviceNumber) { ServiceContext serviceContext = new ServiceContext(); - serviceContext.fileName = getClassPrefix() + serviceProto.getName() + getClassSuffix() + ".java"; + serviceContext.fileName = + getClassPrefix() + serviceProto.getName() + getClassSuffix() + ".java"; serviceContext.className = getClassPrefix() + serviceProto.getName() + getClassSuffix(); - + serviceContext.outerClassName = serviceProto.getName() + "OuterClass"; serviceContext.interfaceFileName = serviceProto.getName() + ".java"; serviceContext.interfaceClassName = serviceProto.getName(); serviceContext.serviceName = serviceProto.getName(); - serviceContext.deprecated = serviceProto.getOptions() != null && serviceProto.getOptions().getDeprecated(); + serviceContext.deprecated = + serviceProto.getOptions() != null && serviceProto.getOptions().getDeprecated(); List allLocationsForService = locations.stream() .filter(location -> @@ -143,7 +153,8 @@ public abstract class AbstractGenerator extends Generator { .filter(location -> location.getPathCount() == SERVICE_NUMBER_OF_PATHS) .findFirst() .orElseGet(Location::getDefaultInstance); - serviceContext.javaDoc = getJavaDoc(getComments(serviceLocation), getServiceJavaDocPrefix()); + serviceContext.javaDoc = getJavaDoc(getComments(serviceLocation), + getServiceJavaDocPrefix()); for (int methodNumber = 0; methodNumber < serviceProto.getMethodCount(); methodNumber++) { MethodContext methodContext = buildMethodContext( @@ -160,13 +171,15 @@ public abstract class AbstractGenerator extends Generator { return serviceContext; } - private MethodContext buildMethodContext(MethodDescriptorProto methodProto, ProtoTypeMap typeMap, List locations, int methodNumber) { + private MethodContext buildMethodContext(MethodDescriptorProto methodProto, + ProtoTypeMap typeMap, List locations, int methodNumber) { MethodContext methodContext = new MethodContext(); methodContext.originMethodName = methodProto.getName(); methodContext.methodName = lowerCaseFirst(methodProto.getName()); methodContext.inputType = typeMap.toJavaTypeName(methodProto.getInputType()); methodContext.outputType = typeMap.toJavaTypeName(methodProto.getOutputType()); - methodContext.deprecated = methodProto.getOptions() != null && methodProto.getOptions().getDeprecated(); + methodContext.deprecated = + methodProto.getOptions() != null && methodProto.getOptions().getDeprecated(); methodContext.isManyInput = methodProto.getClientStreaming(); methodContext.isManyOutput = methodProto.getServerStreaming(); methodContext.methodNumber = methodNumber; @@ -203,7 +216,8 @@ public abstract class AbstractGenerator extends Generator { return Character.toLowerCase(s.charAt(0)) + s.substring(1); } - private List generateFiles(List services) { + private List generateFiles( + List services) { List allServiceFiles = new ArrayList<>(); for (ServiceContext context : services) { List files = buildFile(context); @@ -212,7 +226,7 @@ public abstract class AbstractGenerator extends Generator { return allServiceFiles; } - protected boolean enableMultipleTemplateFiles(){ + protected boolean enableMultipleTemplateFiles() { return false; } @@ -266,7 +280,8 @@ public abstract class AbstractGenerator extends Generator { } private String getComments(Location location) { - return location.getLeadingComments().isEmpty() ? location.getTrailingComments() : location.getLeadingComments(); + return location.getLeadingComments().isEmpty() ? location.getTrailingComments() + : location.getLeadingComments(); } private String getJavaDoc(String comments, String prefix) { @@ -288,6 +303,7 @@ public abstract class AbstractGenerator extends Generator { * Template class for proto Service objects. */ private class ServiceContext { + // CHECKSTYLE DISABLE VisibilityModifier FOR 8 LINES public String fileName; public String interfaceFileName; @@ -300,6 +316,8 @@ public abstract class AbstractGenerator extends Generator { public boolean deprecated; public String javaDoc; public boolean multipleFiles; + + public String outerClassName; public List methods = new ArrayList<>(); public Set methodTypes = new HashSet<>(); @@ -309,11 +327,13 @@ public abstract class AbstractGenerator extends Generator { } public List unaryMethods() { - return methods.stream().filter(m -> (!m.isManyInput && !m.isManyOutput)).collect(Collectors.toList()); + return methods.stream().filter(m -> (!m.isManyInput && !m.isManyOutput)) + .collect(Collectors.toList()); } public List serverStreamingMethods() { - return methods.stream().filter(m -> !m.isManyInput && m.isManyOutput).collect(Collectors.toList()); + return methods.stream().filter(m -> !m.isManyInput && m.isManyOutput) + .collect(Collectors.toList()); } public List biStreamingMethods() { @@ -321,11 +341,13 @@ public abstract class AbstractGenerator extends Generator { } public List biStreamingWithoutClientStreamMethods() { - return methods.stream().filter(m->m.isManyInput&&m.isManyOutput).collect(Collectors.toList()); + return methods.stream().filter(m -> m.isManyInput && m.isManyOutput) + .collect(Collectors.toList()); } public List clientStreamingMethods() { - return methods.stream().filter(m->m.isManyInput&&!m.isManyOutput).collect(Collectors.toList()); + return methods.stream().filter(m -> m.isManyInput && !m.isManyOutput) + .collect(Collectors.toList()); } public List methods() { @@ -338,6 +360,7 @@ public abstract class AbstractGenerator extends Generator { * Template class for proto RPC objects. */ private class MethodContext { + // CHECKSTYLE DISABLE VisibilityModifier FOR 10 LINES public String originMethodName; public String methodName; @@ -358,7 +381,8 @@ public abstract class AbstractGenerator extends Generator { for (int i = 0; i < methodName.length(); i++) { char c = methodName.charAt(i); s.append(Character.toUpperCase(c)); - if ((i < methodName.length() - 1) && Character.isLowerCase(c) && Character.isUpperCase(methodName.charAt(i + 1))) { + if ((i < methodName.length() - 1) && Character.isLowerCase(c) + && Character.isUpperCase(methodName.charAt(i + 1))) { s.append('_'); } } diff --git a/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache b/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache index d6f6fa9fc2..77645108de 100644 --- a/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache +++ b/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache @@ -52,6 +52,7 @@ public final class {{className}} { private static final StubServiceDescriptor serviceDescriptor = new StubServiceDescriptor(SERVICE_NAME,{{interfaceClassName}}.class); static { + org.apache.dubbo.rpc.protocol.tri.service.SchemaDescriptorRegistry.addSchemaDescriptor(SERVICE_NAME,{{outerClassName}}.getDescriptor()); StubSuppliers.addSupplier(SERVICE_NAME, {{className}}::newStub); StubSuppliers.addSupplier({{interfaceClassName}}.JAVA_SERVICE_NAME, {{className}}::newStub); StubSuppliers.addDescriptor(SERVICE_NAME, serviceDescriptor); diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java index 6096caca0a..a56e52f643 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java @@ -33,7 +33,6 @@ public class StubSuppliers { public static void addDescriptor(String interfaceName, ServiceDescriptor serviceDescriptor) { SERVICE_DESCRIPTOR_MAP.put(interfaceName, serviceDescriptor); } - public static void addSupplier(String interfaceName, Function, Object> supplier) { STUB_SUPPLIERS.put(interfaceName, supplier); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java new file mode 100644 index 0000000000..b1a2659eb6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package org.apache.dubbo.rpc.protocol.tri.service; + +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.rpc.TriRpcStatus; + +import com.google.protobuf.Descriptors.FileDescriptor; +import io.grpc.reflection.v1alpha.DubboServerReflectionTriple; +import io.grpc.reflection.v1alpha.ErrorResponse; +import io.grpc.reflection.v1alpha.ExtensionNumberResponse; +import io.grpc.reflection.v1alpha.ExtensionRequest; +import io.grpc.reflection.v1alpha.FileDescriptorResponse; +import io.grpc.reflection.v1alpha.ListServiceResponse; +import io.grpc.reflection.v1alpha.ServerReflectionRequest; +import io.grpc.reflection.v1alpha.ServerReflectionResponse; +import io.grpc.reflection.v1alpha.ServiceResponse; + +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; + +/** + * Provides a reflection service for Protobuf service for service test and dynamic gateway. + * + * @link https://github.com/grpc/grpc/blob/master/doc/server-reflection.md + */ +public class ReflectionV1AlphaService extends DubboServerReflectionTriple.ServerReflectionImplBase { + + + @Override + public StreamObserver serverReflectionInfo( + StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(ServerReflectionRequest request) { + switch (request.getMessageRequestCase()) { + case FILE_BY_FILENAME: + getFileByName(request, responseObserver); + break; + case FILE_CONTAINING_SYMBOL: + getFileContainingSymbol(request, responseObserver); + break; + case FILE_CONTAINING_EXTENSION: + getFileByExtension(request, responseObserver); + break; + case ALL_EXTENSION_NUMBERS_OF_TYPE: + getAllExtensions(request, responseObserver); + break; + case LIST_SERVICES: + listServices(request, responseObserver); + break; + default: + sendErrorResponse( + request, + TriRpcStatus.Code.UNIMPLEMENTED, + "not implemented " + request.getMessageRequestCase(), responseObserver); + } + } + + @Override + public void onError(Throwable throwable) { + responseObserver.onError(throwable); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } + + private void getFileByName(ServerReflectionRequest request, + StreamObserver responseObserver) { + String name = request.getFileByFilename(); + FileDescriptor fd = SchemaDescriptorRegistry.getSchemaDescriptor(name); + if (fd != null) { + responseObserver.onNext(createServerReflectionResponse(request, fd)); + } else { + sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "File not found.", + responseObserver); + } + } + + private void getFileContainingSymbol(ServerReflectionRequest request, + StreamObserver responseObserver) { + String symbol = request.getFileContainingSymbol(); + FileDescriptor fd = SchemaDescriptorRegistry.getSchemaDescriptor(symbol); + if (fd != null) { + responseObserver.onNext(createServerReflectionResponse(request, fd)); + } else { + sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "Symbol not found.", + responseObserver); + } + } + + private void getFileByExtension(ServerReflectionRequest request, + StreamObserver responseObserver) { + ExtensionRequest extensionRequest = request.getFileContainingExtension(); + String type = extensionRequest.getContainingType(); + int extension = extensionRequest.getExtensionNumber(); + FileDescriptor fd = + SchemaDescriptorRegistry.getFileDescriptorByExtensionAndNumber(type, extension); + if (fd != null) { + responseObserver.onNext(createServerReflectionResponse(request, fd)); + } else { + sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "Extension not found.", + responseObserver); + } + } + + private void getAllExtensions(ServerReflectionRequest request, + StreamObserver responseObserver) { + String type = request.getAllExtensionNumbersOfType(); + Set extensions = SchemaDescriptorRegistry.getExtensionNumbers(type); + if (extensions != null) { + ExtensionNumberResponse.Builder builder = + ExtensionNumberResponse.newBuilder() + .setBaseTypeName(type) + .addAllExtensionNumber(extensions); + responseObserver.onNext( + ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setAllExtensionNumbersResponse(builder) + .build()); + } else { + sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "Type not found.", + responseObserver); + } + } + + private void listServices(ServerReflectionRequest request, + StreamObserver responseObserver) { + ListServiceResponse.Builder builder = ListServiceResponse.newBuilder(); + + for (String serviceName : SchemaDescriptorRegistry.listServiceNames()) { + builder.addService(ServiceResponse.newBuilder().setName(serviceName)); + } + responseObserver.onNext( + ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setListServicesResponse(builder) + .build()); + } + + private void sendErrorResponse( + ServerReflectionRequest request, TriRpcStatus.Code code, String message, + StreamObserver responseObserver) { + ServerReflectionResponse response = + ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setErrorResponse( + ErrorResponse.newBuilder() + .setErrorCode(code.code) + .setErrorMessage(message)) + .build(); + responseObserver.onNext(response); + } + + private ServerReflectionResponse createServerReflectionResponse( + ServerReflectionRequest request, FileDescriptor fd) { + FileDescriptorResponse.Builder fdRBuilder = FileDescriptorResponse.newBuilder(); + Set seenFiles = new HashSet<>(); + Queue frontier = new ArrayDeque<>(); + seenFiles.add(fd.getName()); + frontier.add(fd); + while (!frontier.isEmpty()) { + FileDescriptor nextFd = frontier.remove(); + fdRBuilder.addFileDescriptorProto(nextFd.toProto().toByteString()); + for (FileDescriptor dependencyFd : nextFd.getDependencies()) { + if (!seenFiles.contains(dependencyFd.getName())) { + seenFiles.add(dependencyFd.getName()); + frontier.add(dependencyFd); + } + } + } + return ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setFileDescriptorResponse(fdRBuilder) + .build(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java new file mode 100644 index 0000000000..3d37505ae1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package org.apache.dubbo.rpc.protocol.tri.service; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class SchemaDescriptorRegistry { + + private static final Map DESCRIPTORS_BY_SYMBOL = new ConcurrentHashMap<>(); + + private static final Map> EXTENSIONS = new ConcurrentHashMap<>(); + + private static final Set SERVICES = new HashSet<>(); + + public static void addSchemaDescriptor(String serviceName, FileDescriptor fd) { + SERVICES.add(serviceName); + DESCRIPTORS_BY_SYMBOL.put(serviceName, fd); + for (Descriptor messageType : fd.getMessageTypes()) { + addType(messageType); + } + for (FieldDescriptor extension : fd.getExtensions()) { + addExtension(extension, fd); + } + } + + private static void addType(Descriptor descriptor) { + DESCRIPTORS_BY_SYMBOL.put(descriptor.getFullName(), descriptor.getFile()); + for (Descriptor nestedType : descriptor.getNestedTypes()) { + addType(nestedType); + } + } + + private static void addExtension(FieldDescriptor extension, FileDescriptor fd) { + String name = extension.getContainingType().getFullName(); + int number = extension.getNumber(); + if (!EXTENSIONS.containsKey(name)) { + EXTENSIONS.put(name, new HashMap<>()); + } + Map fdMap = EXTENSIONS.get(name); + fdMap.put(number, fd); + } + + public static FileDescriptor getFileDescriptorByExtensionAndNumber(String extension, + int number) { + return EXTENSIONS.getOrDefault(extension, Collections.emptyMap()).get(number); + } + + public static Set getExtensionNumbers(String extension) { + Map ret = EXTENSIONS.get(extension); + if (ret == null) { + return null; + } else { + return ret.keySet(); + } + } + + public static FileDescriptor getSchemaDescriptor(String serviceName) { + return DESCRIPTORS_BY_SYMBOL.get(serviceName); + } + + public static List listServiceNames() { + return new ArrayList<>(SERVICES); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java index 35d6ad48ea..2bf3ca0ece 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java @@ -24,6 +24,7 @@ import org.apache.dubbo.rpc.PathResolver; import org.apache.dubbo.rpc.ProxyFactory; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ModuleModel; import io.grpc.health.v1.DubboHealthTriple; import io.grpc.health.v1.Health; @@ -44,6 +45,7 @@ public class TriBuiltinService { private final Health healthService; + private final ReflectionV1AlphaService reflectionServiceV1Alpha; private final HealthStatusManager healthStatusManager; private final AtomicBoolean init = new AtomicBoolean(); @@ -51,6 +53,7 @@ public class TriBuiltinService { public TriBuiltinService(FrameworkModel frameworkModel) { healthStatusManager = new HealthStatusManager(new TriHealthImpl()); healthService = healthStatusManager.getHealthService(); + reflectionServiceV1Alpha = new ReflectionV1AlphaService(); proxyFactory = frameworkModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); pathResolver = frameworkModel.getExtensionLoader(PathResolver.class).getDefaultExtension(); init(); @@ -58,17 +61,23 @@ public class TriBuiltinService { public void init() { if (init.compareAndSet(false, true)) { - URL url = new ServiceConfigURL(CommonConstants.TRIPLE, null, null, ANYHOST_VALUE, 0, - DubboHealthTriple.SERVICE_NAME) - .addParameter(PROXY_KEY, CommonConstants.NATIVE_STUB) - .setScopeModel(ApplicationModel.defaultModel().getInternalModule()); - Invoker invoker = proxyFactory.getInvoker(healthService, Health.class, url); - pathResolver.add(DubboHealthTriple.SERVICE_NAME, invoker); - ApplicationModel.defaultModel().getInternalModule() - .addDestroyListener(scopeModel -> pathResolver.remove(DubboHealthTriple.SERVICE_NAME)); + addSingleBuiltinService(DubboHealthTriple.SERVICE_NAME, healthService, Health.class); + addSingleBuiltinService(ReflectionV1AlphaService.SERVICE_NAME, reflectionServiceV1Alpha, + ReflectionV1AlphaService.class); } } + private void addSingleBuiltinService(String serviceName, T impl, Class interfaceClass) { + ModuleModel internalModule = ApplicationModel.defaultModel().getInternalModule(); + URL url = new ServiceConfigURL(CommonConstants.TRIPLE, null, null, ANYHOST_VALUE, 0, + serviceName) + .addParameter(PROXY_KEY, CommonConstants.NATIVE_STUB) + .setScopeModel(internalModule); + Invoker invoker = proxyFactory.getInvoker(impl, interfaceClass, url); + pathResolver.add(serviceName, invoker); + internalModule.addDestroyListener(scopeModel -> pathResolver.remove(serviceName)); + } + public HealthStatusManager getHealthStatusManager() { return healthStatusManager; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto new file mode 100644 index 0000000000..8c5e06fe14 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto @@ -0,0 +1,144 @@ +// Copyright 2016 The gRPC Authors +// +// Licensed 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. +// Service exported by server reflection + + +// Warning: this entire file is deprecated. Use this instead: +// https://github.com/grpc/grpc-proto/blob/master/grpc/reflection/v1/reflection.proto + +syntax = "proto3"; + +package grpc.reflection.v1alpha; + +option deprecated = true; +option java_multiple_files = true; +option java_package = "io.grpc.reflection.v1alpha"; +option java_outer_classname = "ServerReflectionProto"; + +service ServerReflection { + // The reflection service is structured as a bidirectional stream, ensuring + // all related requests go to a single server. + rpc ServerReflectionInfo(stream ServerReflectionRequest) + returns (stream ServerReflectionResponse); +} + +// The message sent by the client when calling ServerReflectionInfo method. +message ServerReflectionRequest { + string host = 1; + // To use reflection service, the client should set one of the following + // fields in message_request. The server distinguishes requests by their + // defined field and then handles them using corresponding methods. + oneof message_request { + // Find a proto file by the file name. + string file_by_filename = 3; + + // Find the proto file that declares the given fully-qualified symbol name. + // This field should be a fully-qualified symbol name + // (e.g. .[.] or .). + string file_containing_symbol = 4; + + // Find the proto file which defines an extension extending the given + // message type with the given field number. + ExtensionRequest file_containing_extension = 5; + + // Finds the tag numbers used by all known extensions of extendee_type, and + // appends them to ExtensionNumberResponse in an undefined order. + // Its corresponding method is best-effort: it's not guaranteed that the + // reflection service will implement this method, and it's not guaranteed + // that this method will provide all extensions. Returns + // StatusCode::UNIMPLEMENTED if it's not implemented. + // This field should be a fully-qualified type name. The format is + // . + string all_extension_numbers_of_type = 6; + + // List the full names of registered services. The content will not be + // checked. + string list_services = 7; + } +} + +// The type name and extension number sent by the client when requesting +// file_containing_extension. +message ExtensionRequest { + // Fully-qualified type name. The format should be . + string containing_type = 1; + int32 extension_number = 2; +} + +// The message sent by the server to answer ServerReflectionInfo method. +message ServerReflectionResponse { + string valid_host = 1; + ServerReflectionRequest original_request = 2; + // The server set one of the following fields accroding to the message_request + // in the request. + oneof message_response { + // This message is used to answer file_by_filename, file_containing_symbol, + // file_containing_extension requests with transitive dependencies. As + // the repeated label is not allowed in oneof fields, we use a + // FileDescriptorResponse message to encapsulate the repeated fields. + // The reflection service is allowed to avoid sending FileDescriptorProtos + // that were previously sent in response to earlier requests in the stream. + FileDescriptorResponse file_descriptor_response = 4; + + // This message is used to answer all_extension_numbers_of_type requst. + ExtensionNumberResponse all_extension_numbers_response = 5; + + // This message is used to answer list_services request. + ListServiceResponse list_services_response = 6; + + // This message is used when an error occurs. + ErrorResponse error_response = 7; + } +} + +// Serialized FileDescriptorProto messages sent by the server answering +// a file_by_filename, file_containing_symbol, or file_containing_extension +// request. +message FileDescriptorResponse { + // Serialized FileDescriptorProto messages. We avoid taking a dependency on + // descriptor.proto, which uses proto2 only features, by making them opaque + // bytes instead. + repeated bytes file_descriptor_proto = 1; +} + +// A list of extension numbers sent by the server answering +// all_extension_numbers_of_type request. +message ExtensionNumberResponse { + // Full name of the base type, including the package name. The format + // is . + string base_type_name = 1; + repeated int32 extension_number = 2; +} + +// A list of ServiceResponse sent by the server answering list_services request. +message ListServiceResponse { + // The information of each service may be expanded in the future, so we use + // ServiceResponse message to encapsulate it. + repeated ServiceResponse service = 1; +} + +// The information of a single service used by ListServiceResponse to answer +// list_services request. +message ServiceResponse { + // Full name of a registered service, including its package name. The format + // is . + string name = 1; +} + +// The error code and error message sent by the server when an error occurs. +message ErrorResponse { + // This field uses the error codes defined in grpc::StatusCode. + int32 error_code = 1; + string error_message = 2; +} diff --git a/pom.xml b/pom.xml index afb9b5c1c5..54b105f0d9 100644 --- a/pom.xml +++ b/pom.xml @@ -331,6 +331,7 @@ **/com/google/rpc/*, **/generated/**/*, **/grpc/health/**/*, + **/grpc/reflection/**/*, **/target/**/*, **/*.json