Skip to content

Commit 473539f

Browse files
committed
- code cleanup, bug fixing
- ref #3875
1 parent a38e881 commit 473539f

File tree

34 files changed

+865
-1289
lines changed

34 files changed

+865
-1289
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
import java.util.Map;
1010
import java.util.function.Consumer;
1111

12+
import edu.umd.cs.findbugs.annotations.Nullable;
13+
1214
/** Server-agnostic abstraction for HTTP/2 trailing-header exchanges. */
1315
public interface GrpcExchange {
1416

1517
String getRequestPath();
1618

17-
String getHeader(String name);
19+
@Nullable String getHeader(String name);
1820

1921
Map<String, String> getHeaders();
2022

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
/** Intercepts and processes gRPC exchanges. */
1414
public interface GrpcProcessor {
15+
16+
/** Checks if the given URI path exactly matches a registered gRPC method. */
17+
boolean isGrpcMethod(String path);
18+
1519
/**
1620
* @return A subscriber that the server will feed ByteBuffer chunks into, or null if the exchange
1721
* was rejected/unimplemented.

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

Lines changed: 0 additions & 28 deletions
This file was deleted.

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

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,24 @@
55
*/
66
package io.jooby.grpc;
77

8+
import java.util.HashMap;
89
import java.util.List;
10+
import java.util.Map;
911

1012
import org.slf4j.bridge.SLF4JBridgeHandler;
1113

1214
import edu.umd.cs.findbugs.annotations.NonNull;
1315
import io.grpc.BindableService;
16+
import io.grpc.MethodDescriptor;
1417
import io.grpc.Server;
1518
import io.grpc.inprocess.InProcessChannelBuilder;
1619
import io.grpc.inprocess.InProcessServerBuilder;
17-
import io.grpc.protobuf.services.ProtoReflectionServiceV1;
1820
import io.jooby.*;
21+
import io.jooby.internal.grpc.DefaultGrpcProcessor;
1922

2023
public class GrpcModule implements Extension {
2124
private final List<BindableService> services;
22-
private final GrpcMethodRegistry methodRegistry = new GrpcMethodRegistry();
23-
private final String serverName = "jooby-internal-" + System.nanoTime();
25+
private final Map<String, MethodDescriptor<?, ?>> registry = new HashMap<>();
2426
private Server grpcServer;
2527

2628
static {
@@ -36,35 +38,47 @@ public GrpcModule(BindableService... services) {
3638

3739
@Override
3840
public void install(@NonNull Jooby app) throws Exception {
39-
var builder = InProcessServerBuilder.forName(serverName).directExecutor();
41+
var serverName = app.getName();
42+
var builder = InProcessServerBuilder.forName(serverName);
4043

4144
// 1. Register user-provided services
42-
for (BindableService service : services) {
45+
for (var service : services) {
4346
builder.addService(service);
44-
methodRegistry.registerService(service);
45-
}
47+
for (var method : service.bindService().getMethods()) {
48+
var descriptor = method.getMethodDescriptor();
49+
String methodFullName = descriptor.getFullMethodName();
50+
registry.put(methodFullName, descriptor);
51+
String routePath = "/" + methodFullName;
4652

47-
BindableService reflectionService = ProtoReflectionServiceV1.newInstance();
48-
builder.addService(reflectionService);
49-
methodRegistry.registerService(reflectionService);
53+
//
54+
app.post(
55+
routePath,
56+
ctx -> {
57+
throw new IllegalStateException(
58+
"gRPC request reached the standard HTTP router for path: "
59+
+ routePath
60+
+ ". "
61+
+ "This means the native gRPC server interceptor was bypassed. "
62+
+ "Ensure you are running Jetty, Netty, or Undertow with HTTP/2 enabled, "
63+
+ "and that the GrpcProcessor SPI is correctly loaded.");
64+
});
65+
}
66+
}
5067

5168
this.grpcServer = builder.build().start();
5269

70+
// KEEP .directExecutor() here!
71+
// This ensures that when the background gRPC worker finishes, it instantly pushes
72+
// the response back to Undertow/Netty without wasting time on another thread hop.
5373
var channel = InProcessChannelBuilder.forName(serverName).directExecutor().build();
74+
var services = app.getServices();
75+
var bridge = new DefaultGrpcProcessor(channel, registry);
5476

55-
var bridge = new UnifiedGrpcBridge(channel, methodRegistry);
5677
// Register it in the Service Registry so the server layer can find it
57-
app.getServices().put(UnifiedGrpcBridge.class, bridge);
58-
59-
app.getServices().put(GrpcProcessor.class, bridge);
60-
61-
// Mount the bridge.
62-
// app.post("/*", bridge);
78+
services.put(DefaultGrpcProcessor.class, bridge);
79+
services.put(GrpcProcessor.class, bridge);
6380

64-
app.onStop(
65-
() -> {
66-
channel.shutdownNow();
67-
grpcServer.shutdownNow();
68-
});
81+
app.onStop(channel::shutdownNow);
82+
app.onStop(grpcServer::shutdownNow);
6983
}
7084
}

modules/jooby-grpc/src/main/java/io/jooby/grpc/UnifiedGrpcBridge.java renamed to modules/jooby-grpc/src/main/java/io/jooby/internal/grpc/DefaultGrpcProcessor.java

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
44
* Copyright 2014 Edgar Espina
55
*/
6-
package io.jooby.grpc;
6+
package io.jooby.internal.grpc;
77

88
import java.io.ByteArrayInputStream;
99
import java.io.IOException;
@@ -16,8 +16,8 @@
1616
import org.slf4j.Logger;
1717
import org.slf4j.LoggerFactory;
1818

19+
import edu.umd.cs.findbugs.annotations.NonNull;
1920
import io.grpc.CallOptions;
20-
import io.grpc.ClientCall;
2121
import io.grpc.ManagedChannel;
2222
import io.grpc.MethodDescriptor;
2323
import io.grpc.Status;
@@ -27,7 +27,7 @@
2727
import io.jooby.GrpcExchange;
2828
import io.jooby.GrpcProcessor;
2929

30-
public class UnifiedGrpcBridge implements GrpcProcessor {
30+
public class DefaultGrpcProcessor implements GrpcProcessor {
3131

3232
// Minimal Marshaller to pass raw bytes through the bridge
3333
private static class RawMarshaller implements MethodDescriptor.Marshaller<byte[]> {
@@ -48,24 +48,39 @@ public byte[] parse(InputStream stream) {
4848

4949
private final Logger log = LoggerFactory.getLogger(getClass());
5050
private final ManagedChannel channel;
51-
private final GrpcMethodRegistry methodRegistry;
51+
private final Map<String, MethodDescriptor<?, ?>> registry;
5252

53-
public UnifiedGrpcBridge(ManagedChannel channel, GrpcMethodRegistry methodRegistry) {
53+
public DefaultGrpcProcessor(
54+
ManagedChannel channel, Map<String, MethodDescriptor<?, ?>> registry) {
5455
this.channel = channel;
55-
this.methodRegistry = methodRegistry;
56+
this.registry = registry;
5657
}
5758

5859
@Override
59-
public Flow.Subscriber<ByteBuffer> process(GrpcExchange exchange) {
60+
public boolean isGrpcMethod(String path) {
61+
// gRPC paths typically come in as "/package.Service/Method"
62+
// Our registry stores them as "package.Service/Method"
63+
String methodName = path.startsWith("/") ? path.substring(1) : path;
64+
65+
// Quick O(1) hash map lookup
66+
return registry.get(methodName) != null;
67+
}
68+
69+
@Override
70+
public @NonNull Flow.Subscriber<ByteBuffer> process(@NonNull GrpcExchange exchange) {
6071
// Route paths: /{package.Service}/{Method}
6172
String path = exchange.getRequestPath();
6273
// Remove the leading slash to match the gRPC method registry format
63-
var descriptor = methodRegistry.get(path.substring(1));
74+
var descriptor = registry.get(path.substring(1));
6475

6576
if (descriptor == null) {
66-
log.warn("Method not found in bridge registry: {}", path);
67-
exchange.close(Status.UNIMPLEMENTED.getCode().value(), "Method not found");
68-
return null;
77+
// MUST never occur, it is guarded by {@link #isGrpcMethod}
78+
throw new IllegalStateException(
79+
"Unregistered gRPC method: '"
80+
+ path
81+
+ "'. "
82+
+ "This request bypassed the GrpcProcessor.isGrpcMethod() guard, "
83+
+ "indicating a bug or misconfiguration in the native server interceptor.");
6984
}
7085

7186
var method =
@@ -76,25 +91,24 @@ public Flow.Subscriber<ByteBuffer> process(GrpcExchange exchange) {
7691
.setResponseMarshaller(new RawMarshaller())
7792
.build();
7893

79-
CallOptions callOptions = extractCallOptions(exchange);
80-
io.grpc.Metadata metadata = extractMetadata(exchange);
94+
var callOptions = extractCallOptions(exchange);
95+
var metadata = extractMetadata(exchange);
8196

82-
io.grpc.Channel interceptedChannel =
97+
var interceptedChannel =
8398
io.grpc.ClientInterceptors.intercept(
8499
channel, io.grpc.stub.MetadataUtils.newAttachHeadersInterceptor(metadata));
85100

86-
ClientCall<byte[], byte[]> call = interceptedChannel.newCall(method, callOptions);
87-
AtomicBoolean isFinished = new AtomicBoolean(false);
101+
var call = interceptedChannel.newCall(method, callOptions);
102+
var isFinished = new AtomicBoolean(false);
88103

89104
boolean isUnaryOrServerStreaming =
90105
method.getType() == MethodDescriptor.MethodType.UNARY
91106
|| method.getType() == MethodDescriptor.MethodType.SERVER_STREAMING;
92107

93-
// 1. Create the effectively final bridge
94-
GrpcRequestBridge requestBridge = new GrpcRequestBridge(call, method.getType());
108+
var requestBridge = new GrpcRequestBridge(call, method.getType());
95109

96-
ClientResponseObserver<byte[], byte[]> responseObserver =
97-
new ClientResponseObserver<>() {
110+
var responseObserver =
111+
new ClientResponseObserver<byte[], byte[]>() {
98112

99113
@Override
100114
public void beforeStart(ClientCallStreamObserver<byte[]> requestStream) {
@@ -158,10 +172,10 @@ private CallOptions extractCallOptions(GrpcExchange exchange) {
158172
}
159173

160174
try {
161-
char unit = timeout.charAt(timeout.length() - 1);
162-
long value = Long.parseLong(timeout.substring(0, timeout.length() - 1));
175+
var unit = timeout.charAt(timeout.length() - 1);
176+
var value = Long.parseLong(timeout.substring(0, timeout.length() - 1));
163177

164-
java.util.concurrent.TimeUnit timeUnit =
178+
var timeUnit =
165179
switch (unit) {
166180
case 'H' -> java.util.concurrent.TimeUnit.HOURS;
167181
case 'M' -> java.util.concurrent.TimeUnit.MINUTES;
@@ -184,10 +198,10 @@ private CallOptions extractCallOptions(GrpcExchange exchange) {
184198

185199
/** Maps standard HTTP headers from the GrpcExchange into gRPC Metadata. */
186200
private io.grpc.Metadata extractMetadata(GrpcExchange exchange) {
187-
io.grpc.Metadata metadata = new io.grpc.Metadata();
201+
var metadata = new io.grpc.Metadata();
188202

189-
for (Map.Entry<String, String> header : exchange.getHeaders().entrySet()) {
190-
String key = header.getKey().toLowerCase();
203+
for (var header : exchange.getHeaders().entrySet()) {
204+
var key = header.getKey().toLowerCase();
191205

192206
if (key.startsWith(":")
193207
|| key.startsWith("grpc-")
@@ -212,7 +226,7 @@ private io.grpc.Metadata extractMetadata(GrpcExchange exchange) {
212226

213227
/** Prepends the 5-byte gRPC header and returns a ready-to-write ByteBuffer. */
214228
private ByteBuffer addGrpcHeader(byte[] payload) {
215-
ByteBuffer buffer = ByteBuffer.allocate(5 + payload.length);
229+
var buffer = ByteBuffer.allocate(5 + payload.length);
216230
buffer.put((byte) 0); // Compressed flag (0 = none)
217231
buffer.putInt(payload.length);
218232
buffer.put(payload);

modules/jooby-grpc/src/main/java/io/jooby/grpc/GrpcDeframer.java renamed to modules/jooby-grpc/src/main/java/io/jooby/internal/grpc/GrpcDeframer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
44
* Copyright 2014 Edgar Espina
55
*/
6-
package io.jooby.grpc;
6+
package io.jooby.internal.grpc;
77

88
import java.nio.ByteBuffer;
99
import java.util.function.Consumer;

modules/jooby-grpc/src/main/java/io/jooby/grpc/GrpcRequestBridge.java renamed to modules/jooby-grpc/src/main/java/io/jooby/internal/grpc/GrpcRequestBridge.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
44
* Copyright 2014 Edgar Espina
55
*/
6-
package io.jooby.grpc;
6+
package io.jooby.internal.grpc;
77

88
import java.nio.ByteBuffer;
99
import java.util.concurrent.Flow.Subscriber;

0 commit comments

Comments
 (0)