diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 862fcb69b8ed64..7a3fcf85dabefd 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -2099,6 +2099,11 @@ quarkus-grpc-stubs ${project.version} + + io.quarkus + quarkus-grpc-reflection + ${project.version} + io.quarkus quarkus-grpc-deployment diff --git a/extensions/grpc/cli/pom.xml b/extensions/grpc/cli/pom.xml new file mode 100644 index 00000000000000..2c689bbfedacfd --- /dev/null +++ b/extensions/grpc/cli/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + quarkus-grpc-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-grpc-cli + Quarkus - gRPC - CLI + gRPC CLI + + + + io.vertx + vertx-grpc-client + + + com.google.protobuf + protobuf-java-util + + + io.quarkus + quarkus-grpc-reflection + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-picocli + + + io.quarkus + quarkus-arc + + + + + + io.quarkus + quarkus-maven-plugin + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + + + -parameters + + + + + + \ No newline at end of file diff --git a/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/DescribeCommand.java b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/DescribeCommand.java new file mode 100644 index 00000000000000..4103ea238eb6ce --- /dev/null +++ b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/DescribeCommand.java @@ -0,0 +1,44 @@ +package io.quarkus.grpc.cli; + +import java.util.List; + +import com.google.protobuf.ByteString; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.util.JsonFormat; + +import io.grpc.reflection.v1.MutinyServerReflectionGrpc; +import io.grpc.reflection.v1.ServerReflectionRequest; +import io.grpc.reflection.v1.ServerReflectionResponse; +import io.smallrye.mutiny.Multi; +import picocli.CommandLine; + +@CommandLine.Command(name = "describe", sortOptions = false, header = "grpc describe") +public class DescribeCommand extends GcurlBaseCommand { + + public String getAction() { + return "describe"; + } + + @Override + protected void execute(MutinyServerReflectionGrpc.MutinyServerReflectionStub stub) { + ServerReflectionRequest request = ServerReflectionRequest + .newBuilder() + .setFileContainingSymbol(unmatched.get(1)) + .build(); + Multi response = stub.serverReflectionInfo(Multi.createFrom().item(request)); + response.toUni().map(r -> { + List list = r.getFileDescriptorResponse().getFileDescriptorProtoList(); + for (ByteString bs : list) { + try { + DescriptorProtos.FileDescriptorProto fdp = DescriptorProtos.FileDescriptorProto.parseFrom(bs); + log(JsonFormat.printer().print(fdp)); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return null; + }).await().indefinitely(); + } +} diff --git a/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/GcurlBaseCommand.java b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/GcurlBaseCommand.java new file mode 100644 index 00000000000000..51be27f15269da --- /dev/null +++ b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/GcurlBaseCommand.java @@ -0,0 +1,132 @@ +package io.quarkus.grpc.cli; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.protobuf.ByteString; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ProtocolStringList; + +import io.grpc.Channel; +import io.grpc.reflection.v1.MutinyServerReflectionGrpc; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.net.SocketAddress; +import io.vertx.grpc.client.GrpcClient; +import io.vertx.grpc.client.GrpcClientChannel; +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +public abstract class GcurlBaseCommand implements Callable { + + @Spec + protected CommandSpec spec; + + @CommandLine.Unmatched + protected List unmatched; + + Vertx vertx = Vertx.vertx(); + + /** + * The grpc subcommand (e.g. list, describe, invoke) + * + * @return the subcommand + */ + protected abstract String getAction(); + + protected abstract void execute(MutinyServerReflectionGrpc.MutinyServerReflectionStub stub); + + protected void log(String msg) { + System.out.println(msg); + } + + protected void err(String msg) { + System.err.println(msg); + } + + protected static List getFileDescriptorsFromProtos(List protos) { + try { + Map all = protos + .stream() + .map(bs -> { + try { + return DescriptorProtos.FileDescriptorProto.parseFrom(bs); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toMap(DescriptorProtos.FileDescriptorProto::getName, Function.identity(), (a, b) -> a)); + List fds = new ArrayList<>(); + Map resolved = new HashMap<>(); + for (DescriptorProtos.FileDescriptorProto fdp : all.values()) { + fds.add(toFileDescriptor(fdp, all, resolved)); + } + return fds; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Descriptors.FileDescriptor toFileDescriptor(DescriptorProtos.FileDescriptorProto fdp, + Map all, Map resolved) { + int n = fdp.getDependencyCount(); + ProtocolStringList list = fdp.getDependencyList(); + Descriptors.FileDescriptor[] fds = new Descriptors.FileDescriptor[n]; + for (int i = 0; i < n; i++) { + String dep = list.get(i); + // remember resolved FDs, recursively resolve deps + fds[i] = resolved.computeIfAbsent(dep, key -> { + DescriptorProtos.FileDescriptorProto proto = all.get(key); + return toFileDescriptor(proto, all, resolved); + }); + } + try { + return Descriptors.FileDescriptor.buildFrom(fdp, fds); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException(e); + } + } + + @Override + public Integer call() { + if (unmatched == null || unmatched.isEmpty()) { + log("Missing host:port"); + return CommandLine.ExitCode.USAGE; + } + + return execute(channel -> { + try { + MutinyServerReflectionGrpc.MutinyServerReflectionStub stub = MutinyServerReflectionGrpc.newMutinyStub(channel); + execute(stub); + return CommandLine.ExitCode.OK; + } catch (Exception e) { + err("Failed to execute grpc " + getAction() + ", due to: " + e.getMessage()); + return CommandLine.ExitCode.SOFTWARE; + } + }); + } + + protected X execute(Function fn) { + HttpClientOptions options = new HttpClientOptions(); // TODO + options.setHttp2ClearTextUpgrade(false); + + GrpcClient client = GrpcClient.client(vertx, options); + String[] split = unmatched.get(0).split(":"); + String host = split[0]; + int port = Integer.parseInt(split[1]); + Channel channel = new GrpcClientChannel(client, SocketAddress.inetSocketAddress(port, host)); + try { + return fn.apply(channel); + } finally { + client.close().toCompletionStage().toCompletableFuture().join(); + } + } +} diff --git a/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/GrpcCommand.java b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/GrpcCommand.java new file mode 100644 index 00000000000000..18d5b16348eb67 --- /dev/null +++ b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/GrpcCommand.java @@ -0,0 +1,27 @@ +package io.quarkus.grpc.cli; + +import java.util.concurrent.Callable; + +import io.quarkus.picocli.runtime.annotations.TopCommand; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +@TopCommand +@Command(name = "grpc", sortOptions = false, header = "grpc CLI", subcommands = { + ListCommand.class, DescribeCommand.class, InvokeCommand.class }) +public class GrpcCommand implements Callable { + + @Spec + protected CommandSpec spec; + + @CommandLine.Option(names = { "-h", "--help" }, usageHelp = true, description = "Display this help message.") + public boolean help; + + @Override + public Integer call() { + CommandLine schemaCommand = spec.subcommands().get("list"); + return schemaCommand.execute(); + } +} \ No newline at end of file diff --git a/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/InvokeCommand.java b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/InvokeCommand.java new file mode 100644 index 00000000000000..08b22962964965 --- /dev/null +++ b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/InvokeCommand.java @@ -0,0 +1,120 @@ +package io.quarkus.grpc.cli; + +import java.util.List; +import java.util.Optional; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; + +import io.grpc.CallOptions; +import io.grpc.MethodDescriptor; +import io.grpc.protobuf.ProtoUtils; +import io.grpc.reflection.v1.MutinyServerReflectionGrpc; +import io.grpc.reflection.v1.ServerReflectionRequest; +import io.grpc.reflection.v1.ServerReflectionResponse; +import io.grpc.stub.ClientCalls; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.infrastructure.Infrastructure; +import picocli.CommandLine; + +@CommandLine.Command(name = "invoke", sortOptions = false, header = "grpc invoke") +public class InvokeCommand extends GcurlBaseCommand { + + @CommandLine.Option(names = { "-d" }, description = "Request input") + Optional content; + + public String getAction() { + return "invoke"; + } + + @Override + protected void execute(MutinyServerReflectionGrpc.MutinyServerReflectionStub stub) { + String toInvoke = unmatched.get(1); + String[] split = toInvoke.split("/"); + String serviceName = split[0]; + String methodName = split[1]; + ServerReflectionRequest request = ServerReflectionRequest + .newBuilder() + .setFileContainingSymbol(serviceName) + .build(); + Multi response = stub.serverReflectionInfo(Multi.createFrom().item(request)); + response.emitOn(Infrastructure.getDefaultWorkerPool()).toUni().map(r -> { + ServerReflectionResponse.MessageResponseCase responseCase = r.getMessageResponseCase(); + if (responseCase == ServerReflectionResponse.MessageResponseCase.FILE_DESCRIPTOR_RESPONSE) { + List byteStrings = r.getFileDescriptorResponse().getFileDescriptorProtoList(); + for (Descriptors.FileDescriptor fd : getFileDescriptorsFromProtos(byteStrings)) { + fd.getServices().forEach( + sd -> { + String fullName = sd.getFullName(); + if (fullName.equals(serviceName)) { + Descriptors.MethodDescriptor md = sd.findMethodByName(methodName); + if (md != null) { + invokeMethod(md); + } else { + log("Method not found: " + methodName); + } + } + }); + } + } else { + err("Unexpected response from server reflection: " + responseCase); + } + return null; + }).await().indefinitely(); + } + + private void invokeMethod(Descriptors.MethodDescriptor md) { + String fullMethodName = md.getService().getFullName() + "/" + md.getName(); + log("Invoking method: " + fullMethodName); + Descriptors.Descriptor inputType = md.getInputType(); + DynamicMessage.Builder messageBuilder = DynamicMessage.newBuilder(inputType); + try { + content.ifPresent(request -> { + try { + JsonFormat.parser().merge(request, messageBuilder); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + }); + DynamicMessage msg = messageBuilder.build(); + MethodDescriptor.MethodType methodType = MethodDescriptor.MethodType.UNARY; + if (md.isClientStreaming()) { + methodType = MethodDescriptor.MethodType.CLIENT_STREAMING; + } + if (md.isServerStreaming()) { + methodType = MethodDescriptor.MethodType.SERVER_STREAMING; + } + if (md.isClientStreaming() && md.isServerStreaming()) { + methodType = MethodDescriptor.MethodType.BIDI_STREAMING; + } + MethodDescriptor methodDescriptor = io.grpc.MethodDescriptor + . newBuilder() + .setType(methodType) + .setFullMethodName(fullMethodName) + .setRequestMarshaller(ProtoUtils.marshaller(DynamicMessage.getDefaultInstance(inputType))) + .setResponseMarshaller( + ProtoUtils.marshaller(DynamicMessage.getDefaultInstance(md.getOutputType()))) + .build(); + + execute(channel -> { + DynamicMessage response = ClientCalls.blockingUnaryCall( + channel, + methodDescriptor, + CallOptions.DEFAULT, + msg); + + try { + log(JsonFormat.printer().print(response)); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + return null; + }); + } catch (Exception e) { + err("Error creating dynamic message: " + e.getMessage()); + } + } +} diff --git a/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/ListCommand.java b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/ListCommand.java new file mode 100644 index 00000000000000..2999fdb9b08fa4 --- /dev/null +++ b/extensions/grpc/cli/src/main/java/io/quarkus/grpc/cli/ListCommand.java @@ -0,0 +1,34 @@ +package io.quarkus.grpc.cli; + +import java.util.List; + +import io.grpc.reflection.v1.MutinyServerReflectionGrpc; +import io.grpc.reflection.v1.ServerReflectionRequest; +import io.grpc.reflection.v1.ServerReflectionResponse; +import io.grpc.reflection.v1.ServiceResponse; +import io.smallrye.mutiny.Multi; +import picocli.CommandLine; + +@CommandLine.Command(name = "list", sortOptions = false, header = "grpc list") +public class ListCommand extends GcurlBaseCommand { + + public String getAction() { + return "list"; + } + + @Override + protected void execute(MutinyServerReflectionGrpc.MutinyServerReflectionStub stub) { + ServerReflectionRequest request = ServerReflectionRequest + .newBuilder() + .setListServices("dummy") + .build(); + Multi response = stub.serverReflectionInfo(Multi.createFrom().item(request)); + response.toUni().map(r -> { + List serviceList = r.getListServicesResponse().getServiceList(); + serviceList.forEach(sr -> { + log(sr.getName()); + }); + return null; + }).await().indefinitely(); + } +} diff --git a/extensions/grpc/cli/src/main/resources/application.properties b/extensions/grpc/cli/src/main/resources/application.properties new file mode 100644 index 00000000000000..d76bd2c707c29e --- /dev/null +++ b/extensions/grpc/cli/src/main/resources/application.properties @@ -0,0 +1,4 @@ +quarkus.log.level=INFO +quarkus.banner.enabled=false +quarkus.package.jar.type=uber-jar +quarkus.package.jar.add-runner-suffix=false \ No newline at end of file diff --git a/extensions/grpc/pom.xml b/extensions/grpc/pom.xml index da3eb3e1eed285..32c150f5b3c140 100644 --- a/extensions/grpc/pom.xml +++ b/extensions/grpc/pom.xml @@ -18,9 +18,11 @@ codegen api stubs + reflection deployment runtime xds inprocess + cli \ No newline at end of file diff --git a/extensions/grpc/reflection/pom.xml b/extensions/grpc/reflection/pom.xml new file mode 100644 index 00000000000000..c8e6d9fac8e619 --- /dev/null +++ b/extensions/grpc/reflection/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + quarkus-grpc-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-grpc-reflection + Quarkus - gRPC - Reflection + Reflection gRPC services and utils + + + io.quarkus + quarkus-grpc-api + + + io.quarkus + quarkus-grpc-stubs + + + * + * + + + + + io.grpc + grpc-protobuf + + + org.codehaus.mojo + animal-sniffer-annotations + + + org.checkerframework + checker-qual + + + com.google.guava + guava + + + com.google.errorprone + error_prone_annotations + + + + + io.grpc + grpc-stub + + + com.google.code.findbugs + jsr305 + + + org.codehaus.mojo + animal-sniffer-annotations + + + org.checkerframework + checker-qual + + + + + io.quarkus + quarkus-mutiny + + + diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/GrpcServerIndex.java b/extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/GrpcServerIndex.java similarity index 99% rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/GrpcServerIndex.java rename to extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/GrpcServerIndex.java index 5c1dece314e527..1786106fb1be46 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/GrpcServerIndex.java +++ b/extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/GrpcServerIndex.java @@ -1,4 +1,4 @@ -package io.quarkus.grpc.runtime.reflection; +package io.quarkus.grpc.reflection.service; import static com.google.protobuf.Descriptors.FileDescriptor; diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1.java b/extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/ReflectionServiceV1.java similarity index 99% rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1.java rename to extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/ReflectionServiceV1.java index 0f57ad617c4f01..1c668087de500d 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1.java +++ b/extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/ReflectionServiceV1.java @@ -1,4 +1,4 @@ -package io.quarkus.grpc.runtime.reflection; +package io.quarkus.grpc.reflection.service; import static com.google.protobuf.Descriptors.FileDescriptor; diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1alpha.java b/extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/ReflectionServiceV1alpha.java similarity index 99% rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1alpha.java rename to extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/ReflectionServiceV1alpha.java index 1caafa1a4425bf..a590fdfe2a95a1 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1alpha.java +++ b/extensions/grpc/reflection/src/main/java/io/quarkus/grpc/reflection/service/ReflectionServiceV1alpha.java @@ -1,4 +1,4 @@ -package io.quarkus.grpc.runtime.reflection; +package io.quarkus.grpc.reflection.service; import static com.google.protobuf.Descriptors.FileDescriptor; diff --git a/extensions/grpc/runtime/pom.xml b/extensions/grpc/runtime/pom.xml index 73393d614ff219..35442249814cc2 100644 --- a/extensions/grpc/runtime/pom.xml +++ b/extensions/grpc/runtime/pom.xml @@ -25,6 +25,10 @@ io.quarkus quarkus-grpc-common + + io.quarkus + quarkus-grpc-reflection + io.quarkus quarkus-vertx-http @@ -121,6 +125,12 @@ + + + src/main/resources + true + + io.quarkus diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java index 322cf5eb18db7c..dfcbd00be93d55 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java @@ -42,6 +42,8 @@ import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.Subclass; import io.quarkus.grpc.auth.GrpcSecurityInterceptor; +import io.quarkus.grpc.reflection.service.ReflectionServiceV1; +import io.quarkus.grpc.reflection.service.ReflectionServiceV1alpha; import io.quarkus.grpc.runtime.config.GrpcConfiguration; import io.quarkus.grpc.runtime.config.GrpcServerConfiguration; import io.quarkus.grpc.runtime.config.GrpcServerNettyConfig; @@ -49,8 +51,6 @@ import io.quarkus.grpc.runtime.devmode.GrpcHotReplacementInterceptor; import io.quarkus.grpc.runtime.devmode.GrpcServerReloader; import io.quarkus.grpc.runtime.health.GrpcHealthStorage; -import io.quarkus.grpc.runtime.reflection.ReflectionServiceV1; -import io.quarkus.grpc.runtime.reflection.ReflectionServiceV1alpha; import io.quarkus.grpc.runtime.supports.CompressionInterceptor; import io.quarkus.grpc.runtime.supports.blocking.BlockingServerInterceptor; import io.quarkus.grpc.spi.GrpcBuilderProvider; diff --git a/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 2a2f8152e8fe75..ad0bc59f0e4ce7 100644 --- a/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -16,3 +16,5 @@ metadata: artifact: "io.quarkus:quarkus-project-core-extension-codestarts" config: - "quarkus.grpc." + cli-plugins: + - "${project.groupId}:quarkus-grpc-cli:${project.version}" \ No newline at end of file diff --git a/integration-tests/grpc-vertx/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldNewService.java b/integration-tests/grpc-vertx/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldNewService.java index d41dcca08d0401..ea5d5d38ddf6e3 100644 --- a/integration-tests/grpc-vertx/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldNewService.java +++ b/integration-tests/grpc-vertx/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldNewService.java @@ -1,5 +1,7 @@ package io.quarkus.grpc.examples.hello; +import com.google.protobuf.Empty; + import examples.HelloReply; import examples.HelloRequest; import examples.MutinyGreeterGrpc; @@ -16,6 +18,12 @@ public Uni sayHello(HelloRequest request) { .map(res -> HelloReply.newBuilder().setMessage(res).build()); } + @Override + public Uni sayJo(Empty request) { + return Uni.createFrom().item("Jo!") + .map(res -> HelloReply.newBuilder().setMessage(res).build()); + } + @Override public Uni threadName(HelloRequest request) { return Uni.createFrom().item(Thread.currentThread().getName()) diff --git a/integration-tests/grpc-vertx/src/main/proto/helloworld.proto b/integration-tests/grpc-vertx/src/main/proto/helloworld.proto index c798d111640b48..6ae0904fb041d8 100644 --- a/integration-tests/grpc-vertx/src/main/proto/helloworld.proto +++ b/integration-tests/grpc-vertx/src/main/proto/helloworld.proto @@ -36,10 +36,14 @@ option objc_class_prefix = "HLW"; package helloworld; +// Import the empty message definition +import "google/protobuf/empty.proto"; + // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} + rpc SayJo (google.protobuf.Empty) returns (HelloReply) {} rpc ThreadName (HelloRequest) returns (HelloReply) {} }