Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grpc - added grpc transcoding feature #40254

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum Feature {
FLYWAY,
GRPC_CLIENT,
GRPC_SERVER,
GRPC_TRANSCODING,
HIBERNATE_ORM,
HIBERNATE_ENVERS,
HIBERNATE_ORM_PANACHE,
Expand Down
166 changes: 166 additions & 0 deletions docs/src/main/asciidoc/grpc-transcoding.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Using gRPC Transcoding

Check warning on line 6 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Using gRPC Transcoding'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Using gRPC Transcoding'.", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 6, "column": 3}}}, "severity": "INFO"}

Check warning on line 6 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'?", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 6, "column": 14}}}, "severity": "WARNING"}
include::_attributes.adoc[]
:categories: serialization
:summary: This page explains how to enable gRPC Transcoding in your Quarkus application for RESTful interactions with gRPC services.

Check warning on line 9 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'?", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 9, "column": 49}}}, "severity": "WARNING"}
:topics: grpc, transcoding, rest, json
:extensions: io.quarkus:quarkus-grpc

gRPC Transcoding lets you expose your gRPC services as RESTful JSON endpoints.

Check warning on line 13 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'?", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 13, "column": 6}}}, "severity": "WARNING"}

Check warning on line 13 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 13, "column": 53}}}, "severity": "INFO"}
This is particularly useful in these scenarios:

1. **Client-side limitations:** When you need to interact with gRPC services from environments (like web browsers) that don't directly support gRPC.

Check warning on line 16 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 16, "column": 42}}}, "severity": "INFO"}
2. **Simplified local development:** While services like Google Cloud Run and Google Cloud Endpoints offer built-in gRPC transcoding, replicating this locally often requires setting up a proxy like Envoy. Transcoding directly within your Quarkus application streamlines your development process.

Check warning on line 17 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'transcoding'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'transcoding'?", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 17, "column": 122}}}, "severity": "WARNING"}

Check warning on line 17 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Transcoding'?", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 17, "column": 206}}}, "severity": "WARNING"}

== Configuring Your Project

Check warning on line 19 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Configuring Your Project'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Configuring Your Project'.", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 19, "column": 4}}}, "severity": "INFO"}

First, add the `quarkus-grpc` extension to your project:

[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"]
.pom.xml
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-grpc</artifactId>
</dependency>
----

[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"]
.build.gradle

Check warning on line 33 in docs/src/main/asciidoc/grpc-transcoding.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.CaseSensitiveTerms] Use 'Gradle' rather than 'gradle'. Raw Output: {"message": "[Quarkus.CaseSensitiveTerms] Use 'Gradle' rather than 'gradle'.", "location": {"path": "docs/src/main/asciidoc/grpc-transcoding.adoc", "range": {"start": {"line": 33, "column": 8}}}, "severity": "INFO"}
----
implementation("io.quarkus:quarkus-grpc")
----

== Transcoding configuration

include::{generated-dir}/config/quarkus-grpc-transcoding-config-grpc-transcoding-config.adoc[opts=optional,leveloffset=+1]

== Example

Let's imagine you have a gRPC service defined.
Here's an example of a simple service:

[source,protobuf]
----
syntax = "proto3";

import "google/api/annotations.proto"; //<1>

option java_multiple_files = true;
option java_package = "examples";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
// RPC with simple path
rpc SimplePath (HelloRequest) returns (HelloReply) {
option (google.api.http) = { //<2>
post: "/v1/simple"
body: "*"
};
}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}
----

<1> We need to import the `google/api/annotations.proto` file so that we can use the `google.api.http` option.
<2> This option is used to define the RESTful path for the gRPC service.

Now we need to implement the service:

[source,java]
----
@GrpcService
public class HelloWorldNewService implements Greeter {

@Override
public Uni<HelloReply> simplePath(HelloRequest request) {
return Uni.createFrom().item(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
}
}
----

To enable gRPC Transcoding, you need to add the following configuration to your `application.properties` file:

[source,properties]
----
quarkus.grpc.transcoding.enabled=true
----

Now you can access the gRPC service through a RESTful JSON interface.
For example, you can use the following `curl` command:

[source,shell]
----
curl -X POST http://localhost:8080/v1/simple -H "Content-Type: application/json" -d '{"name": "World"}'
----

This command should return response similar to the following:

[source,json]
----
{
"message": "Hello World"
}
----

== Advanced Usage

While the above example demonstrates a simple use case, gRPC Transcoding can be configured in more complex scenarios.
For example, you can define paths with variables, query parameters, and more.

For example you can define methods with path variables:

[source,protobuf]
----
service Greeter {
rpc PathWithVariable (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/path/{name}" //<1>
body: "*"
};
}
}

