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

Add support for gRPC transcoding #122

Merged
merged 17 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2d07f73
Add support for gRPC transcoding
zZHorizonZz Jan 22, 2025
337802a
Remove duplicate transcoding test class
zZHorizonZz Jan 23, 2025
6ec2791
Optimized PathMatcherResult lookup
zZHorizonZz Jan 23, 2025
0a66f73
Refactor ServiceTranscodingOptions and PathMatcher classes for improv…
zZHorizonZz Jan 25, 2025
634d638
Remove unnecessary changes in the grpc common
zZHorizonZz Jan 25, 2025
78d93f3
Rename ServiceTranscodingOptions to MethodTranscodingOptions and expa…
zZHorizonZz Jan 27, 2025
8b6e8d9
Rename ServiceTranscodingOptions to MethodTranscodingOptions and expa…
zZHorizonZz Jan 27, 2025
14e93bb
Added MessageWeaver and added error http responses
zZHorizonZz Jan 27, 2025
f1d06f7
Fixed a issue that caused request to hang when there was no body in r…
zZHorizonZz Jan 28, 2025
012d75f
Fixed a potential issue with duplicate call
zZHorizonZz Jan 28, 2025
eb00511
Fixed issue with generator not being able to read annotations and gen…
zZHorizonZz Jan 30, 2025
841f9f3
Add author attribution to classes based on grpc-httpjson-transcoding
zZHorizonZz Jan 30, 2025
2676b63
Enhance transcoding tests and add additional grpc methods with transc…
zZHorizonZz Feb 6, 2025
556b6a5
Add new transcoding tests and enhance existing ones for various gRPC …
zZHorizonZz Feb 6, 2025
85789f6
Remove unnecessary formatting changes
zZHorizonZz Feb 7, 2025
3bee5be
Refactor MessageWeaver and PercentEncoding for improved JSON handling…
zZHorizonZz Feb 11, 2025
f0a9e8c
Implement error handling for transcoding and add corresponding tests
zZHorizonZz Feb 12, 2025
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
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@

