Skip to content

Commit 86f51c0

Browse files
committed
gRPC: javadoc for major/public components
1 parent f3ae53f commit 86f51c0

3 files changed

Lines changed: 178 additions & 11 deletions

File tree

jooby/src/main/java/io/jooby/GrpcExchange.java

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,73 @@
1111

1212
import edu.umd.cs.findbugs.annotations.Nullable;
1313

14-
/** Server-agnostic abstraction for HTTP/2 trailing-header exchanges. */
14+
/**
15+
* Server-agnostic abstraction for a native HTTP/2 gRPC exchange.
16+
*
17+
* <p>This interface bridges the gap between the underlying web server (Undertow, Netty, or Jetty)
18+
* and the reactive gRPC processor. Because gRPC heavily relies on HTTP/2 multiplexing, asynchronous
19+
* I/O, and trailing headers (trailers) to communicate status codes, standard HTTP/1.1 context
20+
* abstractions are insufficient.
21+
*
22+
* <p>Native server interceptors wrap their respective request/response objects into this interface,
23+
* allowing the {@link GrpcProcessor} to read headers, push zero-copy byte frames, and finalize the
24+
* stream with standard gRPC trailers without knowing which server engine is actually running.
25+
*/
1526
public interface GrpcExchange {
1627

28+
/**
29+
* Retrieves the requested URI path.
30+
*
31+
* <p>In gRPC, this dictates the routing and strictly follows the pattern: {@code
32+
* /Fully.Qualified.ServiceName/MethodName}.
33+
*
34+
* @return The exact path of the incoming HTTP/2 request.
35+
*/
1736
String getRequestPath();
1837

38+
/**
39+
* Retrieves the value of the specified HTTP request header.
40+
*
41+
* @param name The name of the header (case-insensitive).
42+
* @return The header value, or {@code null} if the header is not present.
43+
*/
1944
@Nullable String getHeader(String name);
2045

46+
/**
47+
* Retrieves all HTTP request headers.
48+
*
49+
* @return A map containing all headers provided by the client.
50+
*/
2151
Map<String, String> getHeaders();
2252

23-
/** Write framed bytes to the underlying non-blocking socket. */
53+
/**
54+
* Writes a gRPC-framed byte payload to the underlying non-blocking socket.
55+
*
56+
* <p>This method must push the buffer to the native network layer without blocking the invoking
57+
* thread (which is typically a background gRPC worker). The implementation is responsible for
58+
* translating the ByteBuffer into the server's native data format and flushing it over the
59+
* network.
60+
*
61+
* @param payload The properly framed 5-byte-prefixed gRPC payload to send.
62+
* @param onFailure A callback invoked immediately if the asynchronous network write fails (e.g.,
63+
* if the client abruptly disconnects or the channel is closed).
64+
*/
2465
void send(ByteBuffer payload, Consumer<Throwable> onFailure);
2566

2667
/**
27-
* Closes the HTTP/2 stream with trailing headers.
68+
* Closes the HTTP/2 stream by appending the mandatory gRPC trailing headers.
69+
*
70+
* <p>In the gRPC specification, a successful response or an application-level error is
71+
* communicated not by standard HTTP status codes (which are always 200 OK), but by appending
72+
* HTTP/2 trailers ({@code grpc-status} and {@code grpc-message}) at the very end of the stream.
73+
*
74+
* <p>Calling this method informs the native server to write those trailing headers and formally
75+
* close the bidirectional stream.
2876
*
29-
* @param statusCode The gRPC status code (e.g., 0 for OK, 12 for UNIMPLEMENTED).
30-
* @param description Optional status message.
77+
* @param statusCode The gRPC integer status code (e.g., {@code 0} for OK, {@code 12} for
78+
* UNIMPLEMENTED, {@code 4} for DEADLINE_EXCEEDED).
79+
* @param description An optional, human-readable status message detailing the result or error.
80+
* May be {@code null}.
3181
*/
32-
void close(int statusCode, String description);
82+
void close(int statusCode, @Nullable String description);
3383
}

jooby/src/main/java/io/jooby/GrpcProcessor.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,43 @@
1010

1111
import edu.umd.cs.findbugs.annotations.NonNull;
1212

