Skip to content

Commit

Permalink
Add ConditionalOnGrpcServerEnabled flag
Browse files Browse the repository at this point in the history
This adds a coarse-grained conditional guard that will disable the
server autoconfiguration if `spring.grpc.server.enabled` is set to
false or `BindableService` class is not available on the classpath.

Signed-off-by: Chris Bono <[email protected]>
  • Loading branch information
onobc committed Feb 19, 2025
1 parent 3006332 commit bf0b372
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
|spring.grpc.client.enabled | `+++true+++` | Whether to enable client autoconfiguration.
|spring.grpc.client.observations.enabled | `+++true+++` | Whether to enable Observations on the client.
|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.enabled | `+++true+++` | Whether to enable server autoconfiguration.
|spring.grpc.server.exception-handling.enabled | `+++true+++` | Whether to enable user-defined global exception handling on the gRPC server.
|spring.grpc.server.health.actuator.enabled | `+++true+++` | Whether to adapt Actuator health indicators into gRPC health checks.
|spring.grpc.server.health.actuator.health-indicator-paths | | List of Actuator health indicator paths to adapt into gRPC health checks.
Expand Down Expand Up @@ -49,4 +50,4 @@
|spring.grpc.server.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise.
|spring.grpc.server.ssl.secure | `+++true+++` | Flag to indicate that client authentication is secure (i.e. certificates are checked). Do not set this to false in production.

|===
|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Conditional;

import io.grpc.BindableService;

/**
* {@link Conditional @Conditional} that only matches when the
* {@code io.grpc.BindableService} class is on the classpath and the
* {@code spring.grpc.server.enabled} property is not explicitly set to {@code false}.
*
* @author Freeman
* @author Chris Bono
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnClass(BindableService.class)
@ConditionalOnProperty(prefix = "spring.grpc.server", name = "enabled", matchIfMissing = true)
public @interface ConditionalOnGrpcServerEnabled {

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
Expand Down Expand Up @@ -50,9 +48,8 @@
* @author David Syer
* @author Chris Bono
*/
@AutoConfiguration
@AutoConfigureAfter(GrpcServerFactoryAutoConfiguration.class)
@ConditionalOnClass(BindableService.class)
@AutoConfiguration(after = GrpcServerFactoryAutoConfiguration.class)
@ConditionalOnGrpcServerEnabled
@ConditionalOnBean(BindableService.class)
@EnableConfigurationProperties(GrpcServerProperties.class)
@Import({ GrpcCodecConfiguration.class })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
*/
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(BindableService.class)
@ConditionalOnGrpcServerEnabled
@ConditionalOnBean(BindableService.class)
public class GrpcServerFactoryAutoConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