<modules>
<module>vertx-grpc-common</module>
<module>vertx-grpc-transcoding</module>
<module>vertx-grpc-server</module>
<module>vertx-grpc-client</module>
<module>vertx-grpcio-common</module>
Expand Down Expand Up @@ -271,4 +272,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public GrpcClientResponseImpl(ContextInternal context,
long maxMessageSize,
GrpcStatus status,
HttpClientResponse httpResponse, GrpcMessageDecoder<Resp> messageDecoder) {
super(context, httpResponse, httpResponse.headers().get("grpc-encoding"), format, maxMessageSize, messageDecoder);
super(context, httpResponse, httpResponse.headers().get("grpc-encoding"), format, false, maxMessageSize, messageDecoder);
this.request = request;
this.httpResponse = httpResponse;
this.status = status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ public Buffer payload() {
private final GrpcMessageDecoder<T> messageDecoder;
private final Promise<Void> end;
private GrpcWriteStreamBase<?, ?> ws;
private boolean transcodable;

protected GrpcReadStreamBase(Context context,
ReadStream<Buffer> stream,
String encoding,
WireFormat format,
boolean transcodable,
long maxMessageSize,
GrpcMessageDecoder<T> messageDecoder) {
ContextInternal ctx = (ContextInternal) context;
Expand Down Expand Up @@ -95,12 +97,18 @@ protected void handleMessage(GrpcMessage msg) {
};
this.messageDecoder = messageDecoder;
this.end = ctx.promise();
this.transcodable = transcodable;
}

public void init(GrpcWriteStreamBase<?, ?> ws) {
this.ws = ws;
stream.handler(this);
stream.endHandler(v -> queue.write(END_SENTINEL));
stream.endHandler(v -> {
if (transcodable && last == null) {
handle(Buffer.buffer());
}
queue.write(END_SENTINEL);
});
stream.exceptionHandler(err -> {
if (err instanceof StreamResetException) {
StreamResetException reset = (StreamResetException) err;
Expand Down Expand Up @@ -267,6 +275,10 @@ public final void tryFail(Throwable err) {
}
}

protected final void cancelTranscodable() {
transcodable = false;
}

protected final void handleException(Throwable err) {
tryFail(err);
}
Expand Down
56 changes: 56 additions & 0 deletions vertx-grpc-docs/src/main/asciidoc/plugin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,59 @@ Vert.x gRPC needs to know to interact with gRPC.
- the message encoder

They can be used to bind services or interact with a remote server.

=== Generate transcoding definitions

The plugin can also generate transcoding definitions for the gRPC services. For more information see the
xref:server.adoc#_grpc_transcoding[Transcoding] section.

==== Generate transcoding definitions

Currently, the plugin supports generating transcoding definitions for the gRPC services via https://github.com/googleapis/api-common-protos/blob/main/google/api/http.proto[http.proto]. This feature is enabled by default.

Example of the gRPC transcoding definition:

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

import "google/api/http.proto";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/hello"
};
}

rpc SayHelloAgain (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/hello/{name}"
};
}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

----

To test if the transcoding is working correctly, you can use the `curl` command:

[source]
----
curl -X POST -H "Content-Type: application/json" -d '{"name":"vert.x"}' http://localhost:8080/v1/hello
----

And for the `SayHelloAgain` method:

[source]
----
curl -X POST -H "Content-Type: application/json" http://localhost:8080/v1/hello/vert.x
----

84 changes: 84 additions & 0 deletions vertx-grpc-docs/src/main/asciidoc/server.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,90 @@ router.route("/com.mycompany.MyService/*").handler(corsHandler);
----
====

=== gRPC Transcoding

gRPC transcoding is a feature that enables mapping between HTTP/JSON requests and gRPC services.

==== What is gRPC Transcoding?

gRPC transcoding allows your services to accept both gRPC and HTTP/JSON requests, providing greater flexibility. This feature is particularly useful when:

* You want to expose your gRPC services to clients that don't support gRPC
* You need to support both traditional REST APIs and gRPC endpoints
* You want to leverage gRPC's efficiency while maintaining HTTP/JSON compatibility

==== Configuration

==== Enabling Transcoding

By default, gRPC transcoding is disabled in Vert.x. To enable it:

[source,java]
----
GrpcServerOptions options = new GrpcServerOptions().setGrpcTranscodingEnabled(true);

GrpcServer server = GrpcServer.server(vertx, options);
----

==== Creating Bindings

To create HTTP/gRPC bindings:

1. Create a `MethodTranscodingOptions` instance
2. Configure the binding options
3. Pass the options when registering the handler

[source,java]
----
import io.vertx.grpc.server.GrpcServer;
import io.vertx.grpc.server.GrpcServerOptions;
import io.vertx.grpc.transcoding.MethodTranscodingOptions;
import io.vertx.grpc.common.ServiceMethod;
import io.vertx.grpc.common.GrpcMessageDecoder;
import io.vertx.grpc.common.GrpcMessageEncoder;
import io.vertx.core.http.HttpMethod;

public class GrpcServerExamples {

// The encoder and decoder for the service method. Must be provided by the user and must be able to decode/encode the from wire format.
public static GrpcMessageDecoder<GetUserRequest> DECODER = GrpcMessageDecoder.json(GetUserRequest::newBuilder);
public static GrpcMessageEncoder<GetUserResponse> ENCODER = GrpcMessageEncoder.json();

public GrpcServer createTranscodingServer() {
// Create a new gRPC server with transcoding enabled
GrpcServer server = GrpcServer.server(vertx, new GrpcServerOptions().setGrpcTranscodingEnabled(true));

// Define the service method
ServiceMethod<GetUserRequest, GetUserResponse> getUser = ServiceMethod.server("UserService", "GetUser", ENCODER, DECODER);

// Define the transcoding options
MethodTranscodingOptions transcodingOptions = new MethodTranscodingOptions(null, HttpMethod.GET, "/v1/users/{id}", "*", null, null);

// Register the handler with transcoding options
server.callHandlerWithTranscoding(
getUser,
request -> {
// Handle the request
},
transcodingOptions
);

return server;
}
}
----

==== Testing Transcoding

To test gRPC transcoding, you can use a tool like `curl` to send HTTP requests to your gRPC service.

For example, to send a `GET` request to the `/v1/users/123` endpoint:

[source]
----
curl -X GET http://localhost:8080/v1/users/123
----

=== Server request/response API

The gRPC request/response server API provides an alternative way to interact with a client without the need of extending
Expand Down
12 changes: 12 additions & 0 deletions vertx-grpc-docs/src/main/java/examples/VertxGreeterGrpcServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.WriteStream;
Expand All @@ -13,6 +14,7 @@
import io.vertx.grpc.common.GrpcWriteStream;
import io.vertx.grpc.common.GrpcMessageDecoder;
import io.vertx.grpc.common.GrpcMessageEncoder;
import io.vertx.grpc.transcoding.MethodTranscodingOptions;
import io.vertx.grpc.server.GrpcServerResponse;
import io.vertx.grpc.server.GrpcServer;

Expand All @@ -32,6 +34,7 @@ public class VertxGreeterGrpcServer {
GrpcMessageEncoder.json(),
GrpcMessageDecoder.json(() -> examples.HelloRequest.newBuilder()));


public static class GreeterApi {

public Future<examples.HelloReply> sayHello(examples.HelloRequest request) {
Expand Down Expand Up @@ -75,6 +78,7 @@ public GreeterApi bind_sayHello(GrpcServer server, io.vertx.grpc.common.WireForm
return this;
}


public final GreeterApi bindAll(GrpcServer server) {
bind_sayHello(server);
return this;
Expand All @@ -84,5 +88,13 @@ public final GreeterApi bindAll(GrpcServer server, io.vertx.grpc.common.WireForm
bind_sayHello(server, format);
return this;
}

public final GreeterApi bindAllWithTranscoding(GrpcServer server) {
return this;
}

public final GreeterApi bindAllWithTranscoding(GrpcServer server, io.vertx.grpc.common.WireFormat format) {
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.WriteStream;
Expand All @@ -13,6 +14,7 @@
import io.vertx.grpc.common.GrpcWriteStream;
import io.vertx.grpc.common.GrpcMessageDecoder;
import io.vertx.grpc.common.GrpcMessageEncoder;
import io.vertx.grpc.transcoding.MethodTranscodingOptions;
import io.vertx.grpc.server.GrpcServerResponse;
import io.vertx.grpc.server.GrpcServer;

Expand Down Expand Up @@ -52,6 +54,7 @@ public class VertxStreamingGrpcServer {
GrpcMessageEncoder.json(),
GrpcMessageDecoder.json(() -> examples.Item.newBuilder()));


public static class StreamingApi {

public ReadStream<examples.Item> source(examples.Empty request) {
Expand Down Expand Up @@ -163,6 +166,7 @@ public final StreamingApi bind_pipe(GrpcServer server, io.vertx.grpc.common.Wire
return this;
}


public final StreamingApi bindAll(GrpcServer server) {
bind_source(server);
bind_sink(server);
Expand All @@ -176,5 +180,13 @@ public final StreamingApi bindAll(GrpcServer server, io.vertx.grpc.common.WireFo
bind_pipe(server, format);
return this;
}

public final StreamingApi bindAllWithTranscoding(GrpcServer server) {
return this;
}

public final StreamingApi bindAllWithTranscoding(GrpcServer server, io.vertx.grpc.common.WireFormat format) {
return this;
}
}
}
Loading