From 1997593755fcf51c32c14133c2dda44df7cf4749 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 18 Oct 2024 14:42:36 +0100 Subject: [PATCH 1/3] Add tomcat sample (no autoconfig) Signed-off-by: Dave Syer --- samples/grpc-tomcat/README.md | 61 ++++++ samples/grpc-tomcat/pom.xml | 185 ++++++++++++++++++ .../grpc/sample/GrpcServerApplication.java | 38 ++++ .../grpc/sample/GrpcServerService.java | 47 +++++ .../grpc-tomcat/src/main/proto/hello.proto | 23 +++ .../native-image.properties | 2 + .../src/main/resources/application.properties | 3 + .../sample/GrpcServerApplicationTests.java | 57 ++++++ samples/pom.xml | 3 +- 9 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 samples/grpc-tomcat/README.md create mode 100644 samples/grpc-tomcat/pom.xml create mode 100644 samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java create mode 100644 samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerService.java create mode 100644 samples/grpc-tomcat/src/main/proto/hello.proto create mode 100644 samples/grpc-tomcat/src/main/resources/META-INF/native-image/org.springframework.samples/grpc-tomcat-sample/native-image.properties create mode 100644 samples/grpc-tomcat/src/main/resources/application.properties create mode 100644 samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java diff --git a/samples/grpc-tomcat/README.md b/samples/grpc-tomcat/README.md new file mode 100644 index 00000000..975f0634 --- /dev/null +++ b/samples/grpc-tomcat/README.md @@ -0,0 +1,61 @@ +# Spring Boot gRPC Sample + +This project is a copy one of the samples from the [gRPC Spring Boot Starter](https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/examples/local-grpc-server/build.gradle). Build and run any way you like to run Spring Boot. E.g: + +``` +$ ./mvnw spring-boot:run +... + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v3.0.0) + +2022-12-08T05:32:24.934-08:00 INFO 551632 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 17.0.5 with PID 551632 (/home/dsyer/dev/scratch/demo/target/classes started by dsyer in /home/dsyer/dev/scratch/demo) +2022-12-08T05:32:24.938-08:00 INFO 551632 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default" +2022-12-08T05:32:25.377-08:00 WARN 551632 --- [ main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: net.devh.boot.grpc.server.autoconfigure.GrpcHealthServiceAutoConfiguration +2022-12-08T05:32:25.416-08:00 WARN 551632 --- [ main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration +2022-12-08T05:32:25.425-08:00 WARN 551632 --- [ main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration +2022-12-08T05:32:25.427-08:00 INFO 551632 --- [ main] g.s.a.GrpcServerFactoryAutoConfiguration : Detected grpc-netty: Creating NettyGrpcServerFactory +2022-12-08T05:32:25.712-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: Simple, bean: grpcServerService, class: com.example.demo.GrpcServerService +2022-12-08T05:32:25.712-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.health.v1.Health, bean: grpcHealthService, class: io.grpc.protobuf.services.HealthServiceImpl +2022-12-08T05:32:25.712-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.reflection.v1alpha.ServerReflection, bean: protoReflectionService, class: io.grpc.protobuf.services.ProtoReflectionService +2022-12-08T05:32:25.820-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.GrpcServerLifecycle : gRPC Server started, listening on address: *, port: 9090 +2022-12-08T05:32:25.831-08:00 INFO 551632 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.264 seconds (process running for 1.623) +``` + +The server starts by default on port 9090. Test with [gRPCurl](https://github.com/fullstorydev/grpcurl): + +``` +$ grpcurl -d '{"name":"Hi"}' -plaintext localhost:9090 Simple.SayHello +{ + "message": "Hello ==\u003e Hi" +} +``` + +## Native Image + +The app compiles to a native image if the JVM is GraalVM: + +``` +$ ./mvnw -Pnative native:compile +$ ./target/demo + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v3.0.0) + +2022-12-08T05:36:54.365-08:00 INFO 554359 --- [ main] com.example.demo.DemoApplication : Starting AOT-processed DemoApplication using Java 17.0.5 with PID 554359 (/home/dsyer/dev/scratch/demo/target/demo started by dsyer in /home/dsyer/dev/scratch/demo) +2022-12-08T05:36:54.366-08:00 INFO 554359 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default" +2022-12-08T05:36:54.377-08:00 INFO 554359 --- [ main] g.s.a.GrpcServerFactoryAutoConfiguration : Detected grpc-netty: Creating NettyGrpcServerFactory +2022-12-08T05:36:54.392-08:00 INFO 554359 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: Simple, bean: grpcServerService, class: com.example.demo.GrpcServerService +2022-12-08T05:36:54.392-08:00 INFO 554359 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.health.v1.Health, bean: grpcHealthService, class: io.grpc.protobuf.services.HealthServiceImpl +2022-12-08T05:36:54.392-08:00 INFO 554359 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.reflection.v1alpha.ServerReflection, bean: protoReflectionService, class: io.grpc.protobuf.services.ProtoReflectionService +2022-12-08T05:36:54.396-08:00 INFO 554359 --- [ main] n.d.b.g.s.s.GrpcServerLifecycle : gRPC Server started, listening on address: *, port: 9090 +2022-12-08T05:36:54.396-08:00 INFO 554359 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.046 seconds (process running for 0.052) +``` diff --git a/samples/grpc-tomcat/pom.xml b/samples/grpc-tomcat/pom.xml new file mode 100644 index 00000000..f98a4f99 --- /dev/null +++ b/samples/grpc-tomcat/pom.xml @@ -0,0 +1,185 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + org.springframework.grpc + grpc-tomcat-sample + 0.1.0-SNAPSHOT + Spring gRPC Server Sample + Demo project for Spring gRPC + + + + + + + + + + + + + + + 17 + 0.0.39 + 3.25.5 + 1.63.2 + + + + + org.springframework.grpc + spring-grpc-dependencies + 0.1.0-SNAPSHOT + pom + import + + + + + + org.springframework.grpc + spring-grpc-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + io.grpc + grpc-services + + + io.grpc + grpc-servlet-jakarta + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.grpc + spring-grpc-test + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.graalvm.buildtools + native-maven-plugin + + + --verbose + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + io.spring.javaformat + spring-javaformat-maven-plugin + ${spring-javaformat-maven-plugin.version} + + + + validate + true + + validate + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + + compile + compile-custom + + + + + + jakarta_omit + + + com.google.protobuf:protoc:${protobuf-java.version}:exe:${os.detected.classifier} + grpc-java + + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + \ No newline at end of file diff --git a/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java b/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java new file mode 100644 index 00000000..090ddfcf --- /dev/null +++ b/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java @@ -0,0 +1,38 @@ +package org.springframework.grpc.sample; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.grpc.autoconfigure.server.GrpcServerAutoConfiguration; + +import io.grpc.BindableService; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.servlet.jakarta.GrpcServlet; + +@SpringBootApplication(exclude = GrpcServerAutoConfiguration.class) +public class GrpcServerApplication { + + public static void main(String[] args) { + SpringApplication.run(GrpcServerApplication.class, args); + } + + @Bean + public ServletRegistrationBean grpcServlet(List services) { + List paths = services.stream() + .map(service -> "/" + service.bindService().getServiceDescriptor().getName() + "/*") + .collect(Collectors.toList()); + ServletRegistrationBean servlet = new ServletRegistrationBean<>(new GrpcServlet(services)); + servlet.setUrlMappings(paths); + return servlet; + } + + @Bean + public BindableService serverReflection() { + return ProtoReflectionService.newInstance(); + } + +} diff --git a/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerService.java b/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerService.java new file mode 100644 index 00000000..fdf0ba7e --- /dev/null +++ b/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerService.java @@ -0,0 +1,47 @@ +package org.springframework.grpc.sample; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.grpc.sample.proto.HelloReply; +import org.springframework.grpc.sample.proto.HelloRequest; +import org.springframework.grpc.sample.proto.SimpleGrpc; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import io.grpc.stub.StreamObserver; + +@Service +public class GrpcServerService extends SimpleGrpc.SimpleImplBase { + + private static Log log = LogFactory.getLog(GrpcServerService.class); + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + log.info("Hello " + req.getName()); + HelloReply reply = HelloReply.newBuilder().setMessage("Hello ==> " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + + @Override + @Async + public void streamHello(HelloRequest req, StreamObserver responseObserver) { + log.info("Hello " + req.getName()); + int count = 0; + while (count < 10) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello(" + count + ") ==> " + req.getName()).build(); + responseObserver.onNext(reply); + count++; + try { + Thread.sleep(1000L); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + responseObserver.onError(e); + return; + } + } + responseObserver.onCompleted(); + } + +} \ No newline at end of file diff --git a/samples/grpc-tomcat/src/main/proto/hello.proto b/samples/grpc-tomcat/src/main/proto/hello.proto new file mode 100644 index 00000000..731679cd --- /dev/null +++ b/samples/grpc-tomcat/src/main/proto/hello.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.springframework.grpc.sample.proto"; +option java_outer_classname = "HelloWorldProto"; + +// The greeting service definition. +service Simple { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) { + } + rpc StreamHello(HelloRequest) returns (stream 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; +} \ No newline at end of file diff --git a/samples/grpc-tomcat/src/main/resources/META-INF/native-image/org.springframework.samples/grpc-tomcat-sample/native-image.properties b/samples/grpc-tomcat/src/main/resources/META-INF/native-image/org.springframework.samples/grpc-tomcat-sample/native-image.properties new file mode 100644 index 00000000..5c434515 --- /dev/null +++ b/samples/grpc-tomcat/src/main/resources/META-INF/native-image/org.springframework.samples/grpc-tomcat-sample/native-image.properties @@ -0,0 +1,2 @@ +# Ignored unless building in Nix (https://github.com/oracle/graal/issues/8639) +Args = -ENIX_LDFLAGS -ENIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu diff --git a/samples/grpc-tomcat/src/main/resources/application.properties b/samples/grpc-tomcat/src/main/resources/application.properties new file mode 100644 index 00000000..5f753c5b --- /dev/null +++ b/samples/grpc-tomcat/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.application.name=grpc-tomcat +server.port=9090 +server.http2.enabled=true \ No newline at end of file diff --git a/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java b/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java new file mode 100644 index 00000000..78499260 --- /dev/null +++ b/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java @@ -0,0 +1,57 @@ +package org.springframework.grpc.sample; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; +import org.springframework.grpc.client.GrpcChannelFactory; +import org.springframework.grpc.sample.proto.HelloReply; +import org.springframework.grpc.sample.proto.HelloRequest; +import org.springframework.grpc.sample.proto.SimpleGrpc; +import org.springframework.test.annotation.DirtiesContext; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class GrpcServerApplicationTests { + + private static Log log = LogFactory.getLog(GrpcServerApplicationTests.class); + + public static void main(String[] args) { + new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class).profiles("ssl").run(args); + } + + @Autowired + private SimpleGrpc.SimpleBlockingStub stub; + + @Test + @DirtiesContext + void contextLoads() { + } + + @Test + @DirtiesContext + void serverResponds() { + log.info("Testing"); + HelloReply response = stub.sayHello(HelloRequest.newBuilder().setName("Alien").build()); + assertEquals("Hello ==> Alien", response.getMessage()); + } + + @TestConfiguration + static class ExtraConfiguration { + + @Bean + @Lazy + SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels, @LocalServerPort int port) { + return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:" + port).build()); + } + + } + +} diff --git a/samples/pom.xml b/samples/pom.xml index 56a037b1..1a329187 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -17,6 +17,7 @@ grpc-server + grpc-tomcat @@ -31,4 +32,4 @@ - \ No newline at end of file + From c358f6ef4db9862b080d604be885339c98d017e7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 23 Oct 2024 10:58:14 +0100 Subject: [PATCH 2/3] Move GrpcServlet to autoconfiguration Signed-off-by: Dave Syer --- .../grpc/sample/GrpcServerApplication.java | 18 +---- .../sample/GrpcServerApplicationTests.java | 2 +- spring-grpc-spring-boot-autoconfigure/pom.xml | 10 +++ .../server/GrpcServerAutoConfiguration.java | 19 ++--- .../GrpcServerFactoryAutoConfiguration.java | 74 +++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../GrpcServerAutoConfigurationTests.java | 6 +- 7 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryAutoConfiguration.java diff --git a/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java b/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java index 090ddfcf..d6052094 100644 --- a/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java +++ b/samples/grpc-tomcat/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java @@ -1,35 +1,19 @@ package org.springframework.grpc.sample; -import java.util.List; -import java.util.stream.Collectors; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; -import org.springframework.grpc.autoconfigure.server.GrpcServerAutoConfiguration; import io.grpc.BindableService; import io.grpc.protobuf.services.ProtoReflectionService; -import io.grpc.servlet.jakarta.GrpcServlet; -@SpringBootApplication(exclude = GrpcServerAutoConfiguration.class) +@SpringBootApplication public class GrpcServerApplication { public static void main(String[] args) { SpringApplication.run(GrpcServerApplication.class, args); } - @Bean - public ServletRegistrationBean grpcServlet(List services) { - List paths = services.stream() - .map(service -> "/" + service.bindService().getServiceDescriptor().getName() + "/*") - .collect(Collectors.toList()); - ServletRegistrationBean servlet = new ServletRegistrationBean<>(new GrpcServlet(services)); - servlet.setUrlMappings(paths); - return servlet; - } - @Bean public BindableService serverReflection() { return ProtoReflectionService.newInstance(); diff --git a/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java b/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java index 78499260..fd747cbd 100644 --- a/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java +++ b/samples/grpc-tomcat/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java @@ -24,7 +24,7 @@ public class GrpcServerApplicationTests { private static Log log = LogFactory.getLog(GrpcServerApplicationTests.class); public static void main(String[] args) { - new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class).profiles("ssl").run(args); + new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class).run(args); } @Autowired diff --git a/spring-grpc-spring-boot-autoconfigure/pom.xml b/spring-grpc-spring-boot-autoconfigure/pom.xml index 4c5211d1..23a7e947 100644 --- a/spring-grpc-spring-boot-autoconfigure/pom.xml +++ b/spring-grpc-spring-boot-autoconfigure/pom.xml @@ -45,6 +45,16 @@ ${project.version} true + + org.apache.tomcat.embed + tomcat-embed-core + true + + + io.grpc + grpc-servlet-jakarta + true + io.grpc grpc-netty-shaded diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java index fe82d868..29339398 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java @@ -15,14 +15,9 @@ */ package org.springframework.grpc.autoconfigure.server; -import io.grpc.BindableService; - -import io.grpc.CompressorRegistry; -import io.grpc.DecompressorRegistry; -import io.grpc.netty.NettyServerBuilder; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -30,14 +25,17 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; import org.springframework.grpc.autoconfigure.common.codec.GrpcCodecConfiguration; import org.springframework.grpc.server.GrpcServerFactory; import org.springframework.grpc.server.ServerBuilderCustomizer; import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; +import io.grpc.BindableService; +import io.grpc.CompressorRegistry; +import io.grpc.DecompressorRegistry; +import io.grpc.netty.NettyServerBuilder; + /** * {@link EnableAutoConfiguration Auto-configuration} for gRPC server-side components. *