13-
/** Intercepts and processes gRPC exchanges. */
13+
/**
14+
* Core Service Provider Interface (SPI) for the gRPC extension.
15+
*
16+
* <p>This processor acts as the bridge between the native HTTP/2 web servers (Undertow, Netty,
17+
* Jetty) and the embedded gRPC engine. It is designed to intercept and process gRPC exchanges at
18+
* the lowest possible network level, completely bypassing Jooby's standard HTTP/1.1 routing
19+
* pipeline. This architecture ensures strict HTTP/2 specification compliance, zero-copy buffering,
20+
* and reactive backpressure.
21+
*/
1422
public interface GrpcProcessor {
1523

16-
/** Checks if the given URI path exactly matches a registered gRPC method. */
24+
/**
25+
* Checks if the given URI path exactly matches a registered gRPC method.
26+
*
27+
* <p>Native server interceptors (Undertow, Netty, Jetty) use this method as a lightweight,
28+
* fail-fast guard. If this returns {@code true}, the server will hijack the request and upgrade
29+
* it to a native gRPC stream. If {@code false}, the request safely falls through to the standard
30+
* Jooby router (typically resulting in a standard HTTP 404 Not Found, which gRPC clients
31+
* gracefully translate to Status 12 UNIMPLEMENTED).
32+
*
33+
* @param path The incoming request path (e.g., {@code /fully.qualified.Service/MethodName}).
34+
* @return {@code true} if the path is mapped to an active gRPC service; {@code false} otherwise.
35+
*/
1736
boolean isGrpcMethod(String path);
1837

1938
/**
20-
* @return A subscriber that the server will feed ByteBuffer chunks into, or null if the exchange
21-
* was rejected/unimplemented.
39+
* Initiates the reactive gRPC pipeline for an incoming HTTP/2 request.
40+
*
41+
* <p>When a valid gRPC request is intercepted, the native server wraps the underlying network
42+
* connection into a {@link GrpcExchange} and passes it to this method. The processor uses this
43+
* exchange to asynchronously send response headers, payload byte frames, and HTTP/2 trailers.
44+
*
45+
* @param exchange The native server exchange representing the bidirectional HTTP/2 stream.
46+
* @return A reactive {@link Flow.Subscriber} that the native server must feed incoming request
47+
* payload {@link ByteBuffer} chunks into. Returns {@code null} if the exchange was rejected.
48+
* @throws IllegalStateException If an unregistered path bypasses the {@link
49+
* #isGrpcMethod(String)} guard.
2250
*/
2351
Flow.Subscriber<ByteBuffer> process(@NonNull GrpcExchange exchange);
2452
}

modules/jooby-grpc/src/main/java/io/jooby/grpc/GrpcModule.java

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,61 @@
1717
import io.jooby.*;
1818
import io.jooby.internal.grpc.DefaultGrpcProcessor;
1919

