From 72afe9479c2c45d523d9fd8a44a4f1debb0f9831 Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Tue, 1 Jul 2025 08:22:27 -0500 Subject: [PATCH 1/3] Allow filtering of services This commit adds the ability to filter out services from being bound to server factories. Resolves #207 Signed-off-by: Andrey Litvitski Signed-off-by: Chris Bono --- .../grpc/server/DefaultGrpcServerFactory.java | 14 +++++-- .../server/InProcessGrpcServerFactory.java | 8 +++- .../grpc/server/NettyGrpcServerFactory.java | 10 +++-- .../server/ServerServiceDefinitionFilter.java | 37 +++++++++++++++++++ .../server/ShadedNettyGrpcServerFactory.java | 10 +++-- .../GrpcServerFactoryConfigurations.java | 17 ++++++--- .../GrpcServerAutoConfigurationTests.java | 3 ++ .../test/InProcessTestAutoConfiguration.java | 12 ++++-- 8 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java index 3b4646be..982f15f5 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2024-2025 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. @@ -28,6 +28,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.grpc.internal.GrpcUtils; +import org.springframework.lang.Nullable; import com.google.common.collect.Lists; import io.grpc.Grpc; @@ -49,6 +50,7 @@ * @param the type of server builder * @author David Syer * @author Chris Bono + * @author Andrey Litvitski * @see ServerProvider#provider() */ public class DefaultGrpcServerFactory> implements GrpcServerFactory { @@ -68,17 +70,21 @@ public class DefaultGrpcServerFactory> implements Grp private ClientAuth clientAuth; + private ServerServiceDefinitionFilter serviceFilter; + public DefaultGrpcServerFactory(String address, List> serverBuilderCustomizers) { this.address = address; this.serverBuilderCustomizers = Objects.requireNonNull(serverBuilderCustomizers, "serverBuilderCustomizers"); } public DefaultGrpcServerFactory(String address, List> serverBuilderCustomizers, - KeyManagerFactory keyManager, TrustManagerFactory trustManager, ClientAuth clientAuth) { + KeyManagerFactory keyManager, TrustManagerFactory trustManager, ClientAuth clientAuth, + @Nullable ServerServiceDefinitionFilter serviceFilter) { this(address, serverBuilderCustomizers); this.keyManager = keyManager; this.trustManager = trustManager; this.clientAuth = clientAuth; + this.serviceFilter = serviceFilter; } protected String address() { @@ -94,7 +100,9 @@ public Server createServer() { @Override public void addService(ServerServiceDefinition service) { - this.serviceList.add(service); + if (this.serviceFilter == null || this.serviceFilter.filter(service)) { + this.serviceList.add(service); + } } /** diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java index cdbc4ca1..259a2e5a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java @@ -17,18 +17,22 @@ import java.util.List; +import org.springframework.lang.Nullable; + import io.grpc.inprocess.InProcessServerBuilder; /** * {@link GrpcServerFactory} that can be used to create an in-process gRPC server. * * @author Chris Bono + * @author Andrey Litvitski */ public class InProcessGrpcServerFactory extends DefaultGrpcServerFactory { public InProcessGrpcServerFactory(String address, - List> serverBuilderCustomizers) { - super(address, serverBuilderCustomizers); + List> serverBuilderCustomizers, + @Nullable ServerServiceDefinitionFilter serviceFilter) { + super(address, serverBuilderCustomizers, null, null, null, serviceFilter); } @Override diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java index 2e6c9c1f..67aefcbd 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2024-2025 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. @@ -21,6 +21,8 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import org.springframework.lang.Nullable; + import io.grpc.TlsServerCredentials.ClientAuth; import io.grpc.netty.NettyServerBuilder; import io.netty.channel.epoll.EpollEventLoopGroup; @@ -32,13 +34,15 @@ * * @author David Syer * @author Chris Bono + * @author Andrey Litvitski */ public class NettyGrpcServerFactory extends DefaultGrpcServerFactory { public NettyGrpcServerFactory(String address, List> serverBuilderCustomizers, KeyManagerFactory keyManager, - TrustManagerFactory trustManager, ClientAuth clientAuth) { - super(address, serverBuilderCustomizers, keyManager, trustManager, clientAuth); + TrustManagerFactory trustManager, ClientAuth clientAuth, + @Nullable ServerServiceDefinitionFilter serviceFilter) { + super(address, serverBuilderCustomizers, keyManager, trustManager, clientAuth, serviceFilter); } @Override diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java new file mode 100644 index 00000000..bf294471 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023-2025 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.server; + +import io.grpc.ServerServiceDefinition; + +/** + * Strategy to determine whether a {@link ServerServiceDefinition} should be included for + * the {@link GrpcServerFactory server factory}. + * + * @author Andrey Litvitski + */ +@FunctionalInterface +public interface ServerServiceDefinitionFilter { + + /** + * Determine whether the given {@link ServerServiceDefinition} should be included for + * the {@link GrpcServerFactory server factory}. + * @param serviceDefinition the gRPC service definition under consideration. + * @return {@code true} if the service should be included; {@code false} otherwise. + */ + boolean filter(ServerServiceDefinition serviceDefinition); + +} diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java index 3ad992a8..ac14202a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2024-2025 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. @@ -21,6 +21,8 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import org.springframework.lang.Nullable; + import io.grpc.TlsServerCredentials.ClientAuth; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup; @@ -32,13 +34,15 @@ * * @author David Syer * @author Chris Bono + * @author Andrey Litvitski */ public class ShadedNettyGrpcServerFactory extends DefaultGrpcServerFactory { public ShadedNettyGrpcServerFactory(String address, List> serverBuilderCustomizers, KeyManagerFactory keyManager, - TrustManagerFactory trustManager, ClientAuth clientAuth) { - super(address, serverBuilderCustomizers, keyManager, trustManager, clientAuth); + TrustManagerFactory trustManager, ClientAuth clientAuth, + @Nullable ServerServiceDefinitionFilter serviceFilter) { + super(address, serverBuilderCustomizers, keyManager, trustManager, clientAuth, serviceFilter); } @Override diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java index 18b8d667..6516589f 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java @@ -30,11 +30,13 @@ import org.springframework.boot.ssl.SslBundles; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; +import org.springframework.lang.Nullable; import org.springframework.context.annotation.Configuration; import org.springframework.grpc.server.GrpcServerFactory; import org.springframework.grpc.server.InProcessGrpcServerFactory; import org.springframework.grpc.server.NettyGrpcServerFactory; import org.springframework.grpc.server.ServerBuilderCustomizer; +import org.springframework.grpc.server.ServerServiceDefinitionFilter; import org.springframework.grpc.server.ShadedNettyGrpcServerFactory; import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; import org.springframework.grpc.server.service.GrpcServiceConfigurer; @@ -62,7 +64,8 @@ static class ShadedNettyServerFactoryConfiguration { @Bean ShadedNettyGrpcServerFactory shadedNettyGrpcServerFactory(GrpcServerProperties properties, GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer, - ServerBuilderCustomizers serverBuilderCustomizers, SslBundles bundles) { + ServerBuilderCustomizers serverBuilderCustomizers, SslBundles bundles, + @Nullable ServerServiceDefinitionFilter serviceFilter) { ShadedNettyServerFactoryPropertyMapper mapper = new ShadedNettyServerFactoryPropertyMapper(properties); List> builderCustomizers = List .of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize); @@ -75,7 +78,7 @@ ShadedNettyGrpcServerFactory shadedNettyGrpcServerFactory(GrpcServerProperties p : io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE; } ShadedNettyGrpcServerFactory factory = new ShadedNettyGrpcServerFactory(properties.getAddress(), - builderCustomizers, keyManager, trustManager, properties.getSsl().getClientAuth()); + builderCustomizers, keyManager, trustManager, properties.getSsl().getClientAuth(), serviceFilter); serviceDiscoverer.findServices() .stream() .map((serviceSpec) -> serviceConfigurer.configure(serviceSpec)) @@ -104,7 +107,8 @@ static class NettyServerFactoryConfiguration { @Bean NettyGrpcServerFactory nettyGrpcServerFactory(GrpcServerProperties properties, GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer, - ServerBuilderCustomizers serverBuilderCustomizers, SslBundles bundles) { + ServerBuilderCustomizers serverBuilderCustomizers, SslBundles bundles, + @Nullable ServerServiceDefinitionFilter serviceFilter) { NettyServerFactoryPropertyMapper mapper = new NettyServerFactoryPropertyMapper(properties); List> builderCustomizers = List .of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize); @@ -117,7 +121,7 @@ NettyGrpcServerFactory nettyGrpcServerFactory(GrpcServerProperties properties, : InsecureTrustManagerFactory.INSTANCE; } NettyGrpcServerFactory factory = new NettyGrpcServerFactory(properties.getAddress(), builderCustomizers, - keyManager, trustManager, properties.getSsl().getClientAuth()); + keyManager, trustManager, properties.getSsl().getClientAuth(), serviceFilter); serviceDiscoverer.findServices() .stream() .map((serviceSpec) -> serviceConfigurer.configure(serviceSpec)) @@ -145,12 +149,13 @@ static class InProcessServerFactoryConfiguration { @Bean InProcessGrpcServerFactory inProcessGrpcServerFactory(GrpcServerProperties properties, GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer, - ServerBuilderCustomizers serverBuilderCustomizers) { + ServerBuilderCustomizers serverBuilderCustomizers, + @Nullable ServerServiceDefinitionFilter serviceFilter) { var mapper = new InProcessServerFactoryPropertyMapper(properties); List> builderCustomizers = List .of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize); InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(properties.getInprocess().getName(), - builderCustomizers); + builderCustomizers, serviceFilter); serviceDiscoverer.findServices() .stream() .map((serviceSpec) -> serviceConfigurer.configure(serviceSpec)) 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 2f7fd6c3..82b68bf7 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 @@ -45,10 +45,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.grpc.server.DefaultGrpcServerFactory; import org.springframework.grpc.server.GrpcServerFactory; import org.springframework.grpc.server.InProcessGrpcServerFactory; import org.springframework.grpc.server.NettyGrpcServerFactory; import org.springframework.grpc.server.ServerBuilderCustomizer; +import org.springframework.grpc.server.ServerServiceDefinitionFilter; import org.springframework.grpc.server.ShadedNettyGrpcServerFactory; import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; import org.springframework.grpc.server.service.DefaultGrpcServiceConfigurer; @@ -68,6 +70,7 @@ * Tests for {@link GrpcServerAutoConfiguration}. * * @author Chris Bono + * @author Andrey Litvitski */ class GrpcServerAutoConfigurationTests { diff --git a/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java b/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java index 8941513b..5242087f 100644 --- a/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java +++ b/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java @@ -34,9 +34,11 @@ import org.springframework.grpc.client.InProcessGrpcChannelFactory; import org.springframework.grpc.server.InProcessGrpcServerFactory; import org.springframework.grpc.server.ServerBuilderCustomizer; +import org.springframework.grpc.server.ServerServiceDefinitionFilter; import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; import org.springframework.grpc.server.service.GrpcServiceConfigurer; import org.springframework.grpc.server.service.GrpcServiceDiscoverer; +import org.springframework.lang.Nullable; import io.grpc.BindableService; import io.grpc.ChannelCredentials; @@ -56,8 +58,9 @@ public class InProcessTestAutoConfiguration { @Order(Ordered.HIGHEST_PRECEDENCE) TestInProcessGrpcServerFactory testInProcessGrpcServerFactory(GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer, - List> customizers) { - var factory = new TestInProcessGrpcServerFactory(address, customizers); + List> customizers, + @Nullable ServerServiceDefinitionFilter serviceFilter) { + var factory = new TestInProcessGrpcServerFactory(address, customizers, serviceFilter); serviceDiscoverer.findServices() .stream() .map((serviceSpec) -> serviceConfigurer.configure(serviceSpec)) @@ -86,8 +89,9 @@ GrpcServerLifecycle inProcessGrpcServerLifecycle(InProcessGrpcServerFactory fact public static class TestInProcessGrpcServerFactory extends InProcessGrpcServerFactory { public TestInProcessGrpcServerFactory(String address, - List> serverBuilderCustomizers) { - super(address, serverBuilderCustomizers); + List> serverBuilderCustomizers, + @Nullable ServerServiceDefinitionFilter serviceFilter) { + super(address, serverBuilderCustomizers, serviceFilter); } } From a4ce3b4f46e1277a6257f52d5ae5876c9e8f1607 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Tue, 1 Jul 2025 20:17:48 -0500 Subject: [PATCH 2/3] Polish "Allow filtering of services" This commit updates some tests and the contract slightly of the previous commit to allow a serverFactory parameter as part of the inputs to the service filter and tests all variants of server factories via `@ParameterizedTest` See #207 Signed-off-by: Chris Bono --- .../grpc/server/DefaultGrpcServerFactory.java | 2 +- .../server/ServerServiceDefinitionFilter.java | 11 ++- .../server/DefaultGrpcServerFactoryTests.java | 81 +++++++++++++++++ .../grpc/server/GrpcServerFactoryTests.java | 30 ------ .../GrpcServerAutoConfigurationTests.java | 91 ++++++++++++++++++- .../test/InProcessTestAutoConfiguration.java | 3 +- 6 files changed, 176 insertions(+), 42 deletions(-) create mode 100644 spring-grpc-core/src/test/java/org/springframework/grpc/server/DefaultGrpcServerFactoryTests.java delete mode 100644 spring-grpc-core/src/test/java/org/springframework/grpc/server/GrpcServerFactoryTests.java diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java index 982f15f5..a66e6340 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java @@ -100,7 +100,7 @@ public Server createServer() { @Override public void addService(ServerServiceDefinition service) { - if (this.serviceFilter == null || this.serviceFilter.filter(service)) { + if (this.serviceFilter == null || this.serviceFilter.filter(service, this)) { this.serviceList.add(service); } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java index bf294471..b805f253 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ServerServiceDefinitionFilter.java @@ -18,8 +18,8 @@ import io.grpc.ServerServiceDefinition; /** - * Strategy to determine whether a {@link ServerServiceDefinition} should be included for - * the {@link GrpcServerFactory server factory}. + * Strategy to determine whether a {@link ServerServiceDefinition} should be added to a + * {@link GrpcServerFactory server factory}. * * @author Andrey Litvitski */ @@ -27,11 +27,12 @@ public interface ServerServiceDefinitionFilter { /** - * Determine whether the given {@link ServerServiceDefinition} should be included for - * the {@link GrpcServerFactory server factory}. + * Determine whether the given {@link ServerServiceDefinition} should be added to the + * given {@link GrpcServerFactory server factory}. * @param serviceDefinition the gRPC service definition under consideration. + * @param serverFactory the server factory in use. * @return {@code true} if the service should be included; {@code false} otherwise. */ - boolean filter(ServerServiceDefinition serviceDefinition); + boolean filter(ServerServiceDefinition serviceDefinition, GrpcServerFactory serverFactory); } diff --git a/spring-grpc-core/src/test/java/org/springframework/grpc/server/DefaultGrpcServerFactoryTests.java b/spring-grpc-core/src/test/java/org/springframework/grpc/server/DefaultGrpcServerFactoryTests.java new file mode 100644 index 00000000..20b1f792 --- /dev/null +++ b/spring-grpc-core/src/test/java/org/springframework/grpc/server/DefaultGrpcServerFactoryTests.java @@ -0,0 +1,81 @@ +/* + * 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.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.List; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import io.grpc.ServerServiceDefinition; + +/** + * Tests for {@link DefaultGrpcServerFactory}. + */ +class DefaultGrpcServerFactoryTests { + + @Nested + class WithServiceFilter { + + @Test + void whenNoFilterThenAllServicesAdded() { + ServerServiceDefinition serviceDef1 = mock(); + ServerServiceDefinition serviceDef2 = mock(); + DefaultGrpcServerFactory serverFactory = new DefaultGrpcServerFactory("myhost:5150", List.of(), null, null, + null, null); + serverFactory.addService(serviceDef2); + serverFactory.addService(serviceDef1); + assertThat(serverFactory) + .extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class)) + .containsExactly(serviceDef2, serviceDef1); + } + + @Test + void whenFilterAllowsAllThenAllServicesAdded() { + ServerServiceDefinition serviceDef1 = mock(); + ServerServiceDefinition serviceDef2 = mock(); + ServerServiceDefinitionFilter serviceFilter = (serviceDef, serviceFactory) -> true; + DefaultGrpcServerFactory serverFactory = new DefaultGrpcServerFactory("myhost:5150", List.of(), null, null, + null, serviceFilter); + serverFactory.addService(serviceDef2); + serverFactory.addService(serviceDef1); + assertThat(serverFactory) + .extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class)) + .containsExactly(serviceDef2, serviceDef1); + } + + @Test + void whenFilterAllowsOneThenOneServiceAdded() { + ServerServiceDefinition serviceDef1 = mock(); + ServerServiceDefinition serviceDef2 = mock(); + ServerServiceDefinitionFilter serviceFilter = (serviceDef, serviceFactory) -> serviceDef == serviceDef1; + DefaultGrpcServerFactory serverFactory = new DefaultGrpcServerFactory("myhost:5150", List.of(), null, null, + null, serviceFilter); + serverFactory.addService(serviceDef2); + serverFactory.addService(serviceDef1); + assertThat(serverFactory) + .extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class)) + .containsExactly(serviceDef1); + } + + } + +} diff --git a/spring-grpc-core/src/test/java/org/springframework/grpc/server/GrpcServerFactoryTests.java b/spring-grpc-core/src/test/java/org/springframework/grpc/server/GrpcServerFactoryTests.java deleted file mode 100644 index 76265feb..00000000 --- a/spring-grpc-core/src/test/java/org/springframework/grpc/server/GrpcServerFactoryTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.server; - -import org.junit.jupiter.api.Test; - -/** - * Tests for {@link GrpcServerFactory gRPC server factories}. - */ -class GrpcServerFactoryTests { - - @Test - void placeholderTest() { - } - -} 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 82b68bf7..6a980e46 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 @@ -17,6 +17,7 @@ package org.springframework.grpc.autoconfigure.server; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.inOrder; @@ -26,11 +27,16 @@ import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Stream; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InOrder; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -45,7 +51,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; -import org.springframework.grpc.server.DefaultGrpcServerFactory; import org.springframework.grpc.server.GrpcServerFactory; import org.springframework.grpc.server.InProcessGrpcServerFactory; import org.springframework.grpc.server.NettyGrpcServerFactory; @@ -83,13 +88,23 @@ void prepareForTest() { when(service.bindService()).thenReturn(serviceDefinition); } - private AbstractApplicationContextRunner contextRunner() { + private ApplicationContextRunner contextRunner() { // NOTE: we use noop server lifecycle to avoid startup ApplicationContextRunner runner = new ApplicationContextRunner(); return contextRunner(runner); } - private AbstractApplicationContextRunner contextRunner(AbstractApplicationContextRunner runner) { + private ApplicationContextRunner contextRunner(ApplicationContextRunner runner) { + return runner + .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, + GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class)) + .withBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock) + .withBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock) + .withBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock) + .withBean(BindableService.class, () -> service); + } + + private WebApplicationContextRunner webContextRunner(WebApplicationContextRunner runner) { return runner .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class)) @@ -294,7 +309,7 @@ void shadedNettyServerFactoryAutoConfiguredAsExpected() { @Test void serverFactoryAutoConfiguredInWebAppWhenServletDisabled() { serverFactoryAutoConfiguredAsExpected( - this.contextRunner(new WebApplicationContextRunner()) + this.webContextRunner(new WebApplicationContextRunner()) .withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160") .withPropertyValues("spring.grpc.server.servlet.enabled=false"), GrpcServerFactory.class, "myhost:6160", "shadedNettyGrpcServerLifecycle"); @@ -395,6 +410,74 @@ void nettyServerFactoryAutoConfiguredWithSsl() { NettyGrpcServerFactory.class, "myhost:6160", "nettyGrpcServerLifecycle"); } + @Nested + class WithAllFactoriesServiceFilterAutoConfig { + + static Stream serverFactoryProvider() { + return Stream.of(arguments( + (Function) (contextRunner) -> contextRunner, + ShadedNettyGrpcServerFactory.class), + arguments( + (Function) ( + contextRunner) -> contextRunner.withClassLoader(new FilteredClassLoader( + io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)), + NettyGrpcServerFactory.class), + arguments( + (Function) ( + contextRunner) -> contextRunner + .withPropertyValues("spring.grpc.server.inprocess.name=foo") + .withClassLoader(new FilteredClassLoader(NettyServerBuilder.class, + io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)), + InProcessGrpcServerFactory.class)); + } + + @ParameterizedTest(name = "whenNoServiceFilterThenFactoryUsesNoFilter w/ factory {1}") + @MethodSource("serverFactoryProvider") + void whenNoServiceFilterThenFactoryUsesNoFilter( + Function serverFactoryContextCustomizer, + Class expectedServerFactoryType) { + GrpcServerAutoConfigurationTests.this.contextRunner() + .withPropertyValues("spring.grpc.server.port=0") + .with(serverFactoryContextCustomizer) + .run((context) -> assertThat(context).getBean(GrpcServerFactory.class) + .isInstanceOf(expectedServerFactoryType) + .extracting("serviceFilter") + .isNull()); + } + + @ParameterizedTest(name = "whenUniqueServiceFilterThenFactoryUsesFilter w/ factory {1}") + @MethodSource("serverFactoryProvider") + void whenUniqueServiceFilterThenFactoryUsesFilter( + Function serverFactoryContextCustomizer, + Class expectedServerFactoryType) { + ServerServiceDefinitionFilter serviceFilter = mock(); + GrpcServerAutoConfigurationTests.this.contextRunner() + .withPropertyValues("spring.grpc.server.port=0") + .withBean(ServerServiceDefinitionFilter.class, () -> serviceFilter) + .with(serverFactoryContextCustomizer) + .run((context) -> assertThat(context).getBean(GrpcServerFactory.class) + .isInstanceOf(expectedServerFactoryType) + .extracting("serviceFilter") + .isSameAs(serviceFilter)); + } + + @ParameterizedTest(name = "whenMultipleServiceFiltersThenThrowsException w/ factory {1}") + @MethodSource("serverFactoryProvider") + void whenMultipleServiceFiltersThenThrowsException( + Function serverFactoryContextCustomizer, + Class ignored) { + GrpcServerAutoConfigurationTests.this.contextRunnerWithLifecyle() + .withPropertyValues("spring.grpc.server.port=0") + .withBean("filter1", ServerServiceDefinitionFilter.class, Mockito::mock) + .withBean("filter2", ServerServiceDefinitionFilter.class, Mockito::mock) + .with(serverFactoryContextCustomizer) + .run((context) -> assertThat(context).hasFailed() + .getFailure() + .hasMessageContaining("expected single matching bean but found 2: filter1,filter2")); + } + + } + @Nested class WithGrpcServiceConfigurerAutoConfig { diff --git a/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java b/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java index 5242087f..afc05c37 100644 --- a/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java +++ b/spring-grpc-test/src/main/java/org/springframework/grpc/test/InProcessTestAutoConfiguration.java @@ -57,8 +57,7 @@ public class InProcessTestAutoConfiguration { @ConditionalOnBean(BindableService.class) @Order(Ordered.HIGHEST_PRECEDENCE) TestInProcessGrpcServerFactory testInProcessGrpcServerFactory(GrpcServiceDiscoverer serviceDiscoverer, - GrpcServiceConfigurer serviceConfigurer, - List> customizers, + GrpcServiceConfigurer serviceConfigurer, List> customizers, @Nullable ServerServiceDefinitionFilter serviceFilter) { var factory = new TestInProcessGrpcServerFactory(address, customizers, serviceFilter); serviceDiscoverer.findServices() From a0f5c55496f4f5db7c9027018ac07567f8c3e100 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Wed, 2 Jul 2025 15:50:08 -0500 Subject: [PATCH 3/3] WIP: Exploring options for filtering only on InProcessGrpcServerFactory Signed-off-by: Chris Bono --- .../grpc/server/DefaultGrpcServerFactory.java | 11 ++------- .../grpc/server/GrpcServerFactory.java | 5 ++++ .../server/InProcessGrpcServerFactory.java | 23 ++++++++++++++++++- .../service/DefaultGrpcServiceConfigurer.java | 10 ++++---- .../server/service/GrpcServiceConfigurer.java | 4 +++- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java index a66e6340..60bab2a2 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java @@ -28,7 +28,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.grpc.internal.GrpcUtils; -import org.springframework.lang.Nullable; import com.google.common.collect.Lists; import io.grpc.Grpc; @@ -70,21 +69,17 @@ public class DefaultGrpcServerFactory> implements Grp private ClientAuth clientAuth; - private ServerServiceDefinitionFilter serviceFilter; - public DefaultGrpcServerFactory(String address, List> serverBuilderCustomizers) { this.address = address; this.serverBuilderCustomizers = Objects.requireNonNull(serverBuilderCustomizers, "serverBuilderCustomizers"); } public DefaultGrpcServerFactory(String address, List> serverBuilderCustomizers, - KeyManagerFactory keyManager, TrustManagerFactory trustManager, ClientAuth clientAuth, - @Nullable ServerServiceDefinitionFilter serviceFilter) { + KeyManagerFactory keyManager, TrustManagerFactory trustManager, ClientAuth clientAuth) { this(address, serverBuilderCustomizers); this.keyManager = keyManager; this.trustManager = trustManager; this.clientAuth = clientAuth; - this.serviceFilter = serviceFilter; } protected String address() { @@ -100,9 +95,7 @@ public Server createServer() { @Override public void addService(ServerServiceDefinition service) { - if (this.serviceFilter == null || this.serviceFilter.filter(service, this)) { - this.serviceList.add(service); - } + this.serviceList.add(service); } /** diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java index 24e971ad..0b603dde 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/GrpcServerFactory.java @@ -21,6 +21,7 @@ import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; import io.grpc.Server; +import io.grpc.ServerInterceptor; import io.grpc.ServerServiceDefinition; /** @@ -48,4 +49,8 @@ public interface GrpcServerFactory { */ void addService(ServerServiceDefinition service); + default boolean supports(ServerInterceptor interceptor, ServerServiceDefinition service) { + return true; + } + } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java index 259a2e5a..1cf109bc 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/InProcessGrpcServerFactory.java @@ -17,8 +17,11 @@ import java.util.List; +import org.springframework.grpc.server.service.ServerInterceptorFilter; import org.springframework.lang.Nullable; +import io.grpc.ServerInterceptor; +import io.grpc.ServerServiceDefinition; import io.grpc.inprocess.InProcessServerBuilder; /** @@ -29,10 +32,17 @@ */ public class InProcessGrpcServerFactory extends DefaultGrpcServerFactory { + private ServerServiceDefinitionFilter serviceFilter; + + private ServerInterceptorFilter interceptorFilter; + public InProcessGrpcServerFactory(String address, List> serverBuilderCustomizers, + @Nullable ServerInterceptorFilter interceptorFilter, @Nullable ServerServiceDefinitionFilter serviceFilter) { - super(address, serverBuilderCustomizers, null, null, null, serviceFilter); + super(address, serverBuilderCustomizers, null, null, null); + this.interceptorFilter = interceptorFilter; + this.serviceFilter = serviceFilter; } @Override @@ -40,4 +50,15 @@ protected InProcessServerBuilder newServerBuilder() { return InProcessServerBuilder.forName(address()); } + @Override + public void addService(ServerServiceDefinition service) { + if (this.serviceFilter == null || this.serviceFilter.filter(service, this)) { + super.addService(service); + } + } + + @Override + public boolean supports(ServerInterceptor interceptor, ServerServiceDefinition service) { + return (this.interceptorFilter == null || this.interceptorFilter.filter(interceptor, service)); + } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java index 881f75e0..63be6ce0 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java @@ -23,6 +23,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.grpc.internal.ApplicationContextBeanLookupUtils; import org.springframework.grpc.server.GlobalServerInterceptor; +import org.springframework.grpc.server.GrpcServerFactory; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -57,9 +58,9 @@ public void afterPropertiesSet() { } @Override - public ServerServiceDefinition configure(GrpcServiceSpec serviceSpec) { + public ServerServiceDefinition configure(GrpcServiceSpec serviceSpec, GrpcServerFactory serverFactory) { Assert.notNull(serviceSpec, () -> "serviceSpec must not be null"); - return bindInterceptors(serviceSpec.service(), serviceSpec.serviceInfo()); + return bindInterceptors(serviceSpec.service(), serviceSpec.serviceInfo(), serverFactory); } private List findGlobalInterceptors() { @@ -68,13 +69,14 @@ private List findGlobalInterceptors() { } private ServerServiceDefinition bindInterceptors(BindableService bindableService, - @Nullable GrpcServiceInfo serviceInfo) { + @Nullable GrpcServiceInfo serviceInfo, + GrpcServerFactory serverFactory) { var serviceDef = bindableService.bindService(); // Add and filter global interceptors first List allInterceptors = new ArrayList<>(this.globalInterceptors); if (this.interceptorFilter != null) { - allInterceptors.removeIf(interceptor -> !this.interceptorFilter.filter(interceptor, serviceDef)); + allInterceptors.removeIf(interceptor -> !serverFactory.supports(interceptor, serviceDef)); } if (serviceInfo == null) { return ServerInterceptors.interceptForward(serviceDef, allInterceptors); diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/GrpcServiceConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/GrpcServiceConfigurer.java index 4e1dae1f..9de07aaf 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/GrpcServiceConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/GrpcServiceConfigurer.java @@ -18,6 +18,8 @@ import io.grpc.ServerServiceDefinition; +import org.springframework.grpc.server.GrpcServerFactory; + /** * Configures and binds a {@link GrpcServiceSpec service spec} into a * {@link ServerServiceDefinition service definition} that can then be added to a gRPC @@ -35,6 +37,6 @@ public interface GrpcServiceConfigurer { * @return bound and configured service definition that is ready to be added to a * server */ - ServerServiceDefinition configure(GrpcServiceSpec serviceSpec); + ServerServiceDefinition configure(GrpcServiceSpec serviceSpec, GrpcServerFactory serverFactory); }