@@ -48,12 +46,11 @@ * @author Chris Bono */ @AutoConfiguration -@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@AutoConfigureAfter(GrpcServerFactoryAutoConfiguration.class) @ConditionalOnClass(BindableService.class) @ConditionalOnBean(BindableService.class) @EnableConfigurationProperties(GrpcServerProperties.class) -@Import({ GrpcServerFactoryConfigurations.ShadedNettyServerFactoryConfiguration.class, - GrpcServerFactoryConfigurations.NettyServerFactoryConfiguration.class, GrpcCodecConfiguration.class }) +@Import({ GrpcCodecConfiguration.class }) public class GrpcServerAutoConfiguration { private final GrpcServerProperties properties; diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryAutoConfiguration.java new file mode 100644 index 00000000..7b7361a2 --- /dev/null +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryAutoConfiguration.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.grpc.autoconfigure.server; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; + +import io.grpc.BindableService; +import io.grpc.servlet.jakarta.GrpcServlet; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for gRPC server-side components. + *

+ * gRPC must be on the classpath and at least one {@link BindableService} bean registered + * in the context in order for the auto-configuration to execute. + * + * @author David Syer + * @author Chris Bono + */ +@AutoConfiguration +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@ConditionalOnClass(BindableService.class) +public class GrpcServerFactoryAutoConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnNotWebApplication + @Import({ GrpcServerFactoryConfigurations.ShadedNettyServerFactoryConfiguration.class, + GrpcServerFactoryConfigurations.NettyServerFactoryConfiguration.class }) + static class GrpcServerFactoryConfiguration { + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication + static class GrpcServletConfiguration { + + @Bean + public ServletRegistrationBean grpcServlet(List services) { + List paths = services.stream() + .map(service -> "/" + service.bindService().getServiceDescriptor().getName() + "/*") + .collect(Collectors.toList()); + ServletRegistrationBean servlet = new ServletRegistrationBean<>(new GrpcServlet(services)); + servlet.setUrlMappings(paths); + return servlet; + } + + } + +} diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 80bec31d..a7e2a86e 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ +org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration org.springframework.grpc.autoconfigure.server.GrpcServerAutoConfiguration org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java index 79df63b2..312bfaff 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java @@ -66,7 +66,8 @@ private ApplicationContextRunner contextRunner() { when(service.bindService()).thenReturn(serviceDefinition); // NOTE: we use noop server lifecycle to avoid startup return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, SslAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, + GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class)) .withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock) .withBean(BindableService.class, () -> service); } @@ -77,7 +78,8 @@ private ApplicationContextRunner contextRunnerWithLifecyle() { when(service.bindService()).thenReturn(serviceDefinition); // NOTE: we use noop server lifecycle to avoid startup return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, SslAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, + GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class)) .withBean(BindableService.class, () -> service); } From b5a623231a8e7358152de98abacdb1f853ab1b47 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 23 Oct 2024 11:13:24 +0100 Subject: [PATCH 3/3] Add a test for autoconfig of servlet Signed-off-by: Dave Syer --- .../modules/ROOT/partials/_configprops.adoc | 20 ++-- spring-grpc-spring-boot-autoconfigure/pom.xml | 11 ++- .../GrpcServletAutoConfigurationTests.java | 96 +++++++++++++++++++ 3 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServletAutoConfigurationTests.java diff --git a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc index b3f535e3..00497397 100644 --- a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc +++ b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc @@ -12,23 +12,23 @@ |spring.grpc.client.default-channel.max-inbound-message-size | | |spring.grpc.client.default-channel.max-inbound-metadata-size | | |spring.grpc.client.default-channel.negotiation-type | | The negotiation type for the channel. Default is {@link NegotiationType#PLAINTEXT}. -|spring.grpc.client.default-channel.secure | `+++true+++` | Flag to say that strict SSL checks are not enabled (so the remote certificate could be anonymous). +|spring.grpc.client.default-channel.secure | | Flag to say that strict SSL checks are not enabled (so the remote certificate could be anonymous). |spring.grpc.client.default-channel.ssl.bundle | | SSL bundle name. |spring.grpc.client.default-channel.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise. |spring.grpc.client.default-channel.user-agent | | |spring.grpc.server.address | | The address to bind to. could be a host:port combination or a pseudo URL like static://host:port. Can not be set if host or port are set independently. -|spring.grpc.server.host | `+++*+++` | Server address to bind to. The default is any IP address ('*'). +|spring.grpc.server.host | | Server address to bind to. The default is any IP address ('*'). |spring.grpc.server.keep-alive.max-age | | Maximum time a connection may exist before being gracefully terminated (default infinite). |spring.grpc.server.keep-alive.max-age-grace | | Maximum time for graceful connection termination (default infinite). |spring.grpc.server.keep-alive.max-idle | | Maximum time a connection can remain idle before being gracefully terminated (default infinite). -|spring.grpc.server.keep-alive.permit-time | `+++5m+++` | Maximum keep-alive time clients are permitted to configure (default 5m). -|spring.grpc.server.keep-alive.permit-without-calls | `+++false+++` | Whether clients are permitted to send keep alive pings when there are no outstanding RPCs on the connection (default false). -|spring.grpc.server.keep-alive.time | `+++2h+++` | Duration without read activity before sending a keep alive ping (default 2h). -|spring.grpc.server.keep-alive.timeout | `+++20s+++` | Maximum time to wait for read activity after sending a keep alive ping. If sender does not receive an acknowledgment within this time, it will close the connection (default 20s). -|spring.grpc.server.max-inbound-message-size | `+++4194304B+++` | Maximum message size allowed to be received by the server (default 4MiB). -|spring.grpc.server.max-inbound-metadata-size | `+++8192B+++` | Maximum metadata size allowed to be received by the server (default 8KiB). -|spring.grpc.server.port | `+++9090+++` | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090. -|spring.grpc.server.shutdown-grace-period | `+++30s+++` | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds. +|spring.grpc.server.keep-alive.permit-time | | Maximum keep-alive time clients are permitted to configure (default 5m). +|spring.grpc.server.keep-alive.permit-without-calls | | Whether clients are permitted to send keep alive pings when there are no outstanding RPCs on the connection (default false). +|spring.grpc.server.keep-alive.time | | Duration without read activity before sending a keep alive ping (default 2h). +|spring.grpc.server.keep-alive.timeout | | Maximum time to wait for read activity after sending a keep alive ping. If sender does not receive an acknowledgment within this time, it will close the connection (default 20s). +|spring.grpc.server.max-inbound-message-size | | Maximum message size allowed to be received by the server (default 4MiB). +|spring.grpc.server.max-inbound-metadata-size | | Maximum metadata size allowed to be received by the server (default 8KiB). +|spring.grpc.server.port | | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090. +|spring.grpc.server.shutdown-grace-period | | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds. |spring.grpc.server.ssl.bundle | | SSL bundle name. |spring.grpc.server.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise. diff --git a/spring-grpc-spring-boot-autoconfigure/pom.xml b/spring-grpc-spring-boot-autoconfigure/pom.xml index 23a7e947..5a892610 100644 --- a/spring-grpc-spring-boot-autoconfigure/pom.xml +++ b/spring-grpc-spring-boot-autoconfigure/pom.xml @@ -46,8 +46,13 @@ true - org.apache.tomcat.embed - tomcat-embed-core + org.springframework + spring-web + true + + + jakarta.servlet + jakarta.servlet-api true @@ -86,4 +91,4 @@ - + \ No newline at end of file diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServletAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServletAutoConfigurationTests.java new file mode 100644 index 00000000..5e83f9bf --- /dev/null +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServletAutoConfigurationTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.grpc.autoconfigure.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.grpc.server.GrpcServerFactory; +import org.springframework.grpc.server.NettyGrpcServerFactory; +import org.springframework.grpc.server.ServerBuilderCustomizer; +import org.springframework.grpc.server.ShadedNettyGrpcServerFactory; +import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; + +import io.grpc.BindableService; +import io.grpc.Grpc; +import io.grpc.ServerBuilder; +import io.grpc.ServerServiceDefinition; +import io.grpc.ServiceDescriptor; +import io.grpc.netty.NettyServerBuilder; + +/** + * Tests for {@link GrpcServerAutoConfiguration}. + * + * @author Chris Bono + */ +class GrpcServletAutoConfigurationTests { + + private WebApplicationContextRunner contextRunner() { + BindableService service = mock(); + ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder("my-service").build(); + when(service.bindService()).thenReturn(serviceDefinition); + // NOTE: we use noop server lifecycle to avoid startup + return new WebApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(GrpcServerAutoConfiguration.class, GrpcServerFactoryAutoConfiguration.class)) + .withBean(BindableService.class, () -> service); + } + + @Test + void whenGrpcNotOnClasspathAutoConfigurationIsSkipped() { + this.contextRunner() + .withClassLoader(new FilteredClassLoader(BindableService.class)) + .run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class) + .doesNotHaveBean(ServletRegistrationBean.class)); + } + + @Test + void whenNoBindableServicesRegisteredAutoConfigurationIsSkipped() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class)) + .run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class) + .doesNotHaveBean(ServletRegistrationBean.class)); + } + + @Test + void whenWebApplicationServletIsAutoConfigured() { + this.contextRunner().run((context) -> assertThat(context).getBean(ServletRegistrationBean.class).isNotNull()); + } + +}