20+
/**
21+
* Native gRPC extension for Jooby. *
22+
*
23+
* <p>This module allows you to run strictly-typed gRPC services alongside standard Jooby HTTP
24+
* routes on the exact same port. It completely bypasses standard HTTP/1.1 pipelines in favor of a
25+
* highly optimized, reactive, native interceptor tailored for HTTP/2 multiplexing and trailing
26+
* headers. *
27+
*
28+
* <h3>Usage</h3>
29+
*
30+
* <p>gRPC requires HTTP/2. Ensure your Jooby application is configured to use a supported server
31+
* (Undertow, Netty, or Jetty) with HTTP/2 enabled. *
32+
*
33+
* <pre>{@code
34+
* import io.jooby.Jooby;
35+
* import io.jooby.ServerOptions;
36+
* import io.jooby.grpc.GrpcModule;
37+
* * public class App extends Jooby {
38+
* {
39+
* setServerOptions(new ServerOptions().setHttp2(true).setSecurePort(8443));
40+
* * // Install the extension and register your services
41+
* install(new GrpcModule(new GreeterService()));
42+
* }
43+
* }
44+
* }</pre>
45+
*
46+
* *
47+
*
48+
* <h3>Dependency Injection</h3>
49+
*
50+
* <p>If your gRPC services require external dependencies (like repositories or configuration), you
51+
* can register the service classes instead of instances. The module will automatically provision
52+
* them using Jooby's DI registry (e.g., Guice, Spring) during the application startup phase. *
53+
*
54+
* <pre>{@code
55+
* public class App extends Jooby {
56+
* {
57+
* install(new GuiceModule());
58+
* * // Pass the class reference. Guice will instantiate it!
59+
* install(new GrpcModule(GreeterService.class));
60+
* }
61+
* }
62+
* }</pre>
63+
*
64+
* *
65+
*
66+
* <p><strong>Note:</strong> gRPC services are inherently registered as Singletons. Ensure your
67+
* service implementations are thread-safe and do not hold request-scoped state in instance
68+
* variables. *
69+
*
70+
* <h3>Logging</h3>
71+
*
72+
* <p>gRPC internally uses {@code java.util.logging}. This module automatically installs the {@link
73+
* SLF4JBridgeHandler} to redirect all internal gRPC logs to your configured SLF4J backend.
74+
*/
2075
public class GrpcModule implements Extension {
2176
private final List<BindableService> services = new ArrayList<>();
2277
private final List<Class<? extends BindableService>> serviceClasses = new ArrayList<>();
@@ -28,21 +83,45 @@ public class GrpcModule implements Extension {
2883
SLF4JBridgeHandler.install();
2984
}
3085

86+
/**
87+
* Creates a new gRPC module with pre-instantiated service objects. * @param services One or more
88+
* fully instantiated gRPC services.
89+
*/
3190
public GrpcModule(BindableService... services) {
3291
this.services.addAll(Arrays.asList(services));
3392
}
3493

94+
/**
95+
* Creates a new gRPC module with service classes to be provisioned via Dependency Injection.
96+
* * @param serviceClasses One or more gRPC service classes to be resolved from the Jooby
97+
* registry.
98+
*/
3599
@SafeVarargs
36100
public GrpcModule(Class<? extends BindableService>... serviceClasses) {
37101
bind(serviceClasses);
38102
}
39103

104+
/**
105+
* Registers additional gRPC service classes to be provisioned via Dependency Injection. * @param
106+
* serviceClasses One or more gRPC service classes to be resolved from the Jooby registry.
107+
*
108+
* @return This module for chaining.
109+
*/
40110
@SafeVarargs
41111
public final GrpcModule bind(Class<? extends BindableService>... serviceClasses) {
42112
this.serviceClasses.addAll(List.of(serviceClasses));
43113
return this;
44114
}
45115

116+
/**
117+
* Installs the gRPC extension into the Jooby application. *
118+
*
119+
* <p>This method sets up the {@link GrpcProcessor} SPI, registers native fallback routes, and
120+
* defers DI resolution and the starting of the embedded in-process gRPC server to the {@code
121+
* onStarting} lifecycle hook. * @param app The target Jooby application.
122+
*
123+
* @throws Exception If an error occurs during installation.
124+
*/
46125
@Override
47126
public void install(@NonNull Jooby app) throws Exception {
48127
var serverName = app.getName();
@@ -78,6 +157,14 @@ public void install(@NonNull Jooby app) throws Exception {
78157
});
79158
}
80159

160+
/**
161+
* Internal helper to register a service with the gRPC builder, extract its method descriptors,
162+
* and map a fail-fast route in the Jooby router. * @param app The target Jooby application.
163+
*
164+
* @param server The in-process server builder.
165+
* @param registry The method descriptor registry.
166+
* @param service The provisioned gRPC service to bind.
167+
*/
81168
private static void bindService(
82169
Jooby app,
83170
InProcessServerBuilder server,
@@ -89,7 +176,9 @@ private static void bindService(
89176
String methodFullName = descriptor.getFullMethodName();
90177
registry.put(methodFullName, descriptor);
91178
String routePath = "/" + methodFullName;
92-
//
179+
180+
// Map a fallback route. If a request hits this, it means the native SPI interceptor
181+
// failed to upgrade the request, typically due to a missing HTTP/2 configuration.
93182
app.post(
94183
routePath,
95184
ctx -> {

0 commit comments

Comments
 (0)