@AutoConfiguration(
afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration")
@ConditionalOnGrpcServerEnabled
@ConditionalOnClass({ ObservationRegistry.class, ObservationGrpcServerInterceptor.class })
@ConditionalOnBean(ObservationRegistry.class)
@ConditionalOnProperty(name = "spring.grpc.server.observation.enabled", havingValue = "true", matchIfMissing = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* @author Haris Zujo
*/
@AutoConfiguration(before = GrpcServerFactoryAutoConfiguration.class)
@ConditionalOnGrpcServerEnabled
@ConditionalOnClass(ProtoReflectionService.class)
@ConditionalOnProperty(name = "spring.grpc.server.reflection.enabled", havingValue = "true", matchIfMissing = true)
public class GrpcServerReflectionAutoConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
*/
package org.springframework.grpc.autoconfigure.server.exception;

import java.util.stream.Collectors;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.grpc.autoconfigure.server.ConditionalOnGrpcServerEnabled;
import org.springframework.grpc.server.GlobalServerInterceptor;
import org.springframework.grpc.server.exception.CompositeGrpcExceptionHandler;
import org.springframework.grpc.server.exception.GrpcExceptionHandler;
Expand All @@ -32,6 +31,7 @@
import io.grpc.Grpc;

@AutoConfiguration
@ConditionalOnGrpcServerEnabled
@ConditionalOnClass(Grpc.class)
@ConditionalOnBean(GrpcExceptionHandler.class)
@ConditionalOnMissingBean(GrpcExceptionHandlerInterceptor.class)
Expand All @@ -44,7 +44,7 @@ public class GrpcExceptionHandlerAutoConfiguration {
public GrpcExceptionHandlerInterceptor globalExceptionHandlerInterceptor(
ObjectProvider<GrpcExceptionHandler> exceptionHandler) {
return new GrpcExceptionHandlerInterceptor(new CompositeGrpcExceptionHandler(
exceptionHandler.orderedStream().collect(Collectors.toList()).toArray(new GrpcExceptionHandler[0])));
exceptionHandler.orderedStream().toArray(GrpcExceptionHandler[]::new)));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.grpc.autoconfigure.server.ConditionalOnGrpcServerEnabled;
import org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration;
import org.springframework.grpc.autoconfigure.server.GrpcServerProperties;
import org.springframework.scheduling.annotation.EnableScheduling;
Expand All @@ -57,6 +58,7 @@
* @author Chris Bono
*/
@AutoConfiguration(before = GrpcServerFactoryAutoConfiguration.class)
@ConditionalOnGrpcServerEnabled
@ConditionalOnClass(HealthStatusManager.class)
@ConditionalOnProperty(name = "spring.grpc.server.health.enabled", havingValue = "true", matchIfMissing = true)
public class GrpcServerHealthAutoConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.grpc.autoconfigure.server.ConditionalOnGrpcServerEnabled;
import org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration;
import org.springframework.grpc.autoconfigure.server.exception.GrpcExceptionHandlerAutoConfiguration;
import org.springframework.grpc.server.GlobalServerInterceptor;
Expand All @@ -40,6 +41,7 @@
import io.grpc.internal.GrpcUtil;

@ConditionalOnClass(ObjectPostProcessor.class)
@ConditionalOnGrpcServerEnabled
@AutoConfiguration(before = GrpcExceptionHandlerAutoConfiguration.class, after = SecurityAutoConfiguration.class)
public class GrpcSecurityAutoConfiguration {

Expand Down Expand Up @@ -84,4 +86,4 @@ public <T extends ServerBuilder<T>> ServerBuilderCustomizer<T> securityContextEx

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
"name": "spring.grpc.server.port",
"defaultValue": "9090"
},
{
"name": "spring.grpc.server.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable server autoconfiguration.",
"defaultValue": true
},
{
"name": "spring.grpc.server.reflection.enabled",
"type": "java.lang.Boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ void whenNoBindableServicesRegisteredAutoConfigurationIsSkipped() {
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
}

@Test
void whenServerEnabledPropertySetFalseThenAutoConfigurationIsSkipped() {
this.contextRunner()
.withPropertyValues("spring.grpc.server.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
}

@Test
void whenServerEnabledPropertyNotSetThenAutoConfigurationIsNotSkipped() {
this.contextRunner().run((context) -> assertThat(context).hasSingleBean(GrpcServerAutoConfiguration.class));
}

@Test
void whenServerEnabledPropertySetTrueThenAutoConfigurationIsNotSkipped() {
this.contextRunner()
.withPropertyValues("spring.grpc.server.enabled=true")
.run((context) -> assertThat(context).hasSingleBean(GrpcServerAutoConfiguration.class));
}

@Test
void whenHasUserDefinedServerLifecycleDoesNotAutoConfigureBean() {
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,32 @@ void whenObservationPropertyDisabledThenAutoConfigIsSkipped() {
.run(context -> assertThat(context).doesNotHaveBean(GrpcServerObservationAutoConfiguration.class));
}

@Test
void whenServerEnabledPropertySetFalseThenAutoConfigurationIsSkipped() {
this.validContextRunner()
.withPropertyValues("spring.grpc.server.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerObservationAutoConfiguration.class));
}

@Test
void whenServerEnabledPropertyNotSetThenAutoConfigurationIsNotSkipped() {
this.validContextRunner()
.run((context) -> assertThat(context).hasSingleBean(GrpcServerObservationAutoConfiguration.class));
}

@Test
void whenServerEnabledPropertySetTrueThenAutoConfigurationIsNotSkipped() {
this.validContextRunner()
.withPropertyValues("spring.grpc.server.enabled=true")
.run((context) -> assertThat(context).hasSingleBean(GrpcServerObservationAutoConfiguration.class));
}

@Test
void whenAllConditionsAreMetThenInterceptorConfiguredAsExpected() {
this.validContextRunner().run((context) -> {
assertThat(context).hasSingleBean(ObservationGrpcServerInterceptor.class)
this.validContextRunner()
.run((context) -> assertThat(context).hasSingleBean(ObservationGrpcServerInterceptor.class)
.has(new Condition<>(beans -> beans.getBeansWithAnnotation(GlobalServerInterceptor.class).size() == 1,
"One global interceptor expected"));
});
"One global interceptor expected")));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,23 @@ void whenReflectionDisabledThenSkipBeanCreation() {
.run((context) -> assertThat(context).doesNotHaveBean(BindableService.class));
}

@Test
void whenServerEnabledPropertySetFalseThenAutoConfigurationIsSkipped() {
this.contextRunner()
.withPropertyValues("spring.grpc.server.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean(BindableService.class));
}

@Test
void whenServerEnabledPropertyNotSetThenAutoConfigurationIsNotSkipped() {
this.contextRunner().run((context) -> assertThat(context).hasSingleBean(BindableService.class));
}

@Test
void whenServerEnabledPropertySetTrueThenAutoConfigurationIsNotSkipped() {
this.contextRunner()
.withPropertyValues("spring.grpc.server.enabled=true")
.run((context) -> assertThat(context).hasSingleBean(BindableService.class));
}

}
Loading

0 comments on commit bf0b372

Please sign in to comment.