55 */
66package io .jooby .grpc ;
77
8- import java .util .HashMap ;
9- import java .util .List ;
10- import java .util .Map ;
8+ import java .util .*;
119
1210import org .slf4j .bridge .SLF4JBridgeHandler ;
1311
1412import edu .umd .cs .findbugs .annotations .NonNull ;
1513import io .grpc .BindableService ;
1614import io .grpc .MethodDescriptor ;
17- import io .grpc .Server ;
1815import io .grpc .inprocess .InProcessChannelBuilder ;
1916import io .grpc .inprocess .InProcessServerBuilder ;
2017import io .jooby .*;
2118import io .jooby .internal .grpc .DefaultGrpcProcessor ;
2219
2320public class GrpcModule implements Extension {
24- private final List <BindableService > services ;
25- private final Map <String , MethodDescriptor <?, ?>> registry = new HashMap <>();
26- private Server grpcServer ;
21+ private final List <BindableService > services = new ArrayList <>();
22+ private final List <Class <? extends BindableService >> serviceClasses = new ArrayList <>();
2723
2824 static {
2925 // Optionally remove existing handlers attached to the j.u.l root logger
@@ -33,52 +29,78 @@ public class GrpcModule implements Extension {
3329 }
3430
3531 public GrpcModule (BindableService ... services ) {
36- this .services = List .of (services );
32+ this .services .addAll (Arrays .asList (services ));
33+ }
34+
35+ @ SafeVarargs
36+ public GrpcModule (Class <? extends BindableService >... serviceClasses ) {
37+ bind (serviceClasses );
38+ }
39+
40+ @ SafeVarargs
41+ public final GrpcModule bind (Class <? extends BindableService >... serviceClasses ) {
42+ this .serviceClasses .addAll (List .of (serviceClasses ));
43+ return this ;
3744 }
3845
3946 @ Override
4047 public void install (@ NonNull Jooby app ) throws Exception {
4148 var serverName = app .getName ();
4249 var builder = InProcessServerBuilder .forName (serverName );
50+ final Map <String , MethodDescriptor <?, ?>> registry = new HashMap <>();
4351
4452 // 1. Register user-provided services
4553 for (var service : services ) {
46- builder .addService (service );
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 ;
52-
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- }
54+ bindService (app , builder , registry , service );
6655 }
6756
68- this .grpcServer = builder .build ().start ();
69-
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.
73- var channel = InProcessChannelBuilder .forName (serverName ).directExecutor ().build ();
7457 var services = app .getServices ();
75- var bridge = new DefaultGrpcProcessor (channel , registry );
58+ var processor = new DefaultGrpcProcessor (registry );
59+ services .put (GrpcProcessor .class , processor );
60+
61+ // Lazy init service from DI.
62+ app .onStarting (
63+ () -> {
64+ for (Class <? extends BindableService > serviceClass : serviceClasses ) {
65+ var service = app .require (serviceClass );
66+ bindService (app , builder , registry , service );
67+ }
68+ var grpcServer = builder .build ().start ();
7669
77- // Register it in the Service Registry so the server layer can find it
78- services .put (DefaultGrpcProcessor .class , bridge );
79- services .put (GrpcProcessor .class , bridge );
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.
73+ var channel = InProcessChannelBuilder .forName (serverName ).directExecutor ().build ();
74+ processor .setChannel (channel );
8075
81- app .onStop (channel ::shutdownNow );
82- app .onStop (grpcServer ::shutdownNow );
76+ app .onStop (channel ::shutdownNow );
77+ app .onStop (grpcServer ::shutdownNow );
78+ });
79+ }
80+
81+ private static void bindService (
82+ Jooby app ,
83+ InProcessServerBuilder server ,
84+ Map <String , MethodDescriptor <?, ?>> registry ,
85+ BindableService service ) {
86+ server .addService (service );
87+ for (var method : service .bindService ().getMethods ()) {
88+ var descriptor = method .getMethodDescriptor ();
89+ String methodFullName = descriptor .getFullMethodName ();
90+ registry .put (methodFullName , descriptor );
91+ String routePath = "/" + methodFullName ;
92+ //
93+ app .post (
94+ routePath ,
95+ ctx -> {
96+ throw new IllegalStateException (
97+ "gRPC request reached the standard HTTP router for path: "
98+ + routePath
99+ + ". "
100+ + "This means the native gRPC server interceptor was bypassed. "
101+ + "Ensure you are running Jetty, Netty, or Undertow with HTTP/2 enabled, "
102+ + "and that the GrpcProcessor SPI is correctly loaded." );
103+ });
104+ }
83105 }
84106}
0 commit comments