Validate 'server.http2.enabled' for gRPC Servlet applications

Throw an error is a Spring gRPC Servlet based server application
has forgotten to configure HTTP/2 support.

There is also the option of suppressing the error if HTTP/2 has
been configured directly and not using the configuration property.

Closes gh-49050
This commit is contained in:
Phillip Webb
2026-03-19 15:12:31 -07:00
parent 67ac57fb77
commit d6b28375c0
4 changed files with 84 additions and 9 deletions
@@ -61,6 +61,8 @@ public class GrpcServerProperties {
private final Netty netty = new Netty();
private final Servlet servlet = new Servlet();
public @Nullable Integer getPort() {
return this.port;
}
@@ -101,6 +103,10 @@ public class GrpcServerProperties {
return this.netty;
}
public Servlet getServlet() {
return this.servlet;
}
/**
* Server shutdown properties.
*/
@@ -449,4 +455,41 @@ public class GrpcServerProperties {
}
/**
* Servlet properties.
*/
public static class Servlet {
/**
* Whether to use a servlet server in a servlet-based web application. When the
* value is false, a native gRPC server will be created as long as one is
* available, and it will listen on its own port. Should only be needed if the
* GrpcServlet is on the classpath.
*/
private boolean enabled;
/**
* Whether to validate that HTTP/2 is enabled. Validation may need to be skipped
* if your servlet container is not configured using properties.
*/
private boolean validateHttp2 = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isValidateHttp2() {
return this.validateHttp2;
}
public void setValidateHttp2(boolean validateHttp2) {
this.validateHttp2 = validateHttp2;
}
}
}
@@ -21,9 +21,11 @@ import io.grpc.servlet.jakarta.GrpcServlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.diagnostics.FailureAnalyzedException;
import org.springframework.boot.grpc.server.GrpcServletRegistration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
@@ -43,8 +45,16 @@ import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
class ServletGrpcServerConfiguration {
@Bean
GrpcServletRegistration grpcServletRegistration(GrpcServiceDiscoverer serviceDiscoverer,
GrpcServiceConfigurer serviceConfigurer, GrpcServerBuilderCustomizers grpcServerBuilderCustomizers) {
GrpcServletRegistration grpcServletRegistration(Environment environment, GrpcServerProperties properties,
GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer,
GrpcServerBuilderCustomizers grpcServerBuilderCustomizers) {
if (properties.getServlet().isValidateHttp2()
&& !Boolean.TRUE.equals(environment.getProperty("server.http2.enabled", Boolean.class))) {
throw new FailureAnalyzedException(
"Configuration property 'server.http2.enabled' should be set to true for gRPC support",
"Update your application to correct the invalid configuration.\n"
+ "You can also set 'spring.grpc.server.servlet.validate-http2' to false to disable the validation.");
}
return new GrpcServletRegistration(serviceDiscoverer, serviceConfigurer, grpcServerBuilderCustomizers::apply);
}
@@ -18,12 +18,6 @@
"type": "java.lang.Boolean",
"description": "Whether to enable Reflection on the gRPC server.",
"defaultValue": true
},
{
"name": "spring.grpc.server.servlet.enabled",
"type": "java.lang.Boolean",
"description": "Whether to use a servlet server in a servlet-based web application. When the value is false, a native gRPC server will be created as long as one is available, and it will listen on its own port. Should only be needed if the GrpcServlet is on the classpath",
"defaultValue": true
}
]
}
@@ -374,7 +374,8 @@ class GrpcServerAutoConfigurationTests {
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(autoConfigurations)
.with(GrpcServerAutoConfigurationTests.this::serviceBean);
.with(GrpcServerAutoConfigurationTests.this::serviceBean)
.withPropertyValues("server.http2.enabled=true");
@Test
void whenGrpcNotOnClasspathAutoConfigurationIsSkipped() {
@@ -439,6 +440,33 @@ class GrpcServerAutoConfigurationTests {
.run((context) -> assertThat(applied.get()).isInstanceOf(ServletServerBuilder.class));
}
@Test
void whenHttp2EnabledPropertyMissing() {
new WebApplicationContextRunner().withConfiguration(autoConfigurations)
.with(GrpcServerAutoConfigurationTests.this::serviceBean)
.run((context) -> assertThat(context).getFailure()
.hasMessageContaining(
"Configuration property 'server.http2.enabled' should be set to true for gRPC support"));
}
@Test
void whenHttp2EnabledPropertyFalse() {
new WebApplicationContextRunner().withConfiguration(autoConfigurations)
.with(GrpcServerAutoConfigurationTests.this::serviceBean)
.withPropertyValues("server.http2.enabled=false")
.run((context) -> assertThat(context).getFailure()
.hasMessageContaining(
"Configuration property 'server.http2.enabled' should be set to true for gRPC support"));
}
@Test
void whenHttp2EnabledPropertyMissingAndValidationDisabled() {
new WebApplicationContextRunner().withConfiguration(autoConfigurations)
.with(GrpcServerAutoConfigurationTests.this::serviceBean)
.withPropertyValues("spring.grpc.server.servlet.validate-http2=false")
.run((context) -> assertThat(context).hasNotFailed());
}
}
@Configuration(proxyBeanMethods = false)