From f555667b24abc3d052ddc9153d228ed9aff548e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Wed, 25 Aug 2021 11:37:13 +0200 Subject: [PATCH] gRPC: simplify adding headers to client calls fixes #19209 --- .../asciidoc/grpc-service-consumption.adoc | 38 +++++++++++++++++++ .../src/main/resources/MutinyClient.mustache | 8 ++++ .../java/io/quarkus/grpc/GrpcClientUtils.java | 38 +++++++++++++++++++ .../io/quarkus/grpc/runtime/MutinyClient.java | 1 + .../examples/hello/HelloWorldEndpoint.java | 10 ++--- 5 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/GrpcClientUtils.java diff --git a/docs/src/main/asciidoc/grpc-service-consumption.adoc b/docs/src/main/asciidoc/grpc-service-consumption.adoc index 8c2beb2dee904..3670cc6c46f9d 100644 --- a/docs/src/main/asciidoc/grpc-service-consumption.adoc +++ b/docs/src/main/asciidoc/grpc-service-consumption.adoc @@ -253,6 +253,44 @@ quarkus.grpc.clients.hello.deadline=2s <1> ---- <1> Set the deadline for all injected clients. +== gRPC Headers +Similarly to HTTP, alongside the message, gRPC calls can carry headers. +Headers can be useful e.g. for authentication. + +To set headers for a gRPC call, create a client with headers attached and then perform the call on this client: +[source,java] +---- +import javax.enterprise.context.ApplicationScoped; + +import examples.Greeter; +import examples.HelloReply; +import examples.HelloRequest; +import io.grpc.Metadata; +import io.quarkus.grpc.GrpcClient; +import io.quarkus.grpc.GrpcClientUtils; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + @GrpcClient + Greeter client; + + public Uni doTheCall() { + Metadata extraHeaders = new Metadata(); + if (headers) { + extraHeaders.put("my-header", "my-interface-value"); + } + + Greeter alteredClient = GrpcClientUtils.attachHeaders(client, extraHeaders); // <1> + + return alteredClient.sayHello(HelloRequest.newBuilder().setName(name).build()); // <2> + } +} +---- +<1> Alter the client to make calls with the `extraHeaders` attached +<2> Perform the call with the altered client. The original client remains unmodified + +`GrpcClientUtils` work with all flavors of clients. == Client Interceptors diff --git a/extensions/grpc/protoc/src/main/resources/MutinyClient.mustache b/extensions/grpc/protoc/src/main/resources/MutinyClient.mustache index 82440fe249359..9b67ec321cd54 100644 --- a/extensions/grpc/protoc/src/main/resources/MutinyClient.mustache +++ b/extensions/grpc/protoc/src/main/resources/MutinyClient.mustache @@ -17,6 +17,14 @@ public class {{serviceName}}Client implements {{serviceName}}, MutinyClient<{{cl this.stub = stubConfigurator.apply(name,{{classPrefix}}{{serviceName}}Grpc.new{{classPrefix}}Stub(channel)); } + private {{serviceName}}Client({{classPrefix}}{{serviceName}}Grpc.{{classPrefix}}{{serviceName}}Stub stub) { + this.stub = stub; + } + + public {{serviceName}}Client cloneWith({{classPrefix}}{{serviceName}}Grpc.{{classPrefix}}{{serviceName}}Stub stub) { + return new {{serviceName}}Client(stub); + } + @Override public {{classPrefix}}{{serviceName}}Grpc.{{classPrefix}}{{serviceName}}Stub getStub() { return stub; diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/GrpcClientUtils.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/GrpcClientUtils.java new file mode 100644 index 0000000000000..ef4e398292399 --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/GrpcClientUtils.java @@ -0,0 +1,38 @@ +package io.quarkus.grpc; + +import io.grpc.Metadata; +import io.grpc.stub.AbstractStub; +import io.grpc.stub.MetadataUtils; +import io.quarkus.grpc.runtime.MutinyClient; + +/** + * gRPC client utilities + */ +public class GrpcClientUtils { + + /** + * Attach headers to a gRPC client. + * + * To make a call with headers, first invoke this method and then perform the intended call with the returned client + * + * @param client any kind of gRPC client + * @param extraHeaders headers to attach + * @param type of the client + * @return a client with headers attached + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static T attachHeaders(T client, Metadata extraHeaders) { + if (client == null) { + throw new NullPointerException("Cannot attach headers to a null client"); + } + if (client instanceof AbstractStub) { + return (T) MetadataUtils.attachHeaders((AbstractStub) client, extraHeaders); + } else if (client instanceof MutinyClient) { + MutinyClient mutinyClient = (MutinyClient) client; + AbstractStub stub = MetadataUtils.attachHeaders(mutinyClient.getStub(), extraHeaders); + return (T) ((MutinyClient) client).cloneWith(stub); + } else { + throw new IllegalArgumentException("Unsupported client type " + client.getClass()); + } + } +} diff --git a/extensions/grpc/stubs/src/main/java/io/quarkus/grpc/runtime/MutinyClient.java b/extensions/grpc/stubs/src/main/java/io/quarkus/grpc/runtime/MutinyClient.java index 5ac160b61d49e..a431db47dc021 100644 --- a/extensions/grpc/stubs/src/main/java/io/quarkus/grpc/runtime/MutinyClient.java +++ b/extensions/grpc/stubs/src/main/java/io/quarkus/grpc/runtime/MutinyClient.java @@ -13,4 +13,5 @@ public interface MutinyClient> { */ T getStub(); + MutinyClient cloneWith(T stub); } diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java index 625c55b5bee63..c7d566b4a2807 100644 --- a/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java +++ b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java @@ -16,14 +16,13 @@ import javax.ws.rs.core.MediaType; import examples.Greeter; -import examples.GreeterClient; import examples.GreeterGrpc; import examples.HelloReply; import examples.HelloRequest; import examples.MutinyGreeterGrpc; import io.grpc.Metadata; -import io.grpc.stub.MetadataUtils; import io.quarkus.grpc.GrpcClient; +import io.quarkus.grpc.GrpcClientUtils; import io.smallrye.mutiny.Uni; @Path("/hello") @@ -48,7 +47,7 @@ public String helloBlocking(@PathParam("name") String name, @QueryParam("headers if (headers) { extraHeaders.put(EXTRA_BLOCKING_HEADER, "my-blocking-value"); } - HelloReply reply = MetadataUtils.attachHeaders(blockingHelloClient, extraHeaders) + HelloReply reply = GrpcClientUtils.attachHeaders(blockingHelloClient, extraHeaders) .sayHello(HelloRequest.newBuilder().setName(name).build()); return generateResponse(reply); @@ -61,7 +60,7 @@ public Uni helloMutiny(@PathParam("name") String name, @QueryParam("head if (headers) { extraHeaders.put(EXTRA_HEADER, "my-extra-value"); } - MutinyGreeterGrpc.MutinyGreeterStub alteredClient = MetadataUtils.attachHeaders(mutinyHelloClient, extraHeaders); + MutinyGreeterGrpc.MutinyGreeterStub alteredClient = GrpcClientUtils.attachHeaders(mutinyHelloClient, extraHeaders); return alteredClient.sayHello(HelloRequest.newBuilder().setName(name).build()) .onItem().transform(this::generateResponse); } @@ -74,8 +73,7 @@ public Uni helloInterface(@PathParam("name") String name, @QueryParam("h extraHeaders.put(INTERFACE_HEADER, "my-interface-value"); } - MutinyGreeterGrpc.MutinyGreeterStub stub = ((GreeterClient) interfaceHelloClient).getStub(); - MutinyGreeterGrpc.MutinyGreeterStub alteredClient = MetadataUtils.attachHeaders(stub, extraHeaders); + Greeter alteredClient = GrpcClientUtils.attachHeaders(interfaceHelloClient, extraHeaders); return alteredClient.sayHello(HelloRequest.newBuilder().setName(name).build()) .onItem().transform(this::generateResponse);