From b1242b2b0576fa9f41efe54d1dc6757a2ee07d8d Mon Sep 17 00:00:00 2001 From: Ron Sigal Date: Fri, 19 Sep 2025 18:19:15 -0400 Subject: [PATCH 1/2] Added new blog and updated version of resteasy-grpc documentation --- .../2025-09-15-grpc-resteasy-beta-release.md | 550 ++++++++++++++++++ data/grpcreleases.yaml | 4 +- 2 files changed, 552 insertions(+), 2 deletions(-) create mode 100644 content/posts/2025-09-15-grpc-resteasy-beta-release.md diff --git a/content/posts/2025-09-15-grpc-resteasy-beta-release.md b/content/posts/2025-09-15-grpc-resteasy-beta-release.md new file mode 100644 index 0000000..b777810 --- /dev/null +++ b/content/posts/2025-09-15-grpc-resteasy-beta-release.md @@ -0,0 +1,550 @@ +--- layout: post title: "resteasy-grpc 1.0.0.Beta1 is available" +aliases: \[/news/2025/08/26/resteasy-grpc-1.0.0.Beta1-released\] date: +2025-08-26 tags: announcement release author: rsigal description: +resteasy-grpc 1.0.0.Beta1 is available. --- + +# RESTEasy gRPC to Jakarta REST Bridge project: first Beta release out + +The first beta releases (1.0.0.Beta1) of the **RESTEasy gRPC to Jakarta REST Bridge** +project (), aka +**resteasy-grpc**, and its sibling **gRPCtoJakartaREST-archetype** +project (1.0.0.Beta2) +() are now +available on Maven Central. A number of blogs have been written on the +projects themselves[^1] [^2][^3] and on their use in WildFly[^4][^5][^6], +so on this occasion we’ll limit ourselves to a general overview. + +The goal of resteasy-grpc is to bridge the semantic gap between the +worlds of gRPC and Jakarta REST so that a developer familiar with gRPC +can write a client that communicates with a Jakarta REST server. +resteasy-grpc contains module **grpc-bridge** which generates a set of +classes that are used at runtime, supported by the module +**grpc-bridge-runtime**. Given an existing Jakarta REST project, which +we call the **target project**, we want to extend it with the generated +classes and the runtime to a **bridge project** which, while still +accepting invocations from Jakarta REST clients, can also process gRPC +invocations. The transformation of the target project to the bridge +project is facilitated by the gRPCtoJakartaREST-archetype project. + +The semantics of the two worlds, gRPC and Jakarta REST, are considerably +different, so resteasy-grpc has to do some considerable lifting. The +goal is to be able to accommodate any valid Jakarta REST project. We’re +not there yet, but the first beta release implements a reasonable set of +constructs. + +We’ll go step by step. + +## Compile time + +A gRPC developer codes in the context of a protobuf message descriptor +file extended with gRPC rpc definitions, aka a **.proto file**, and it +is the responsibility of the resteasy-grpc compile time module to +generate a .proto file from the Jakarta REST server application. +Briefly, it + +1. scans a directory tree of Java classes looking for Jakarta REST + resource classes[^7]; + +2. for each resource class, it finds all of the resource methods and + locators; + +3. for each resource method and locator + + A. it creates an rpc definition, and + + B. for each entity parameter type and each return type, it creates + a message definition. + +A parameter is given at compile time which is used to prefix various +generated files. Here we’ll use "Example", so, for example, we’ll get +Example.proto. + +## Semantic disparities + +The foregoing algorithm passes over some serious semantic issues. Here +are a few. + +### Packages + +We incorporate Java package names into message names[^8]. For example, + + package dev.resteasy.grpc.example; + + public class X { + + private int i; + + public X(int i) {this.i = i;} + } + +is turned into + + // Type: dev.resteasy.grpc.example.X + message dev_resteasy_grpc_example___X { + int32 i = 1; + } + +### Inheritance + +protobuf has no notion of type inheritance, so we explicitly incorporate +fields from ancestor classes into a descendant class. For example, + + package dev.resteasy.grpc.example; + + public class Y extends X { + + private int j; + + public Y(int i, int j) { + super(i); + this.j = j; + } + } + +turns into + + // Type: dev.resteasy.grpc.example.Y + message dev_resteasy_grpc_example___Y { + int32 i = 1; + int32 j = 2; + } + +### Generic types + +protobuf has no notion of type variables and generic types, so we create +create distinct message types for generic types with different type +variable instantiations. For example, given + + package dev.resteasy.grpc.example; + + public class Generic { + T t; + } + +and + + @Path("m") + @GET + public void method(Generic gi, Generic gf) { + } + +we get[^9] + + // Type: dev.resteasy.grpc.example.Generic + message dev_resteasy_grpc_example___Generic46 { + int32 t = 1; + } + + // Type: dev.resteasy.grpc.example.Generic + message dev_resteasy_grpc_example___Generic83 { + float t = 1; + } + +For an "open" type like `Generic` or `Generic`, which `T` is a type variable, +we substitute `java.lang.Object`; i.e., `Generic`. + +### Arrays + +protobuf supports one dimensional arrays with the "repeated" keyword, +but it doesn’t support multidimensional or nullable arrays. First, +consider + + Integer[] intArray; + +In a separate arrays.proto file, included in all generated bridge +projects, we define + + message dev_resteasy_grpc_arrays___Integer___Array { + repeated sfixed32 int_field = 1; + } + + message dev_resteasy_grpc_arrays___Integer___wrapper { + oneof type { + dev_resteasy_grpc_arrays___NONE none_field = 1; + sfixed32 integer_field = 2; + } + } + +and + + message dev_resteasy_grpc_arrays___Integer___WArray { + repeated dev_resteasy_grpc_arrays___Integer___wrapper wrapper_field = 1; + } + +The type `dev_resteasy_grpc_arrays___Integer___Array` is the simpler +version, an integer array which is not nullable. To create a nullable +version we define `dev_resteasy_grpc_arrays___Integer___wrapper` which +can hold either 1) a special type `dev_resteasy_grpc_arrays___NONE` +which represents null, or 2) an integer. Then we define the type +`dev_resteasy_grpc_arrays___Integer___WArray` in which each element is +either null or an integer. + +Now, multidimensional arrays are defined by way of the recursively +defined `dev_resteasy_grpc_arrays___ArrayHolder`: + + message dev_resteasy_grpc_arrays___ArrayHolder { + oneof messageType { + ... + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___Array dev_resteasy_grpc_arrays___Integer___Array_field = 12; + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___WArray dev_resteasy_grpc_arrays___Integer___WArray_field = 13; + --- + dev_resteasy_grpc_arrays___ArrayHolder___WArray dev_resteasy_grpc_arrays___ArrayHolder___WArray_field = 21; + } + } + + message dev_resteasy_grpc_arrays___ArrayHolder___wrapper { + oneof type { + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___NONE none_field = 1; + dev_resteasy_grpc_arrays___ArrayHolder dev_resteasy_grpc_arrays___ArrayHolder_field = 2; + } + } + + message dev_resteasy_grpc_arrays___ArrayHolder___WArray { + string componentType = 1; + repeated dev_resteasy_grpc_arrays___ArrayHolder___wrapper wrapper___field = 2; + } + +Now, consider + + @Path("m2") + @GET + public Superclass[][] method2(Superclass[] sc) { + } + +Then `Superclass[]` is represented by +`dev_resteasy_grpc_example___Superclass___WArray`, and +`Superclass[][]` is represented by +`dev_resteasy_grpc_arrays___ArrayHolder___WArray`. + +### Collections and maps + +We take a simplifying approach to instances of `java.util.List`, +`java.util.Set`, `java.util.Map`, and `javax.ws.rs.core.MultivaluedMap`. +Implementations, e.g., `java.util.ArrayList`, can be quite complex for +reasons of efficiency and desired usage, but we choose to ignore those +implementation details. For example, `java.util.ArrayList` and +`java.util.LinkedList` will both be represented essentially the +same: + + // List: java.util.ArrayList + message java_util___ArrayList176 { + string classname = 1; + //java.lang.Integer + repeated int32 data = 2; + } + +and + + // List: java.util.LinkedList + message java_util___LinkedList177 { + string classname = 1; + //java.lang.Integer + repeated int32 data = 2; + } + +Similarly, `java.util.HashMap` would be represented as + + // Map: java.util.HashMap + message java_util___HashMap41 { + string classname = 1; + //java.lang.String->java.lang.Integer + message Pair { + string key = 2; + int32 value = 3; + } + repeated Pair data = 4; + } + +### HTTP + +Protobuf runs over HTTP/2, but it doesn’t expose much to the user in the +same way as Jakarta REST, so we define two message types to carry HTTP +information: + + message GeneralEntityMessage { + ServletInfo servletInfo = 1; + string URL = 2; + map headers = 3; + repeated gCookie cookies = 4; + string httpMethod = 5; + oneof messageType { + dev_resteasy_grpc_example___Generic46 dev_resteasy_grpc_example___Generic46_field = 6; + dev_resteasy_grpc_lists_sets___D137 dev_resteasy_grpc_lists_sets___D137_field = 7; + ... + } + } + +and + + message GeneralReturnMessage { + map headers = 1; + repeated gNewCookie cookies = 2; + int32 status = 3; + oneof messageType { + dev_resteasy_grpc_example___Subclass dev_resteasy_grpc_example___Subclass_field = 8; + java_util___ArrayList java_util___ArrayList_field = 9; + ... + } + } + +where `messageType` in `GeneralEntityMessage` and `GeneralReturnMessage` +can hold any of the entity types or return types, respectively. All `rpc` definitions use +these two types. For example: + + rpc SayHello (GeneralEntityMessage) returns (GeneralReturnMessage) {} + +## Runtime + +To understand what happens at runtime in a resteasy-grpc generated +bridge project, let’s start by looking at a pure gRPC example. In +particular, consider the "hello world" example in +. It starts with +[helloworld.proto](https://github.com/grpc/grpc-java/blob/master/examples/src/main/proto/helloworld.proto): + + syntax = "proto3"; + + option java_multiple_files = true; + option java_package = "io.grpc.examples.helloworld"; + option java_outer_classname = "HelloWorldProto"; + option objc_class_prefix = "HLW"; + + package helloworld; + + // The greeting service definition. + service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + } + + // The request message containing the user's name. + message HelloRequest { + string name = 1; + } + + // The response message containing the greetings + message HelloReply { + string message = 1; + } + +When the .proto file is compiled, the compiler produces a client side +stub with all of methods defined in the .proto file. The client +[HelloWorldClient.java](https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java) + + public void greet(String name) { + ... + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = stub.sayHello(request); + ... + } + +bridges the gap between Java and protobuf by using a +`io.grpc.examples.helloworld.HelloRequest$Builder` to create an +`io.grpc.examples.helloworld.HelloRequest`, which it passes to the stub +to invoke the matching method on the server. + +For the server side, compiling the .proto file creates a class like +`GreeterGrpc.GreeterImplBase` with no-op methods meant to be overridden. +For example, +[HelloWorldServer.java](https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java) +overrides `GreeterGrpc.GreeterImplBase`: + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + +It extracts a value from `HelloRequest` and uses a `HelloReply$Builder` +to create a response. + +The same thing happens in a bridge project generated by resteasy-grpc, +except that the messages in the generated .proto file represent Java +types defined in the original Jakarta REST target project. + +Consider the resource method + + @Path("m3") + @GET + public Y method3(Y y) { + return y; + } + +We can call it like this: + + dev_resteasy_grpc_example___Y.Builder yb = dev_resteasy_grpc_example___Y.newBuilder(); + dev_resteasy_grpc_example___Y y = yb.setI(3).setJ(7).build(); + + GeneralEntityMessage.Builder gemb = GeneralEntityMessage.newBuilder(); + GeneralEntityMessage gem = gemb.setDevResteasyGrpcExampleYField(y).build(); + GeneralReturnMessage response = stub.method3(gem); + Assertions.assertEquals(y, response.getDevResteasyGrpcExampleYField()); + +It’s structurally similar to `HelloWorldClient.java` except for the +extra step of creating a `GeneralEntityMessage`. + +Similarly, on the server side `ExampleServiceGrpcImpl` contains an +overriding method for each method in the .proto file. It’s structurally +similar to `sayHello()`, but it plays a different role. `sayHello()` +implements some business logic, but with resteasy-grpc we’re creating a +project in which the business logic already exists in the resource +methods of the target project. Instead, the function of the overriding +methods is to provide a bridge between the gRPC world and the Jakarta +REST world. + +For example, the overriding method for `method3()` would look like + + @java.lang.Override + public void method3(GeneralEntityMessage param, StreamObserver responseObserver) { + HttpServletRequest request = null; + try { + HttpServletResponseImpl response + = new HttpServletResponseImpl("dev_resteasy_grpc_example___Y", "sync", + Example_Server.getServletContext(), builder, fd); + GeneratedMessage actualParam = param.getDevResteasyGrpcExampleYField(); + request = getHttpServletRequest(param, actualParam, "/", response, "GET", "dev_resteasy_grpc_example___Y"); + HttpServletDispatcher servlet = getServlet(); + activateRequestContext(); + servlet.service(request.getMethod(), request, response); + MockServletOutputStream msos = (MockServletOutputStream) response.getOutputStream(); + ByteArrayOutputStream baos = msos.getDelegate(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + dev_resteasy_grpc_example___Y reply = dev_resteasy_grpc_example___Y.parseFrom(bais); + GeneralReturnMessage.Builder grmb = createGeneralReturnMessageBuilder(response); + grmb.setDevResteasyGrpcExampleYField(reply); + responseObserver.onNext(grmb.build()); + } catch (Exception e) { + responseObserver.onError(e); + } finally { + responseObserver.onCompleted(); + if (requestContextController != null) { + requestContextController.deactivate(); + } + if (tccl != null) { + Thread.currentThread().setContextClassLoader(tccl); + } + } + } + +Without going into the details, one of its responsibilities is to create +a suitable runtime environment for a Jakarta REST resource method. For +example, a CDI request context is activated. Another responsibility is +to take a protobuf value from the wire, translate it to the appropriate +Java class, and pass it as an entity value. Once the resource method +runs, its response is translated back to a protobuf message, stored in a +`GeneralReturnMessage`, and passed back to the gRPC runtime, which sends +it to the client. + +A couple of other generated classes are worth mentioning. + +Given an existing Jakarta REST application, we start with a set of Java +classes that occur as entity parameters or return values, turn them into +protobuf messages, and then compile the messages into Java classes. For +example, `dev.resteasy.grpc.example.X` is translated to the protobuf +message `dev_resteasy_grpc_example___X`. Then, when the .proto file is +compiled, `dev.resteasy.grpc.example.Example_proto.java` contains the +inner class `dev_resteasy_grpc_example___X`. We call these generated +Java classes **javabuf** classes. + +The generated class `ExampleJavabufTranslator`, which implements implements the interface + + package dev.resteasy.grpc.bridge.runtime.protobuf; + + public interface JavabufTranslator { + + ... + + Object translateFromJavabuf(Message message); + + Message translateToJavabuf(Object o); + + Message translateToJavabuf(Object o, GenericType genericType); + + ... + } + +in the grpc-bridge-runtime module of resteasy-grpc, is responsible for +translating back and forth between the original Java classes and their +javabuf counterparts. + +`ExampleJavabufTranslator` is used by generated class `ExampleMessageBodyReaderWriter`, +which implements the Jakarta REST interfaces +`jakarta.ws.rs.ext.MessageBodyReader` and +`jakarta.ws.rs.ext.MessageBodyWriter`. `ExampleMessageBodyReaderWriter` +is registered with the RESTEasy runtime and is responsible for writing +and reading protobuf messages to and from `java.io.OutputStream`s and +`java.io.InputStream`s. + +It can also be used to replace the laborious creation of javabuf objects +with `Builder`s. For example, instead of + + dev_resteasy_grpc_example___Y.Builder yb = dev_resteasy_grpc_example___Y.newBuilder(); + dev_resteasy_grpc_example___Y y = yb.setI(3).setJ(7).build(); + +we can do this: + + Y y = new Y(3, 7); + dev_resteasy_grpc_example___Y y = ExampleJavabufTranslator.translateToJavabuf(y); + +## gRPCtoJakartaREST-archetype + +There are a number of steps in building a bridge project, and the +gRPCtoJakartaREST-archetype embodies the correct order. Given a target +project such as org.greet:greet:0.0.1, the bridge project can be built +as follows: + + mvn archetype:generate -B \ + -DarchetypeGroupId=dev.resteasy.grpc \ + -DarchetypeArtifactId=gRPCtoJakartaREST-archetype \ + -DarchetypeVersion=${archetype.version} \ + -DgroupId=org.greet \ + -DartifactId=greet \ + -Dversion=0.0.1 \ + -Dgenerate-prefix=Greet \ + -Dgenerate-package=org.greet \ + -Dresteasy-version=${resteasy.version} \ + -Dgrpc-bridge-version=${resteasy.grpc.version} + +Running `mvn install` will build the files discussed above and package +everything into a WAR. Dropping the WAR into an instance of WildFly +provisioned with the +[wildfly-grpc-feature-pack](https://github.com/wildfly-extras/wildfly-grpc-feature-pack) +will expose the bridge project. + +## Conclusion + +For more details, see the +[documentation](https://resteasy.dev/docs/grpc/). + +resteasy-grpc now supports a significant subset of Jakarta REST +semantics, and we eagerly solicit feedback. + +[^1]: **gRPC and WildFly - Part II: Exposing Jakarta RESTFul Web Services to gRPC**: + + +[^2]: **resteasy-grpc: Handling arrays**: + + +[^3]: **resteasy-grpc: Handling Collections**: + + +[^4]: **Vlog: WildFly gRPC**: + + +[^5]: **grpc and WildFly - Part I**: + + +[^6]: **Using the resteasy-grpc feature together with the WildFly gRPC subsystem**: + + +[^7]: With great thanks to the Java parser project +() + +[^8]: In a future release we intend to make use of protobuf’s package +mechanism and multiple .proto files. + +[^9]: The suffix numbers may vary. diff --git a/data/grpcreleases.yaml b/data/grpcreleases.yaml index a70d55c..2efb619 100644 --- a/data/grpcreleases.yaml +++ b/data/grpcreleases.yaml @@ -4,7 +4,7 @@ - version: 1.0.0.Alpha6 date: 2025-02-13 license: ASL v2 - source: https://github.com/resteasy/resteasy-grpc/archive/refs/tags/1.0.0.Alpha6.zip - release_notes: https://github.com/resteasy/resteasy-grpc/releases/tag/1.0.0.Alpha6 + source: https://github.com/resteasy/resteasy-grpc/archive/refs/tags/1.0.0.Beta1.zip + release_notes: https://github.com/resteasy/resteasy-grpc/releases/tag/1.0.0.Beta1 documentation: link: /docs/grpc \ No newline at end of file From 7b990f247b417a18fe67d504d2bdeabedc64d845 Mon Sep 17 00:00:00 2001 From: Ron Sigal Date: Sat, 27 Sep 2025 13:52:31 -0400 Subject: [PATCH 2/2] Added blog about resteasy-grpc; updated resteasy-grpc doc version --- ...2025-09-15-grpc-resteasy-beta-release.adoc | 550 ++++++++++++++++++ .../2025-09-15-grpc-resteasy-beta-release.md | 550 ------------------ data/grpcreleases.yaml | 4 +- pom.xml | 2 +- 4 files changed, 553 insertions(+), 553 deletions(-) create mode 100644 content/posts/2025-09-15-grpc-resteasy-beta-release.adoc delete mode 100644 content/posts/2025-09-15-grpc-resteasy-beta-release.md diff --git a/content/posts/2025-09-15-grpc-resteasy-beta-release.adoc b/content/posts/2025-09-15-grpc-resteasy-beta-release.adoc new file mode 100644 index 0000000..5008cbd --- /dev/null +++ b/content/posts/2025-09-15-grpc-resteasy-beta-release.adoc @@ -0,0 +1,550 @@ +--- +layout: post +title: "resteasy-grpc 1.0.0.Beta1 is available" +aliases: [/news/2025/08/26/resteasy-grpc-1.0.0.Beta1-released] +date: 2025-08-26 +tags: announcement release +author: rsigal +description: resteasy-grpc 1.0.0.Beta1 is available. +--- + +== RESTEasy gRPC to Jakarta REST Bridge project: First Beta release available + +The first beta releases (1.0.0.Beta1) of the *RESTEasy gRPC to Jakarta REST Bridge* +project (https://github.com/resteasy/resteasy-grpc[https://github.com/resteasy/resteasy-grpc]), aka **resteasy-grpc**, +and its sibling *gRPCtoJakartaREST-archetype* project (1.0.0.Beta2) +(https://github.com/resteasy/gRPCtoJakartaREST-archetype[https://github.com/resteasy/gRPCtoJakartaREST-archetype]) +are now available on Maven Central. A number of blogs have been written on the +projects themselvesfootnote:[*gRPC and WildFly - Part II: Exposing Jakarta RESTFul Web Services to gRPC*: https://resteasy.dev/2023/06/11/grpc-in-wildfly-pt2/] +footnote:[*resteasy-grpc: Handling arrays*: https://resteasy.dev/2024/01/23/grpc-jakarta-rs-arrays/] +footnote:[*resteasy-grpc: Handling Collections*: https://resteasy.dev/2025/02/14/resteasy-grpc-collections/] +and on their use in WildFlyfootnote:[*Vlog: WildFly gRPC*: https://www.youtube.com/watch?v=UYSNM9Dy5M4] +footnote:[*grpc and WildFly - Part I*: https://www.wildfly.org/news/2023/06/12/grpc-and-WildFly-Part-I/] +footnote:[*Using the resteasy-grpc feature together with the WildFly gRPC subsystem*: https://resteasy.dev/2023/09/12/resteasy-grpc/], +so on this occasion we'll limit ourselves to a general overview. + +The goal of resteasy-grpc is to bridge the semantic gap between the worlds of gRPC and Jakarta REST so that a +developer familiar with gRPC can write a client that communicates with a Jakarta REST server. resteasy-grpc +contains module **grpc-bridge**, which generates a set of classes that are used at runtime, supported by the +module **grpc-bridge-runtime**. Given an existing Jakarta REST project, which we call the **target project**, +we want to extend it with the generated classes and the runtime to a **bridge project** which, while still +accepting invocations from Jakarta REST clients, can also process gRPC invocations. The transformation +of the target project to the bridge project is facilitated by the gRPCtoJakartaREST-archetype project. + +The semantics of the two worlds, gRPC and Jakarta REST, are considerably different, so resteasy-grpc has to do +some considerable lifting. The goal is to be able to accommodate any valid Jakarta REST project. We're not there +yet, but the first beta release implements a reasonable set of constructs. + +We'll go step by step. + +=== Compile time + +A gRPC developer codes in the context of a protobuf message descriptor file extended with gRPC rpc definitions, +aka a **.proto file**, and it is the responsibility of the resteasy-grpc compile time module to generate a .proto +file from the Jakarta REST application. Briefly, it + +. scans a directory tree of Java classes looking for Jakarta REST resource classesfootnote:[With great thanks to +the Java parser project (https://github.com/javaparser/javaparser)]; +. for each resource class, it finds all of the resource methods and locators; +. for each resource method and locator +.. it creates an rpc definition, and +.. for each entity parameter type and each return type, it creates a message definition. + +A parameter is given at compile time which is used to prefix various generated files. Here we'll use +"Example", so, for example, we'll get Example.proto. + +==== Semantic disparities + +The foregoing algorithm passes over some serious semantic issues. Here are a few. + +===== Packages + +We incorporate Java package names into message namesfootnote:[In a future release we intend to make use of protobuf's package mechanism +and multiple .proto files.]. +For example, + +---- +package dev.resteasy.grpc.example; + +public class X { + + private int i; + + public X(int i) {this.i = i;} +} +---- + +is turned into + +---- +// Type: dev.resteasy.grpc.example.X +message dev_resteasy_grpc_example___X { + int32 i = 1; +} +---- + +===== Inheritance + +protobuf has no notion of type inheritance, so we explicitly incorporate fields from ancestor classes into +a descendant class. For example, + +---- +package dev.resteasy.grpc.example; + +public class Y extends X { + + private int j; + + public Y(int i, int j) { + super(i); + this.j = j; + } +} +---- + +turns into + +---- +// Type: dev.resteasy.grpc.example.Y +message dev_resteasy_grpc_example___Y { + int32 i = 1; + int32 j = 2; +} +---- + +===== Generic types + +protobuf has no notion of type variables and generic types, so we create create distinct message +types for generic types with different type variable instantiations. For example, given + +---- +package dev.resteasy.grpc.example; + +public class Generic { + T t; +} +---- + +and + +---- +@Path("m") +@GET +public void method(Generic gi, Generic gf) { +} +---- + +we getfootnote:[The suffix numbers may vary.] + +---- +// Type: dev.resteasy.grpc.example.Generic +message dev_resteasy_grpc_example___Generic46 { + int32 t = 1; +} + +// Type: dev.resteasy.grpc.example.Generic +message dev_resteasy_grpc_example___Generic83 { + float t = 1; +} +---- + +For an "open" type like `Generic` or `Generic`, which `T` is a type variable, +we substitute `java.lang.Object`; i.e., `Generic`. + +===== Arrays + +protobuf supports one dimensional arrays with the "repeated" keyword, but it doesn't +support multidimensional or nullable arrays. First, consider + +---- +Integer[] intArray; +---- + +In a separate arrays.proto file, included in all generated bridge projects, we define + +---- +message dev_resteasy_grpc_arrays___Integer___Array { + repeated sfixed32 int_field = 1; +} + +message dev_resteasy_grpc_arrays___Integer___wrapper { + oneof type { + dev_resteasy_grpc_arrays___NONE none_field = 1; + sfixed32 integer_field = 2; + } +} +---- + +and + +---- +message dev_resteasy_grpc_arrays___Integer___WArray { + repeated dev_resteasy_grpc_arrays___Integer___wrapper wrapper_field = 1; +} +---- + +The type `dev_resteasy_grpc_arrays_\__Integer___Array` is the simpler version, an +integer array which is not nullable. To create a nullable version we define +`dev_resteasy_grpc_arrays_\__Integer___wrapper`, which can hold either 1) a special +type `dev_resteasy_grpc_arrays_\__NONE` which represents null, or 2) an integer. +Then we define the type `dev_resteasy_grpc_arrays___Integer___WArray` in which +each element is either null or an integer. + +Now, multidimensional arrays are defined by way of the recursively defined +`dev_resteasy_grpc_arrays___ArrayHolder`: + +---- +message dev_resteasy_grpc_arrays___ArrayHolder { + oneof messageType { + ... + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___Array dev_resteasy_grpc_arrays___Integer___Array_field = 12; + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___WArray dev_resteasy_grpc_arrays___Integer___WArray_field = 13; + --- + dev_resteasy_grpc_arrays___ArrayHolder___WArray dev_resteasy_grpc_arrays___ArrayHolder___WArray_field = 21; + } +} + +message dev_resteasy_grpc_arrays___ArrayHolder___wrapper { + oneof type { + dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___NONE none_field = 1; + dev_resteasy_grpc_arrays___ArrayHolder dev_resteasy_grpc_arrays___ArrayHolder_field = 2; + } +} + +message dev_resteasy_grpc_arrays___ArrayHolder___WArray { + string componentType = 1; + repeated dev_resteasy_grpc_arrays___ArrayHolder___wrapper wrapper___field = 2; +} +---- + +Now, consider + +---- +@Path("m2") +@GET +public Superclass[][] method2(Superclass[] sc) { +} +---- + +Then `Superclass[]` is represented by `dev_resteasy_grpc_example_\__Superclass___WArray`, and +Superclass[][] is represented by `dev_resteasy_grpc_arrays_\__ArrayHolder___WArray`. + +===== Collections and maps + +We take a simplifying approach to instances of `java.util.List`, `java.util.Set`, `java.util.Map`, +and `javax.ws.rs.core.MultivaluedMap`. Implementations, e.g., `java.util.ArrayList`, can be +quite complex for reasons of efficiency and desired usage, but we choose to ignore those implementation +details. For example, `java.util.ArrayList` and `java.util.LinkedList` will both be +represented essentially the same: + +---- +// List: java.util.ArrayList +message java_util___ArrayList176 { + string classname = 1; + //java.lang.Integer + repeated int32 data = 2; +} +---- + +and + +---- +// List: java.util.LinkedList +message java_util___LinkedList177 { + string classname = 1; + //java.lang.Integer + repeated int32 data = 2; +} +---- + +Similarly, `java.util.HashMap` would be represented as + +---- +// Map: java.util.HashMap +message java_util___HashMap41 { + string classname = 1; + //java.lang.String->java.lang.Integer + message Pair { + string key = 2; + int32 value = 3; + } + repeated Pair data = 4; +} +---- + +===== HTTP + +Protobuf runs over HTTP/2, but it doesn't expose much to the user in the same way as Jakarta REST, +so we define two message types to carry HTTP information: + +---- +message GeneralEntityMessage { + ServletInfo servletInfo = 1; + string URL = 2; + map headers = 3; + repeated gCookie cookies = 4; + string httpMethod = 5; + oneof messageType { + dev_resteasy_grpc_example___Generic46 dev_resteasy_grpc_example___Generic46_field = 6; + dev_resteasy_grpc_lists_sets___D137 dev_resteasy_grpc_lists_sets___D137_field = 7; + ... + } +} +---- + +and + +---- +message GeneralReturnMessage { + map headers = 1; + repeated gNewCookie cookies = 2; + int32 status = 3; + oneof messageType { + dev_resteasy_grpc_example___Subclass dev_resteasy_grpc_example___Subclass_field = 8; + java_util___ArrayList java_util___ArrayList_field = 9; + ... + } +} +---- + +where `messageType` in `GeneralEntityMessage` and `GeneralReturnMessage` can hold any of the entity types or +return types, respectively. For example: + +---- +rpc SayHello (GeneralEntityMessage) returns (GeneralReturnMessage) {} +---- + +=== Runtime + +To understand what happens at runtime in a resteasy-grpc generated bridge project, let's start by looking at +a pure gRPC example. In particular, consider the "hello world" example in +https://github.com/grpc/grpc-java/tree/master/examples. It starts with +https://github.com/grpc/grpc-java/blob/master/examples/src/main/proto/helloworld.proto[helloworld.proto]: + +---- +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} +---- + +When the .proto file is compiled, the compiler produces a client side stub with all of methods defined in the +.proto file. The client +https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java[HelloWorldClient.java] + +---- + public void greet(String name) { + ... + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = stub.sayHello(request); + ... + } +---- + +bridges the gap between Java and protobuf by using a `io.grpc.examples.helloworld.HelloRequest$Builder` +to create an `io.grpc.examples.helloworld.HelloRequest`, which it passes to the stub to invoke the matching +method on the server. + +For the server side, compiling the .proto file creates a class like `GreeterGrpc.GreeterImplBase` with +no-op methods meant to be overridden. For example, +https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java[HelloWorldServer.java] +overrides `GreeterGrpc.GreeterImplBase`: + +---- +static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + +@Override +public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); +} +---- + +It extracts a value from `HelloRequest` and uses a `HelloReply$Builder` to create a response. + +The same thing happens in a bridge project generated by resteasy-grpc, except that the messages in the generated +.proto file represent Java types defined in the original Jakarta REST target project. + +Consider the resource method + +---- +@Path("m3") +@GET +public Y method3(Y y) { + return y; +} +---- + +We can call it like this: + +---- +dev_resteasy_grpc_example___Y.Builder yb + = dev_resteasy_grpc_example___Y.newBuilder(); +dev_resteasy_grpc_example___Y y = yb.setI(3).setJ(7).build(); + +GeneralEntityMessage.Builder gemb = GeneralEntityMessage.newBuilder(); +GeneralEntityMessage gem = gemb.setDevResteasyGrpcExampleYField(y).build(); +GeneralReturnMessage response = stub.method3(gem); +Assertions.assertEquals(y, response.getDevResteasyGrpcExampleYField();); +---- + +It's structurally similar to `HelloWorldClient.java` except for the extra step of creating +a `GeneralEntityMessage`. + +Similarly, on the server side `ExampleServiceGrpcImpl` +contains an overriding method for each method in the .proto file. +It's structurally similar to `sayHello()`, but it plays a different role. `sayHello()` +implements some business logic, but with resteasy-grpc we're creating a project in which +the business logic already exists in the resource methods of the target project. Instead, +the function of the overriding methods is to provide a bridge between the gRPC world and the +Jakarta REST world. + +For example, the overriding method for `method3()` would look like + +---- +@java.lang.Override +public void method3(GeneralEntityMessage param, StreamObserver responseObserver) { + HttpServletRequest request = null; + try { + HttpServletResponseImpl response + = new HttpServletResponseImpl("dev_resteasy_grpc_example___Y", "sync", + Example_Server.getServletContext(), builder, fd); + GeneratedMessage actualParam = param.getDevResteasyGrpcExampleYField(); + request = getHttpServletRequest(param, actualParam, "/", response, "GET", + "dev_resteasy_grpc_example___Y"); + HttpServletDispatcher servlet = getServlet(); + activateRequestContext(); + servlet.service(request.getMethod(), request, response); + MockServletOutputStream msos = (MockServletOutputStream) response.getOutputStream(); + ByteArrayOutputStream baos = msos.getDelegate(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + dev_resteasy_grpc_example___Y reply = dev_resteasy_grpc_example___Y.parseFrom(bais); + GeneralReturnMessage.Builder grmb = createGeneralReturnMessageBuilder(response); + grmb.setDevResteasyGrpcExampleYField(reply); + responseObserver.onNext(grmb.build()); + } catch (Exception e) { + responseObserver.onError(e); + } finally { + responseObserver.onCompleted(); + if (requestContextController != null) { + requestContextController.deactivate(); + } + if (tccl != null) { + Thread.currentThread().setContextClassLoader(tccl); + } + } +} +---- + +Without going into the details, one of its responsibilities is to create a suitable runtime environment +for a Jakarta REST resource method. For example, a CDI request context is activated. Another responsibility +is to take a protobuf value from the wire, translate it to the appropriate Java class, and pass it as +an entity value. Once the resource method runs, its response is translated back to a protobuf message, +stored in a `GeneralReturnMessage`, and passed back to the gRPC runtime, which sends it to the client. + +A couple of other generated classes are worth mentioning. + +Given an existing Jakarta REST application, we start with a set of Java classes that occur as entity parameters +or return values, turn them into protobuf messages, and then compile the messages +into Java classes. For example, `dev.resteasy.grpc.example.X` is translated to the protobuf message +`dev_resteasy_grpc_example\___X`. Then, when the .proto file is compiled, +`dev.resteasy.grpc.example.Example_proto.java` contains the inner class `dev_resteasy_grpc_example___X`. +We call these generated Java classes **javabuf** classes. + +`ExampleJavabufTranslator`, which implements implements the interface + +---- +package dev.resteasy.grpc.bridge.runtime.protobuf; + +public interface JavabufTranslator { + + ... + + Object translateFromJavabuf(Message message); + + Message translateToJavabuf(Object o); + + Message translateToJavabuf(Object o, GenericType genericType); + + ... +} +---- + +in the grpc-bridge-runtime module of resteasy-grpc, is responsible for translating back and +forth between the original Java classes and their javabuf counterparts. + +`ExampleJavabufTranslator` is used by generated class `ExampleMessageBodyReaderWriter`, which implements the +Jakarta REST interfaces `jakarta.ws.rs.ext.MessageBodyReader` and `jakarta.ws.rs.ext.MessageBodyWriter`. +`ExampleMessageBodyReaderWriter` is registered with the RESTEasy runtime and is responsible for writing and +reading protobuf messages to and from `java.io.OutputStream`{empty}s and `java.io.InputStream`{empty}s. + +It can also be used to replace the laborious creation of javabuf objects with `Builder`{empty}s. For example, +instead of + +---- +dev_resteasy_grpc_example___Y.Builder yb + = dev_resteasy_grpc_example___Y.newBuilder(); +dev_resteasy_grpc_example___Y y = yb.setI(3).setJ(7).build(); +---- + +we can do this: + +---- +Y y = new Y(3, 7); +dev_resteasy_grpc_example___Y y = ExampleJavabufTranslator.translateToJavabuf(y); +---- + +==== gRPCtoJakartaREST-archetype + +There are a number of steps in building a bridge project, and the gRPCtoJakartaREST-archetype +embodies the correct order. Given a target project such as org.greet:greet:0.0.1, the +bridge project can be built as follows: + +---- +mvn archetype:generate -B \ + -DarchetypeGroupId=dev.resteasy.grpc \ + -DarchetypeArtifactId=gRPCtoJakartaREST-archetype \ + -DarchetypeVersion=${archetype.version} \ + -DgroupId=org.greet \ + -DartifactId=greet \ + -Dversion=0.0.1 \ + -Dgenerate-prefix=Greet \ + -Dgenerate-package=org.greet \ + -Dresteasy-version=${resteasy.version} \ + -Dgrpc-bridge-version=${resteasy.grpc.version} +---- + +Running `mvn install` will build the files discussed above and package everything into +a WAR. Dropping the WAR into an instance of WildFly provisioned with the +https://github.com/wildfly-extras/wildfly-grpc-feature-pack[wildfly-grpc-feature-pack] will +expose the gridge project. + +=== Conclusion + +For more details, see the https://resteasy.dev/docs/grpc/[documentation]. + +resteasy-grpc now supports a significant subset of Jakarta REST semantics, and we +eagerly solicit feedback. diff --git a/content/posts/2025-09-15-grpc-resteasy-beta-release.md b/content/posts/2025-09-15-grpc-resteasy-beta-release.md deleted file mode 100644 index b777810..0000000 --- a/content/posts/2025-09-15-grpc-resteasy-beta-release.md +++ /dev/null @@ -1,550 +0,0 @@ ---- layout: post title: "resteasy-grpc 1.0.0.Beta1 is available" -aliases: \[/news/2025/08/26/resteasy-grpc-1.0.0.Beta1-released\] date: -2025-08-26 tags: announcement release author: rsigal description: -resteasy-grpc 1.0.0.Beta1 is available. --- - -# RESTEasy gRPC to Jakarta REST Bridge project: first Beta release out - -The first beta releases (1.0.0.Beta1) of the **RESTEasy gRPC to Jakarta REST Bridge** -project (), aka -**resteasy-grpc**, and its sibling **gRPCtoJakartaREST-archetype** -project (1.0.0.Beta2) -() are now -available on Maven Central. A number of blogs have been written on the -projects themselves[^1] [^2][^3] and on their use in WildFly[^4][^5][^6], -so on this occasion we’ll limit ourselves to a general overview. - -The goal of resteasy-grpc is to bridge the semantic gap between the -worlds of gRPC and Jakarta REST so that a developer familiar with gRPC -can write a client that communicates with a Jakarta REST server. -resteasy-grpc contains module **grpc-bridge** which generates a set of -classes that are used at runtime, supported by the module -**grpc-bridge-runtime**. Given an existing Jakarta REST project, which -we call the **target project**, we want to extend it with the generated -classes and the runtime to a **bridge project** which, while still -accepting invocations from Jakarta REST clients, can also process gRPC -invocations. The transformation of the target project to the bridge -project is facilitated by the gRPCtoJakartaREST-archetype project. - -The semantics of the two worlds, gRPC and Jakarta REST, are considerably -different, so resteasy-grpc has to do some considerable lifting. The -goal is to be able to accommodate any valid Jakarta REST project. We’re -not there yet, but the first beta release implements a reasonable set of -constructs. - -We’ll go step by step. - -## Compile time - -A gRPC developer codes in the context of a protobuf message descriptor -file extended with gRPC rpc definitions, aka a **.proto file**, and it -is the responsibility of the resteasy-grpc compile time module to -generate a .proto file from the Jakarta REST server application. -Briefly, it - -1. scans a directory tree of Java classes looking for Jakarta REST - resource classes[^7]; - -2. for each resource class, it finds all of the resource methods and - locators; - -3. for each resource method and locator - - A. it creates an rpc definition, and - - B. for each entity parameter type and each return type, it creates - a message definition. - -A parameter is given at compile time which is used to prefix various -generated files. Here we’ll use "Example", so, for example, we’ll get -Example.proto. - -## Semantic disparities - -The foregoing algorithm passes over some serious semantic issues. Here -are a few. - -### Packages - -We incorporate Java package names into message names[^8]. For example, - - package dev.resteasy.grpc.example; - - public class X { - - private int i; - - public X(int i) {this.i = i;} - } - -is turned into - - // Type: dev.resteasy.grpc.example.X - message dev_resteasy_grpc_example___X { - int32 i = 1; - } - -### Inheritance - -protobuf has no notion of type inheritance, so we explicitly incorporate -fields from ancestor classes into a descendant class. For example, - - package dev.resteasy.grpc.example; - - public class Y extends X { - - private int j; - - public Y(int i, int j) { - super(i); - this.j = j; - } - } - -turns into - - // Type: dev.resteasy.grpc.example.Y - message dev_resteasy_grpc_example___Y { - int32 i = 1; - int32 j = 2; - } - -### Generic types - -protobuf has no notion of type variables and generic types, so we create -create distinct message types for generic types with different type -variable instantiations. For example, given - - package dev.resteasy.grpc.example; - - public class Generic { - T t; - } - -and - - @Path("m") - @GET - public void method(Generic gi, Generic gf) { - } - -we get[^9] - - // Type: dev.resteasy.grpc.example.Generic - message dev_resteasy_grpc_example___Generic46 { - int32 t = 1; - } - - // Type: dev.resteasy.grpc.example.Generic - message dev_resteasy_grpc_example___Generic83 { - float t = 1; - } - -For an "open" type like `Generic` or `Generic`, which `T` is a type variable, -we substitute `java.lang.Object`; i.e., `Generic`. - -### Arrays - -protobuf supports one dimensional arrays with the "repeated" keyword, -but it doesn’t support multidimensional or nullable arrays. First, -consider - - Integer[] intArray; - -In a separate arrays.proto file, included in all generated bridge -projects, we define - - message dev_resteasy_grpc_arrays___Integer___Array { - repeated sfixed32 int_field = 1; - } - - message dev_resteasy_grpc_arrays___Integer___wrapper { - oneof type { - dev_resteasy_grpc_arrays___NONE none_field = 1; - sfixed32 integer_field = 2; - } - } - -and - - message dev_resteasy_grpc_arrays___Integer___WArray { - repeated dev_resteasy_grpc_arrays___Integer___wrapper wrapper_field = 1; - } - -The type `dev_resteasy_grpc_arrays___Integer___Array` is the simpler -version, an integer array which is not nullable. To create a nullable -version we define `dev_resteasy_grpc_arrays___Integer___wrapper` which -can hold either 1) a special type `dev_resteasy_grpc_arrays___NONE` -which represents null, or 2) an integer. Then we define the type -`dev_resteasy_grpc_arrays___Integer___WArray` in which each element is -either null or an integer. - -Now, multidimensional arrays are defined by way of the recursively -defined `dev_resteasy_grpc_arrays___ArrayHolder`: - - message dev_resteasy_grpc_arrays___ArrayHolder { - oneof messageType { - ... - dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___Array dev_resteasy_grpc_arrays___Integer___Array_field = 12; - dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___Integer___WArray dev_resteasy_grpc_arrays___Integer___WArray_field = 13; - --- - dev_resteasy_grpc_arrays___ArrayHolder___WArray dev_resteasy_grpc_arrays___ArrayHolder___WArray_field = 21; - } - } - - message dev_resteasy_grpc_arrays___ArrayHolder___wrapper { - oneof type { - dev.resteasy.grpc.arrays.dev_resteasy_grpc_arrays___NONE none_field = 1; - dev_resteasy_grpc_arrays___ArrayHolder dev_resteasy_grpc_arrays___ArrayHolder_field = 2; - } - } - - message dev_resteasy_grpc_arrays___ArrayHolder___WArray { - string componentType = 1; - repeated dev_resteasy_grpc_arrays___ArrayHolder___wrapper wrapper___field = 2; - } - -Now, consider - - @Path("m2") - @GET - public Superclass[][] method2(Superclass[] sc) { - } - -Then `Superclass[]` is represented by -`dev_resteasy_grpc_example___Superclass___WArray`, and -`Superclass[][]` is represented by -`dev_resteasy_grpc_arrays___ArrayHolder___WArray`. - -### Collections and maps - -We take a simplifying approach to instances of `java.util.List`, -`java.util.Set`, `java.util.Map`, and `javax.ws.rs.core.MultivaluedMap`. -Implementations, e.g., `java.util.ArrayList`, can be quite complex for -reasons of efficiency and desired usage, but we choose to ignore those -implementation details. For example, `java.util.ArrayList` and -`java.util.LinkedList` will both be represented essentially the -same: - - // List: java.util.ArrayList - message java_util___ArrayList176 { - string classname = 1; - //java.lang.Integer - repeated int32 data = 2; - } - -and - - // List: java.util.LinkedList - message java_util___LinkedList177 { - string classname = 1; - //java.lang.Integer - repeated int32 data = 2; - } - -Similarly, `java.util.HashMap` would be represented as - - // Map: java.util.HashMap - message java_util___HashMap41 { - string classname = 1; - //java.lang.String->java.lang.Integer - message Pair { - string key = 2; - int32 value = 3; - } - repeated Pair data = 4; - } - -### HTTP - -Protobuf runs over HTTP/2, but it doesn’t expose much to the user in the -same way as Jakarta REST, so we define two message types to carry HTTP -information: - - message GeneralEntityMessage { - ServletInfo servletInfo = 1; - string URL = 2; - map headers = 3; - repeated gCookie cookies = 4; - string httpMethod = 5; - oneof messageType { - dev_resteasy_grpc_example___Generic46 dev_resteasy_grpc_example___Generic46_field = 6; - dev_resteasy_grpc_lists_sets___D137 dev_resteasy_grpc_lists_sets___D137_field = 7; - ... - } - } - -and - - message GeneralReturnMessage { - map headers = 1; - repeated gNewCookie cookies = 2; - int32 status = 3; - oneof messageType { - dev_resteasy_grpc_example___Subclass dev_resteasy_grpc_example___Subclass_field = 8; - java_util___ArrayList java_util___ArrayList_field = 9; - ... - } - } - -where `messageType` in `GeneralEntityMessage` and `GeneralReturnMessage` -can hold any of the entity types or return types, respectively. All `rpc` definitions use -these two types. For example: - - rpc SayHello (GeneralEntityMessage) returns (GeneralReturnMessage) {} - -## Runtime - -To understand what happens at runtime in a resteasy-grpc generated -bridge project, let’s start by looking at a pure gRPC example. In -particular, consider the "hello world" example in -. It starts with -[helloworld.proto](https://github.com/grpc/grpc-java/blob/master/examples/src/main/proto/helloworld.proto): - - syntax = "proto3"; - - option java_multiple_files = true; - option java_package = "io.grpc.examples.helloworld"; - option java_outer_classname = "HelloWorldProto"; - option objc_class_prefix = "HLW"; - - package helloworld; - - // The greeting service definition. - service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} - } - - // The request message containing the user's name. - message HelloRequest { - string name = 1; - } - - // The response message containing the greetings - message HelloReply { - string message = 1; - } - -When the .proto file is compiled, the compiler produces a client side -stub with all of methods defined in the .proto file. The client -[HelloWorldClient.java](https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java) - - public void greet(String name) { - ... - HelloRequest request = HelloRequest.newBuilder().setName(name).build(); - HelloReply response; - try { - response = stub.sayHello(request); - ... - } - -bridges the gap between Java and protobuf by using a -`io.grpc.examples.helloworld.HelloRequest$Builder` to create an -`io.grpc.examples.helloworld.HelloRequest`, which it passes to the stub -to invoke the matching method on the server. - -For the server side, compiling the .proto file creates a class like -`GreeterGrpc.GreeterImplBase` with no-op methods meant to be overridden. -For example, -[HelloWorldServer.java](https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java) -overrides `GreeterGrpc.GreeterImplBase`: - - static class GreeterImpl extends GreeterGrpc.GreeterImplBase { - - @Override - public void sayHello(HelloRequest req, StreamObserver responseObserver) { - HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); - } - -It extracts a value from `HelloRequest` and uses a `HelloReply$Builder` -to create a response. - -The same thing happens in a bridge project generated by resteasy-grpc, -except that the messages in the generated .proto file represent Java -types defined in the original Jakarta REST target project. - -Consider the resource method - - @Path("m3") - @GET - public Y method3(Y y) { - return y; - } - -We can call it like this: - - dev_resteasy_grpc_example___Y.Builder yb = dev_resteasy_grpc_example___Y.newBuilder(); - dev_resteasy_grpc_example___Y y = yb.setI(3).setJ(7).build(); - - GeneralEntityMessage.Builder gemb = GeneralEntityMessage.newBuilder(); - GeneralEntityMessage gem = gemb.setDevResteasyGrpcExampleYField(y).build(); - GeneralReturnMessage response = stub.method3(gem); - Assertions.assertEquals(y, response.getDevResteasyGrpcExampleYField()); - -It’s structurally similar to `HelloWorldClient.java` except for the -extra step of creating a `GeneralEntityMessage`. - -Similarly, on the server side `ExampleServiceGrpcImpl` contains an -overriding method for each method in the .proto file. It’s structurally -similar to `sayHello()`, but it plays a different role. `sayHello()` -implements some business logic, but with resteasy-grpc we’re creating a -project in which the business logic already exists in the resource -methods of the target project. Instead, the function of the overriding -methods is to provide a bridge between the gRPC world and the Jakarta -REST world. - -For example, the overriding method for `method3()` would look like - - @java.lang.Override - public void method3(GeneralEntityMessage param, StreamObserver responseObserver) { - HttpServletRequest request = null; - try { - HttpServletResponseImpl response - = new HttpServletResponseImpl("dev_resteasy_grpc_example___Y", "sync", - Example_Server.getServletContext(), builder, fd); - GeneratedMessage actualParam = param.getDevResteasyGrpcExampleYField(); - request = getHttpServletRequest(param, actualParam, "/", response, "GET", "dev_resteasy_grpc_example___Y"); - HttpServletDispatcher servlet = getServlet(); - activateRequestContext(); - servlet.service(request.getMethod(), request, response); - MockServletOutputStream msos = (MockServletOutputStream) response.getOutputStream(); - ByteArrayOutputStream baos = msos.getDelegate(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - dev_resteasy_grpc_example___Y reply = dev_resteasy_grpc_example___Y.parseFrom(bais); - GeneralReturnMessage.Builder grmb = createGeneralReturnMessageBuilder(response); - grmb.setDevResteasyGrpcExampleYField(reply); - responseObserver.onNext(grmb.build()); - } catch (Exception e) { - responseObserver.onError(e); - } finally { - responseObserver.onCompleted(); - if (requestContextController != null) { - requestContextController.deactivate(); - } - if (tccl != null) { - Thread.currentThread().setContextClassLoader(tccl); - } - } - } - -Without going into the details, one of its responsibilities is to create -a suitable runtime environment for a Jakarta REST resource method. For -example, a CDI request context is activated. Another responsibility is -to take a protobuf value from the wire, translate it to the appropriate -Java class, and pass it as an entity value. Once the resource method -runs, its response is translated back to a protobuf message, stored in a -`GeneralReturnMessage`, and passed back to the gRPC runtime, which sends -it to the client. - -A couple of other generated classes are worth mentioning. - -Given an existing Jakarta REST application, we start with a set of Java -classes that occur as entity parameters or return values, turn them into -protobuf messages, and then compile the messages into Java classes. For -example, `dev.resteasy.grpc.example.X` is translated to the protobuf -message `dev_resteasy_grpc_example___X`. Then, when the .proto file is -compiled, `dev.resteasy.grpc.example.Example_proto.java` contains the -inner class `dev_resteasy_grpc_example___X`. We call these generated -Java classes **javabuf** classes. - -The generated class `ExampleJavabufTranslator`, which implements implements the interface - - package dev.resteasy.grpc.bridge.runtime.protobuf; - - public interface JavabufTranslator { - - ... - - Object translateFromJavabuf(Message message); - - Message translateToJavabuf(Object o); - - Message translateToJavabuf(Object o, GenericType genericType); - - ... - } - -in the grpc-bridge-runtime module of resteasy-grpc, is responsible for -translating back and forth between the original Java classes and their -javabuf counterparts. - -`ExampleJavabufTranslator` is used by generated class `ExampleMessageBodyReaderWriter`, -which implements the Jakarta REST interfaces -`jakarta.ws.rs.ext.MessageBodyReader` and -`jakarta.ws.rs.ext.MessageBodyWriter`. `ExampleMessageBodyReaderWriter` -is registered with the RESTEasy runtime and is responsible for writing -and reading protobuf messages to and from `java.io.OutputStream`s and -`java.io.InputStream`s. - -It can also be used to replace the laborious creation of javabuf objects -with `Builder`s. For example, instead of - - dev_resteasy_grpc_example___Y.Builder yb = dev_resteasy_grpc_example___Y.newBuilder(); - dev_resteasy_grpc_example___Y y = yb.setI(3).setJ(7).build(); - -we can do this: - - Y y = new Y(3, 7); - dev_resteasy_grpc_example___Y y = ExampleJavabufTranslator.translateToJavabuf(y); - -## gRPCtoJakartaREST-archetype - -There are a number of steps in building a bridge project, and the -gRPCtoJakartaREST-archetype embodies the correct order. Given a target -project such as org.greet:greet:0.0.1, the bridge project can be built -as follows: - - mvn archetype:generate -B \ - -DarchetypeGroupId=dev.resteasy.grpc \ - -DarchetypeArtifactId=gRPCtoJakartaREST-archetype \ - -DarchetypeVersion=${archetype.version} \ - -DgroupId=org.greet \ - -DartifactId=greet \ - -Dversion=0.0.1 \ - -Dgenerate-prefix=Greet \ - -Dgenerate-package=org.greet \ - -Dresteasy-version=${resteasy.version} \ - -Dgrpc-bridge-version=${resteasy.grpc.version} - -Running `mvn install` will build the files discussed above and package -everything into a WAR. Dropping the WAR into an instance of WildFly -provisioned with the -[wildfly-grpc-feature-pack](https://github.com/wildfly-extras/wildfly-grpc-feature-pack) -will expose the bridge project. - -## Conclusion - -For more details, see the -[documentation](https://resteasy.dev/docs/grpc/). - -resteasy-grpc now supports a significant subset of Jakarta REST -semantics, and we eagerly solicit feedback. - -[^1]: **gRPC and WildFly - Part II: Exposing Jakarta RESTFul Web Services to gRPC**: - - -[^2]: **resteasy-grpc: Handling arrays**: - - -[^3]: **resteasy-grpc: Handling Collections**: - - -[^4]: **Vlog: WildFly gRPC**: - - -[^5]: **grpc and WildFly - Part I**: - - -[^6]: **Using the resteasy-grpc feature together with the WildFly gRPC subsystem**: - - -[^7]: With great thanks to the Java parser project -() - -[^8]: In a future release we intend to make use of protobuf’s package -mechanism and multiple .proto files. - -[^9]: The suffix numbers may vary. diff --git a/data/grpcreleases.yaml b/data/grpcreleases.yaml index 2efb619..ed0d312 100644 --- a/data/grpcreleases.yaml +++ b/data/grpcreleases.yaml @@ -1,8 +1,8 @@ - group: 1.x supported: true detail: - - version: 1.0.0.Alpha6 - date: 2025-02-13 + - version: 1.0.0.Beta1 + date: 2025-09-28 license: ASL v2 source: https://github.com/resteasy/resteasy-grpc/archive/refs/tags/1.0.0.Beta1.zip release_notes: https://github.com/resteasy/resteasy-grpc/releases/tag/1.0.0.Beta1 diff --git a/pom.xml b/pom.xml index 556c8c7..6ba5c67 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.resteasy - resteast-dev-site + resteasy-dev-site 1.0.0-SNAPSHOT