message HelloRequest {
string name = 1; //<2>
}
----

<1> The path variable is defined using curly braces.
<2> The `name` field is used to define the path variable.

Now if you send a request to the `/v1/path/World` path, like this:

[source,shell]
----
curl -X POST http://localhost:8080/v1/path/World
----

You should receive a response similar to the following:

[source,json]
----
{
"message": "Hello World"
}
----

**Important Notes:**

* You also should consult https://cloud.google.com/endpoints/docs/grpc/transcoding[google's documentation on gRPC transcoding] for more information.
* Consider whether you need a proxy like Envoy for advanced transcoding and routing.
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/grpc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ Quarkus gRPC is based on https://vertx.io/docs/vertx-grpc/java/[Vert.x gRPC].
* xref:grpc-service-consumption.adoc[Consuming a gRPC Service]
* xref:grpc-kubernetes.adoc[Deploying your gRPC Service in Kubernetes]
* xref:grpc-xds.adoc[Enabling xDS gRPC support]
* xref:grpc-transcoding.adoc[Enabling gRPC transcoding support]
* xref:grpc-generation-reference.adoc[gRPC code generation reference guide]
* xref:grpc-reference.adoc[gRPC reference guide]
4 changes: 4 additions & 0 deletions extensions/grpc-common/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-common-protos</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-grpc</artifactId>
Expand Down
7 changes: 6 additions & 1 deletion extensions/grpc/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf-java.version}</version>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
</dependencies>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.quarkus.grpc;

import com.google.protobuf.Message;

public interface GrpcTranscoding {

String getGrpcServiceName();

<Req extends Message, Resp extends Message> GrpcTranscodingDescriptor<Req, Resp> findTranscodingDescriptor(
String methodName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.grpc;

public class GrpcTranscodingDescriptor<Req extends com.google.protobuf.Message, Resp extends com.google.protobuf.Message> {

private final GrpcTranscodingMarshaller<Req> requestMarshaller;
private final GrpcTranscodingMarshaller<Resp> responseMarshaller;

public GrpcTranscodingDescriptor(GrpcTranscodingMarshaller<Req> requestMarshaller,
GrpcTranscodingMarshaller<Resp> responseMarshaller) {
this.requestMarshaller = requestMarshaller;
this.responseMarshaller = responseMarshaller;
}

public GrpcTranscodingMarshaller<Req> getRequestMarshaller() {
return requestMarshaller;
}

public GrpcTranscodingMarshaller<Resp> getResponseMarshaller() {
return responseMarshaller;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.quarkus.grpc;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import org.jboss.logging.Logger;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;

import io.grpc.MethodDescriptor;
import io.grpc.Status;

public class GrpcTranscodingMarshaller<T extends Message> implements MethodDescriptor.PrototypeMarshaller<T> {

private final static Logger log = Logger.getLogger(GrpcTranscodingMarshaller.class);

private final T defaultInstance;

public GrpcTranscodingMarshaller(T defaultInstance) {
this.defaultInstance = checkNotNull(defaultInstance, "defaultInstance cannot be null");
}

@SuppressWarnings("unchecked")
@Override
public Class<T> getMessageClass() {
return (Class<T>) defaultInstance.getClass();
}

@Override
public T getMessagePrototype() {
return defaultInstance;
}

@Override
public InputStream stream(T value) {
try {
String response = JsonFormat.printer().omittingInsignificantWhitespace().print(value);
return new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
} catch (InvalidProtocolBufferException e) {
throw Status.INTERNAL.withDescription("Unable to convert message to JSON").withCause(e).asRuntimeException();
}
}

@SuppressWarnings("unchecked")
@Override
public T parse(InputStream stream) {
try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
Message.Builder builder = defaultInstance.newBuilderForType();
JsonFormat.parser().merge(reader, builder);
return (T) builder.build();
} catch (InvalidProtocolBufferException e) {
log.error("Unable to parse JSON to message", e);
throw Status.INTERNAL.withDescription("Unable to parse JSON to message").withCause(e).asRuntimeException();
} catch (IOException e) {
log.error("An I/O error occurred while parsing the stream", e);
throw Status.INTERNAL.withDescription("An I/O error occurred while parsing the stream").withCause(e)
.asRuntimeException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.grpc;

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GrpcTranscodingMethod {

String grpcMethodName();

String httpMethod();

String httpPath();
}
Loading
Loading