mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Add Spring gRPC server support
Add support for Spring gRPC server applications. Closes gh-49044 Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
committed by
Phillip Webb
co-authored by
Phillip Webb
parent
a052376237
commit
e61bb6df5b
@@ -110,6 +110,7 @@ dependencies {
|
||||
implementation(project(path: ":module:spring-boot-data-redis-test"))
|
||||
implementation(project(path: ":module:spring-boot-devtools"))
|
||||
implementation(project(path: ":module:spring-boot-graphql-test"))
|
||||
implementation(project(path: ":module:spring-boot-grpc-server"))
|
||||
implementation(project(path: ":module:spring-boot-health"))
|
||||
implementation(project(path: ":module:spring-boot-hibernate"))
|
||||
implementation(project(path: ":module:spring-boot-http-converter"))
|
||||
|
||||
@@ -97,6 +97,9 @@ dependencies {
|
||||
api(project(":module:spring-boot-graphql")) {
|
||||
transitive = false
|
||||
}
|
||||
api(project(":module:spring-boot-grpc-server")) {
|
||||
transitive = false
|
||||
}
|
||||
api(project(":module:spring-boot-groovy-templates")) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "java-library"
|
||||
id "org.springframework.boot.auto-configuration"
|
||||
id "org.springframework.boot.configuration-properties"
|
||||
id "org.springframework.boot.deployed"
|
||||
id "org.springframework.boot.optional-dependencies"
|
||||
}
|
||||
|
||||
description = "Spring Boot gRPC Server"
|
||||
|
||||
dependencies {
|
||||
api(project(":core:spring-boot"))
|
||||
api("org.springframework.grpc:spring-grpc-core")
|
||||
|
||||
optional(project(":core:spring-boot-autoconfigure"))
|
||||
optional("io.grpc:grpc-servlet-jakarta")
|
||||
optional("io.grpc:grpc-services")
|
||||
optional("io.grpc:grpc-netty")
|
||||
optional("io.grpc:grpc-netty-shaded")
|
||||
optional("io.grpc:grpc-inprocess")
|
||||
optional("io.grpc:grpc-kotlin-stub") {
|
||||
exclude group: "javax.annotation", module: "javax.annotation-api"
|
||||
}
|
||||
optional("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
testImplementation(project(":core:spring-boot-test"))
|
||||
testImplementation(project(":test-support:spring-boot-test-support"))
|
||||
testImplementation(testFixtures(project(":core:spring-boot-autoconfigure")))
|
||||
testImplementation("org.springframework:spring-web")
|
||||
|
||||
testRuntimeOnly("ch.qos.logback:logback-classic")
|
||||
}
|
||||
|
||||
tasks.named("compileTestJava") {
|
||||
options.nullability.checking = "tests"
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.grpc.ServerServiceDefinition;
|
||||
import io.grpc.ServiceDescriptor;
|
||||
import io.grpc.servlet.jakarta.GrpcServlet;
|
||||
import io.grpc.servlet.jakarta.ServletServerBuilder;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletRegistration.Dynamic;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.web.servlet.DynamicRegistrationBean;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceSpec;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link DynamicRegistrationBean} that can be used to register a {@link GrpcServlet}.
|
||||
*
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class GrpcServletRegistration extends DynamicRegistrationBean<Dynamic> {
|
||||
|
||||
private static Log logger = LogFactory.getLog(GrpcServletRegistration.class);
|
||||
|
||||
private final GrpcServlet servlet;
|
||||
|
||||
private final String[] urlMappings;
|
||||
|
||||
/**
|
||||
* Create a new {@link GrpcServletRegistration} instance.
|
||||
* @param serviceDiscoverer the gRPC service discoverer
|
||||
* @param serviceConfigurer the gRPC service configurer
|
||||
*/
|
||||
public GrpcServletRegistration(GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer) {
|
||||
this(serviceDiscoverer, serviceConfigurer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link GrpcServletRegistration} instance.
|
||||
* @param serviceDiscoverer the gRPC service discoverer
|
||||
* @param serviceConfigurer the gRPC service configurer
|
||||
* @param serverBuilderCustomizer an optional customizer to configure the
|
||||
* {@link ServletServerBuilder}
|
||||
*/
|
||||
public GrpcServletRegistration(GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer,
|
||||
@Nullable Consumer<ServletServerBuilder> serverBuilderCustomizer) {
|
||||
Assert.notNull(serviceDiscoverer, "'serviceDiscoverer' must not be null");
|
||||
Assert.notNull(serviceConfigurer, "'serviceConfigurer' must not be null");
|
||||
ServletServerBuilder builder = new ServletServerBuilder();
|
||||
List<String> urlMappings = new ArrayList<>();
|
||||
for (GrpcServiceSpec spec : serviceDiscoverer.findServices()) {
|
||||
ServiceDescriptor descriptor = spec.service().bindService().getServiceDescriptor();
|
||||
logger.info(LogMessage.format("Registering servlet gRPC service: %s", descriptor.getName()));
|
||||
urlMappings.add("/" + descriptor.getName() + "/*");
|
||||
ServerServiceDefinition definition = serviceConfigurer.configure(spec, null);
|
||||
builder.addService(definition);
|
||||
}
|
||||
if (serverBuilderCustomizer != null) {
|
||||
serverBuilderCustomizer.accept(builder);
|
||||
}
|
||||
this.servlet = builder.buildServlet();
|
||||
this.urlMappings = urlMappings.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dynamic addRegistration(String description, ServletContext servletContext) {
|
||||
return servletContext.addServlet(getName(), this.servlet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Dynamic registration) {
|
||||
super.configure(registration);
|
||||
if (!ObjectUtils.isEmpty(this.urlMappings)) {
|
||||
registration.addMapping(this.urlMappings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDescription() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
private String getName() {
|
||||
return getOrDeduceName(this.servlet);
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
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.ConditionalOnBooleanProperty;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* {@link Conditional @Conditional} that matches when
|
||||
* {@code spring.grpc.server.factory.enabled} is {@code true} or missing.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@ConditionalOnBooleanProperty(name = "spring.grpc.server.factory.enabled", matchIfMissing = true)
|
||||
@interface ConditionalOnGrpcServerFactoryEnabled {
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.grpc.server.GrpcServletRegistration;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
import org.springframework.grpc.server.InProcessGrpcServerFactory;
|
||||
|
||||
/**
|
||||
* {@link Conditional @Conditional} that matches when no network gRPC server is found.
|
||||
* Concretely:
|
||||
* <ul>
|
||||
* <li>There are no {@link GrpcServletRegistration} beans.</li>
|
||||
* <li>There are no {@link GrpcServerFactory} beans (ignoring
|
||||
* {@link InProcessGrpcServerFactory} beans)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@Conditional(MissingNetworkGrpcServerCondition.class)
|
||||
@interface ConditionalOnMissingNetworkGrpcServer {
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.Grpc;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
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;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.grpc.server.GlobalServerInterceptor;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.grpc.server.exception.CompositeGrpcExceptionHandler;
|
||||
import org.springframework.grpc.server.exception.GrpcExceptionHandler;
|
||||
import org.springframework.grpc.server.exception.GrpcExceptionHandlerInterceptor;
|
||||
import org.springframework.grpc.server.exception.ReactiveStubBeanDefinitionRegistrar;
|
||||
import org.springframework.grpc.server.service.DefaultGrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.DefaultGrpcServiceDiscoverer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring gRPC server-side
|
||||
* components.
|
||||
*
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ GrpcServerFactory.class, Grpc.class })
|
||||
@ConditionalOnBean(BindableService.class)
|
||||
@ConditionalOnBooleanProperty(name = "spring.grpc.server.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(GrpcServerProperties.class)
|
||||
@Import({ GrpcServerCodecConfiguration.class, ServletGrpcServerConfiguration.class,
|
||||
ShadedNettyGrpcServerConfiguration.class, NettyGrpcServerConfiguration.class,
|
||||
InProcessGrpcServerConfiguration.class })
|
||||
public final class GrpcServerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
GrpcServerBuilderCustomizers grpcServerBuilderCustomizers(GrpcServerProperties grpcServerProperties,
|
||||
ObjectProvider<CompressorRegistry> compressorRegistry,
|
||||
ObjectProvider<DecompressorRegistry> decompressorRegistry,
|
||||
ObjectProvider<GrpcServerExecutorProvider> executorProvider,
|
||||
ObjectProvider<ServerBuilderCustomizer<?>> customizers) {
|
||||
return new GrpcServerBuilderCustomizers(grpcServerProperties, compressorRegistry, decompressorRegistry,
|
||||
executorProvider, customizers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GrpcServiceConfigurer.class)
|
||||
DefaultGrpcServiceConfigurer grpcServiceConfigurer(ApplicationContext applicationContext) {
|
||||
return new DefaultGrpcServiceConfigurer(applicationContext);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GrpcServiceDiscoverer.class)
|
||||
DefaultGrpcServiceDiscoverer grpcServiceDiscoverer(ApplicationContext applicationContext) {
|
||||
return new DefaultGrpcServiceDiscoverer(applicationContext);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@GlobalServerInterceptor
|
||||
@ConditionalOnMissingBean
|
||||
GrpcExceptionHandlerInterceptor globalExceptionHandlerInterceptor(List<GrpcExceptionHandler> exceptionHandlers) {
|
||||
CompositeGrpcExceptionHandler compositeHandler = new CompositeGrpcExceptionHandler(
|
||||
exceptionHandlers.toArray(GrpcExceptionHandler[]::new));
|
||||
return new GrpcExceptionHandlerInterceptor(compositeHandler);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = "com.salesforce.reactivegrpc.common.Function")
|
||||
@Import(ReactiveStubBeanDefinitionRegistrar.class)
|
||||
static class ReactiveStubConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ServerBuilder;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.util.LambdaSafe;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
|
||||
/**
|
||||
* Invokes the customizations to a {@link ServerBuilder} based on the provided beans.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcServerBuilderCustomizers {
|
||||
|
||||
private final List<ServerBuilderCustomizer<?>> customizers;
|
||||
|
||||
GrpcServerBuilderCustomizers(GrpcServerProperties grpcServerProperties,
|
||||
ObjectProvider<CompressorRegistry> compressorRegistry,
|
||||
ObjectProvider<DecompressorRegistry> decompressorRegistry,
|
||||
ObjectProvider<GrpcServerExecutorProvider> executorProvider,
|
||||
ObjectProvider<ServerBuilderCustomizer<?>> customizers) {
|
||||
this(grpcServerProperties, compressorRegistry.getIfAvailable(), decompressorRegistry.getIfAvailable(),
|
||||
executorProvider.getIfAvailable(), customizers.orderedStream().toList());
|
||||
}
|
||||
|
||||
GrpcServerBuilderCustomizers(List<? extends ServerBuilderCustomizer<?>> customizers) {
|
||||
this(null, null, null, null, customizers);
|
||||
}
|
||||
|
||||
GrpcServerBuilderCustomizers(@Nullable GrpcServerProperties grpcServerProperties,
|
||||
@Nullable CompressorRegistry compressorRegistry, @Nullable DecompressorRegistry decompressorRegistry,
|
||||
@Nullable GrpcServerExecutorProvider executorProvider,
|
||||
List<? extends ServerBuilderCustomizer<?>> customizers) {
|
||||
List<ServerBuilderCustomizer<?>> all = new ArrayList<>();
|
||||
addCustomizer(all, compressorRegistry, ServerBuilder::compressorRegistry);
|
||||
addCustomizer(all, decompressorRegistry, ServerBuilder::decompressorRegistry);
|
||||
addCustomizer(all, executorProvider, (builder, bean) -> builder.executor(bean.getExecutor()));
|
||||
if (grpcServerProperties != null) {
|
||||
all.add(new PropertiesServerBuilderCustomizer<>(grpcServerProperties));
|
||||
}
|
||||
all.addAll(customizers);
|
||||
this.customizers = List.copyOf(all);
|
||||
}
|
||||
|
||||
private static <B extends ServerBuilder<B>, T> void addCustomizer(List<ServerBuilderCustomizer<?>> customizers,
|
||||
@Nullable T bean, BiConsumer<B, T> action) {
|
||||
if (bean != null) {
|
||||
ServerBuilderCustomizer<B> customizer = (builder) -> action.accept(builder, bean);
|
||||
customizers.add(customizer);
|
||||
}
|
||||
}
|
||||
|
||||
<T extends ServerBuilder<T>> List<ServerBuilderCustomizer<T>> forFactory() {
|
||||
return List.of(this::apply);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T extends ServerBuilder<?>> void apply(T builder) {
|
||||
LambdaSafe.callbacks(ServerBuilderCustomizer.class, this.customizers, builder)
|
||||
.withLogger(GrpcServerBuilderCustomizers.class)
|
||||
.invoke((customizer) -> customizer.customize(builder));
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.grpc.Compressor;
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.Decompressor;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ServerBuilder;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* The configuration that contains all gRPC codec related beans.
|
||||
*
|
||||
* @author Andrei Lisa
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class GrpcServerCodecConfiguration {
|
||||
|
||||
/**
|
||||
* The compressor registry that is set on the
|
||||
* {@link ServerBuilder#compressorRegistry(CompressorRegistry) server builder} .
|
||||
* @param compressors the compressors to use on the registry
|
||||
* @return a new {@link CompressorRegistry#newEmptyInstance() registry} with the
|
||||
* specified compressors or the {@link CompressorRegistry#getDefaultInstance() default
|
||||
* registry} if no custom compressors are available in the application context.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CompressorRegistry grpcCompressorRegistry(List<Compressor> compressors) {
|
||||
if (compressors.isEmpty()) {
|
||||
return CompressorRegistry.getDefaultInstance();
|
||||
}
|
||||
CompressorRegistry registry = CompressorRegistry.newEmptyInstance();
|
||||
compressors.forEach(registry::register);
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* The decompressor registry that is set on the
|
||||
* {@link ServerBuilder#decompressorRegistry(DecompressorRegistry) server builder}.
|
||||
* @param decompressors the decompressors to use on the registry
|
||||
* @return a new {@link DecompressorRegistry#emptyInstance() registry} with the
|
||||
* specified decompressors or the {@link DecompressorRegistry#getDefaultInstance()
|
||||
* default registry} if no custom decompressors are available in the application
|
||||
* context.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
DecompressorRegistry grpcDecompressorRegistry(List<Decompressor> decompressors) {
|
||||
if (decompressors.isEmpty()) {
|
||||
return DecompressorRegistry.getDefaultInstance();
|
||||
}
|
||||
DecompressorRegistry registry = DecompressorRegistry.emptyInstance();
|
||||
for (Decompressor decompressor : decompressors) {
|
||||
registry = registry.with(decompressor, false);
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Strategy interface to determine the {@link Executor} to use for the gRPC server.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GrpcServerExecutorProvider {
|
||||
|
||||
/**
|
||||
* Returns a {@link Executor} for the gRPC server, if it needs to be customized.
|
||||
* @return the executor to use for the gRPC server
|
||||
*/
|
||||
Executor getExecutor();
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize the
|
||||
* {@link GrpcServerFactory server factory} before it is fully initialized.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GrpcServerFactoryCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the given {@link GrpcServerFactory}.
|
||||
* @param serverFactory the server factory to customize
|
||||
*/
|
||||
void customize(GrpcServerFactory serverFactory);
|
||||
|
||||
}
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import io.grpc.TlsServerCredentials.ClientAuth;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.convert.DataSizeUnit;
|
||||
import org.springframework.boot.convert.DurationUnit;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
import org.springframework.util.unit.DataUnit;
|
||||
|
||||
/**
|
||||
* {@link ConfigurationProperties Properties} for Spring gRPC servers.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.grpc.server")
|
||||
public class GrpcServerProperties {
|
||||
|
||||
/**
|
||||
* Port on which the gRPC server should listen. Use '0' to bind to a dynamic port.
|
||||
*/
|
||||
private @Nullable Integer port;
|
||||
|
||||
/**
|
||||
* Network address to which the gRPC server should bind.
|
||||
*/
|
||||
private @Nullable InetAddress address;
|
||||
|
||||
private final Shutdown shutdown = new Shutdown();
|
||||
|
||||
private final Inbound inbound = new Inbound();
|
||||
|
||||
private final Inprocess inprocess = new Inprocess();
|
||||
|
||||
private final Keepalive keepalive = new Keepalive();
|
||||
|
||||
private final Ssl ssl = new Ssl();
|
||||
|
||||
private final Netty netty = new Netty();
|
||||
|
||||
public @Nullable Integer getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setPort(@Nullable Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public @Nullable InetAddress getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public void setAddress(@Nullable InetAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Shutdown getShutdown() {
|
||||
return this.shutdown;
|
||||
}
|
||||
|
||||
public Inbound getInbound() {
|
||||
return this.inbound;
|
||||
}
|
||||
|
||||
public Inprocess getInprocess() {
|
||||
return this.inprocess;
|
||||
}
|
||||
|
||||
public Keepalive getKeepalive() {
|
||||
return this.keepalive;
|
||||
}
|
||||
|
||||
public Ssl getSsl() {
|
||||
return this.ssl;
|
||||
}
|
||||
|
||||
public Netty getNetty() {
|
||||
return this.netty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server shutdown properties.
|
||||
*/
|
||||
public static class Shutdown {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private Duration gracePeriod = Duration.ofSeconds(30);
|
||||
|
||||
public Duration getGracePeriod() {
|
||||
return this.gracePeriod;
|
||||
}
|
||||
|
||||
public void setGracePeriod(Duration gracePeriod) {
|
||||
this.gracePeriod = gracePeriod;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In-bound properties.
|
||||
*/
|
||||
public static class Inbound {
|
||||
|
||||
private final Message message = new Message();
|
||||
|
||||
private final Metadata metadata = new Metadata();
|
||||
|
||||
public Message getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-bound message properties.
|
||||
*/
|
||||
public static class Message {
|
||||
|
||||
/**
|
||||
* Maximum message size allowed to be received by the server (default 4MiB).
|
||||
*/
|
||||
@DataSizeUnit(DataUnit.BYTES)
|
||||
private DataSize maxSize = DataSize.ofBytes(4194304);
|
||||
|
||||
public DataSize getMaxSize() {
|
||||
return this.maxSize;
|
||||
}
|
||||
|
||||
public void setMaxSize(DataSize maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In-bound metadata properties.
|
||||
*/
|
||||
public static class Metadata {
|
||||
|
||||
/**
|
||||
* Maximum metadata size allowed to be received by the server (default 8KiB).
|
||||
*/
|
||||
@DataSizeUnit(DataUnit.BYTES)
|
||||
private DataSize maxSize = DataSize.ofBytes(8192);
|
||||
|
||||
public DataSize getMaxSize() {
|
||||
return this.maxSize;
|
||||
}
|
||||
|
||||
public void setMaxSize(DataSize maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In-process gRPC properties.
|
||||
*/
|
||||
public static class Inprocess {
|
||||
|
||||
/**
|
||||
* The name of the in-process server or null to not start the in-process server.
|
||||
*/
|
||||
private @Nullable String name;
|
||||
|
||||
public @Nullable String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(@Nullable String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep-alive properties.
|
||||
*/
|
||||
public static class Keepalive {
|
||||
|
||||
/**
|
||||
* Duration without read activity before sending a keep alive ping (default 2h).
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private @Nullable Duration time = Duration.ofHours(2);
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private @Nullable Duration timeout = Duration.ofSeconds(20);
|
||||
|
||||
private final Permit permit = new Permit();
|
||||
|
||||
private final Connection connection = new Connection();
|
||||
|
||||
public @Nullable Duration getTime() {
|
||||
return this.time;
|
||||
}
|
||||
|
||||
public void setTime(@Nullable Duration time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public @Nullable Duration getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(@Nullable Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public Permit getPermit() {
|
||||
return this.permit;
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
return this.connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep-alive permit properties.
|
||||
*/
|
||||
public static class Permit {
|
||||
|
||||
/**
|
||||
* Maximum keep-alive time clients are permitted to configure (default 5m).
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private @Nullable Duration time = Duration.ofMinutes(5);
|
||||
|
||||
/**
|
||||
* Whether clients are permitted to send keep alive pings when there are no
|
||||
* outstanding RPCs on the connection (default false).
|
||||
*/
|
||||
private boolean withoutCalls;
|
||||
|
||||
public @Nullable Duration getTime() {
|
||||
return this.time;
|
||||
}
|
||||
|
||||
public void setTime(@Nullable Duration time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public boolean isWithoutCalls() {
|
||||
return this.withoutCalls;
|
||||
}
|
||||
|
||||
public void setWithoutCalls(boolean withoutCalls) {
|
||||
this.withoutCalls = withoutCalls;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep-alive connection properties.
|
||||
*/
|
||||
public static class Connection {
|
||||
|
||||
/**
|
||||
* Maximum time a connection can remain idle before being gracefully
|
||||
* terminated (default infinite).
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private @Nullable Duration maxIdleTime;
|
||||
|
||||
/**
|
||||
* Maximum time a connection may exist before being gracefully terminated
|
||||
* (default infinite).
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private @Nullable Duration maxAge;
|
||||
|
||||
/**
|
||||
* Maximum time for graceful connection termination (default infinite).
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private @Nullable Duration gracePeriod = Duration.ofSeconds(30);
|
||||
|
||||
public @Nullable Duration getMaxIdleTime() {
|
||||
return this.maxIdleTime;
|
||||
}
|
||||
|
||||
public void setMaxIdleTime(@Nullable Duration maxIdleTime) {
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
}
|
||||
|
||||
public @Nullable Duration getMaxAge() {
|
||||
return this.maxAge;
|
||||
}
|
||||
|
||||
public void setMaxAge(@Nullable Duration maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
}
|
||||
|
||||
public @Nullable Duration getGracePeriod() {
|
||||
return this.gracePeriod;
|
||||
}
|
||||
|
||||
public void setGracePeriod(@Nullable Duration gracePeriod) {
|
||||
this.gracePeriod = gracePeriod;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL properties.
|
||||
*/
|
||||
public static class Ssl {
|
||||
|
||||
/**
|
||||
* Whether to enable SSL support.
|
||||
*/
|
||||
private @Nullable Boolean enabled;
|
||||
|
||||
/**
|
||||
* Client authentication mode.
|
||||
*/
|
||||
private ClientAuth clientAuth = ClientAuth.NONE;
|
||||
|
||||
/**
|
||||
* SSL bundle name. Should match a bundle configured in spring.ssl.bundle.
|
||||
*/
|
||||
private @Nullable String bundle;
|
||||
|
||||
/**
|
||||
* Flag to indicate that client authentication is secure (i.e. certificates are
|
||||
* checked). Do not set this to false in production.
|
||||
*/
|
||||
private boolean secure = true;
|
||||
|
||||
public @Nullable Boolean getEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(@Nullable Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public @Nullable String getBundle() {
|
||||
return this.bundle;
|
||||
}
|
||||
|
||||
public void setBundle(@Nullable String bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
public void setClientAuth(ClientAuth clientAuth) {
|
||||
this.clientAuth = clientAuth;
|
||||
}
|
||||
|
||||
public ClientAuth getClientAuth() {
|
||||
return this.clientAuth;
|
||||
}
|
||||
|
||||
public void setSecure(boolean secure) {
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Netty server properties.
|
||||
*/
|
||||
public static class Netty {
|
||||
|
||||
/**
|
||||
* Transport mechanism used for Netty and Netty Shaded servers. If not specified
|
||||
* will the appropriate transport will be picked based on the
|
||||
* 'deomain-socket-path' or 'address/port'.
|
||||
*/
|
||||
private @Nullable Transport transport;
|
||||
|
||||
/**
|
||||
* Path of the domain socket that should be used.
|
||||
*/
|
||||
private @Nullable String domainSocketPath;
|
||||
|
||||
public @Nullable Transport getTransport() {
|
||||
return this.transport;
|
||||
}
|
||||
|
||||
public void setTransport(@Nullable Transport transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
public @Nullable String getDomainSocketPath() {
|
||||
return this.domainSocketPath;
|
||||
}
|
||||
|
||||
public void setDomainSocketPath(@Nullable String domainSocketPath) {
|
||||
this.domainSocketPath = domainSocketPath;
|
||||
}
|
||||
|
||||
public enum Transport {
|
||||
|
||||
/**
|
||||
* TCP transport.
|
||||
*/
|
||||
TCP,
|
||||
|
||||
/**
|
||||
* Domain socket transport.
|
||||
*/
|
||||
DOMAIN_SOCKET
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.protobuf.services.ProtoReflectionServiceV1;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for gRPC server services.
|
||||
*
|
||||
* @author Haris Zujo
|
||||
* @author Dave Syer
|
||||
* @author Chris Bono
|
||||
* @author Andrey Litvitski
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@AutoConfiguration(before = GrpcServerAutoConfiguration.class)
|
||||
@ConditionalOnClass({ GrpcServerFactory.class, io.grpc.Grpc.class })
|
||||
@ConditionalOnBooleanProperty(name = "spring.grpc.server.enabled", matchIfMissing = true)
|
||||
public final class GrpcServerServicesAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ProtoReflectionServiceV1.class)
|
||||
@ConditionalOnBean(BindableService.class)
|
||||
@ConditionalOnBooleanProperty(name = "spring.grpc.server.reflection.enabled", matchIfMissing = true)
|
||||
static class GrpcServerReflectionServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
BindableService grpcServerReflectionService() {
|
||||
return ProtoReflectionServiceV1.newInstance();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.server.InProcessGrpcServerFactory;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for an in-process gRPC server.
|
||||
*
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnProperty("spring.grpc.server.inprocess.name")
|
||||
@ConditionalOnGrpcServerFactoryEnabled
|
||||
class InProcessGrpcServerConfiguration {
|
||||
|
||||
@Bean
|
||||
InProcessGrpcServerFactory inProcessGrpcServerFactory(GrpcServerProperties properties,
|
||||
GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer,
|
||||
GrpcServerBuilderCustomizers grpcServerBuilderCustomizers,
|
||||
ObjectProvider<GrpcServerFactoryCustomizer> customizers) {
|
||||
String inProcessName = properties.getInprocess().getName();
|
||||
Assert.state(inProcessName != null, "No inprocess name provided");
|
||||
InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(inProcessName,
|
||||
grpcServerBuilderCustomizers.forFactory());
|
||||
customizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
|
||||
serviceDiscoverer.findServices()
|
||||
.stream()
|
||||
.map((spec) -> serviceConfigurer.configure(spec, factory))
|
||||
.forEach(factory::addService);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(InProcessGrpcServerFactory.class)
|
||||
@ConditionalOnMissingBean(name = "inProcessGrpcServerLifecycle")
|
||||
GrpcServerLifecycle inProcessGrpcServerLifecycle(InProcessGrpcServerFactory factory,
|
||||
GrpcServerProperties properties, ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, properties.getShutdown().getGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.grpc.server.GrpcServletRegistration;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
import org.springframework.grpc.server.InProcessGrpcServerFactory;
|
||||
|
||||
/**
|
||||
* {@link Condition} that matches when no network gRPC server is found.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MissingNetworkGrpcServerCondition extends AllNestedConditions {
|
||||
|
||||
MissingNetworkGrpcServerCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(GrpcServletRegistration.class)
|
||||
static class MissingGrpcServletRegistrationBean {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(value = GrpcServerFactory.class, ignored = InProcessGrpcServerFactory.class)
|
||||
static class MissingGrpcFactoryBean {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.grpc.server.autoconfigure.GrpcServerProperties.Netty.Transport;
|
||||
import org.springframework.grpc.internal.GrpcUtils;
|
||||
import org.springframework.grpc.server.GrpcServerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Address {@link GrpcServerFactory} address.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @param transport the transport to use
|
||||
* @param address the bind address
|
||||
* @param port the listen port
|
||||
* @param domainSocketPath the domain socket path
|
||||
*/
|
||||
record NettyAddress(@Nullable Transport transport, @Nullable InetAddress address, @Nullable Integer port,
|
||||
@Nullable String domainSocketPath) {
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
Transport transport = (this.transport != null) ? this.transport : deduceTransport();
|
||||
return switch (transport) {
|
||||
case TCP -> tcpAddress();
|
||||
case DOMAIN_SOCKET -> domainSocketAddress();
|
||||
};
|
||||
}
|
||||
|
||||
private Transport deduceTransport() {
|
||||
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
|
||||
entries.put("spring.grpc.server.address", this.address);
|
||||
entries.put("spring.grpc.server.netty.domain-socket-path", this.domainSocketPath);
|
||||
});
|
||||
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
|
||||
entries.put("spring.grpc.server.port", this.port);
|
||||
entries.put("spring.grpc.server.netty.domain-socket-path", this.domainSocketPath);
|
||||
});
|
||||
if (this.address != null || this.port != null) {
|
||||
return Transport.TCP;
|
||||
}
|
||||
if (this.domainSocketPath != null) {
|
||||
return Transport.DOMAIN_SOCKET;
|
||||
}
|
||||
return Transport.TCP;
|
||||
}
|
||||
|
||||
private String tcpAddress() {
|
||||
String address = (this.address != null) ? toString(this.address) : GrpcUtils.ANY_IP_ADDRESS;
|
||||
int port = (this.port != null) ? this.port : GrpcUtils.DEFAULT_PORT;
|
||||
return address + ":" + port;
|
||||
}
|
||||
|
||||
private String domainSocketAddress() {
|
||||
if (!StringUtils.hasText(this.domainSocketPath)) {
|
||||
throw new InvalidConfigurationPropertyValueException("spring.grpc.server.netty.domain-socket-path",
|
||||
this.domainSocketPath,
|
||||
"A path is required when spring.grpc.server.netty.transport is set to 'domain-socket'");
|
||||
}
|
||||
return "unix:" + this.domainSocketPath;
|
||||
}
|
||||
|
||||
private static String toString(InetAddress address) {
|
||||
String hostName = address.getHostName();
|
||||
return (hostName != null) ? hostName : address.getHostAddress();
|
||||
}
|
||||
|
||||
static NettyAddress fromProperties(GrpcServerProperties properties) {
|
||||
return new NettyAddress(properties.getNetty().getTransport(), properties.getAddress(), properties.getPort(),
|
||||
properties.getNetty().getDomainSocketPath());
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import io.grpc.netty.NettyServerBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.server.NettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for a Netty gRPC server.
|
||||
*
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(NettyServerBuilder.class)
|
||||
@ConditionalOnMissingNetworkGrpcServer
|
||||
@ConditionalOnGrpcServerFactoryEnabled
|
||||
class NettyGrpcServerConfiguration {
|
||||
|
||||
@Bean
|
||||
NettyGrpcServerFactory nettyGrpcServerFactory(GrpcServerProperties properties,
|
||||
GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer,
|
||||
GrpcServerBuilderCustomizers grpcServerBuilderCustomizers, SslBundles bundles,
|
||||
ObjectProvider<GrpcServerFactoryCustomizer> customizers) {
|
||||
NettyAddress address = NettyAddress.fromProperties(properties);
|
||||
ServerCredentials credentials = ServerCredentials.get(properties.getSsl(), bundles,
|
||||
InsecureTrustManagerFactory.INSTANCE);
|
||||
NettyGrpcServerFactory factory = new NettyGrpcServerFactory(address.toString(),
|
||||
grpcServerBuilderCustomizers.forFactory(), credentials.keyManagerFactory(),
|
||||
credentials.trustManagerFactory(), credentials.clientAuth());
|
||||
customizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
|
||||
serviceDiscoverer.findServices()
|
||||
.stream()
|
||||
.map((spec) -> serviceConfigurer.configure(spec, factory))
|
||||
.forEach(factory::addService);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "nettyGrpcServerLifecycle")
|
||||
GrpcServerLifecycle nettyGrpcServerLifecycle(NettyGrpcServerFactory factory, GrpcServerProperties properties,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, properties.getShutdown().getGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.ServerBuilder;
|
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.grpc.server.autoconfigure.GrpcServerProperties.Inbound;
|
||||
import org.springframework.boot.grpc.server.autoconfigure.GrpcServerProperties.Keepalive;
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
/**
|
||||
* {@link ServerBuilderCustomizer} that maps {@link GrpcServerProperties} to a
|
||||
* {@link ManagedChannelBuilder}.
|
||||
*
|
||||
* @param <T> the type of server builder
|
||||
* @param properties the properties to map
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
record PropertiesServerBuilderCustomizer<T extends ServerBuilder<T>>(
|
||||
GrpcServerProperties properties) implements ServerBuilderCustomizer<T> {
|
||||
|
||||
@Override
|
||||
public void customize(T builder) {
|
||||
mapInboundProperties(this.properties.getInbound(), builder);
|
||||
if (supportsKeepAliveProperties(builder)) {
|
||||
mapKeepaliveProperties(this.properties.getKeepalive(), builder);
|
||||
}
|
||||
}
|
||||
|
||||
private void mapInboundProperties(Inbound properties, T builder) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(properties.getMessage()::getMaxSize).asInt(DataSize::toBytes).to(builder::maxInboundMessageSize);
|
||||
map.from(properties.getMetadata()::getMaxSize).asInt(DataSize::toBytes).to(builder::maxInboundMetadataSize);
|
||||
}
|
||||
|
||||
private void mapKeepaliveProperties(Keepalive properties, T builder) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(properties::getTime).to(durationProperty(builder::keepAliveTime));
|
||||
map.from(properties::getTimeout).to(durationProperty(builder::keepAliveTimeout));
|
||||
map.from(properties.getConnection()::getMaxIdleTime).to(durationProperty(builder::maxConnectionIdle));
|
||||
map.from(properties.getConnection()::getMaxAge).to(durationProperty(builder::maxConnectionAge));
|
||||
map.from(properties.getConnection()::getGracePeriod).to(durationProperty(builder::maxConnectionAgeGrace));
|
||||
map.from(properties.getPermit()::getTime).to(durationProperty(builder::permitKeepAliveTime));
|
||||
map.from(properties.getPermit()::isWithoutCalls).to(builder::permitKeepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
private Consumer<Duration> durationProperty(BiConsumer<Long, TimeUnit> setter) {
|
||||
return (duration) -> setter.accept(duration.toNanos(), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
private boolean supportsKeepAliveProperties(T builder) {
|
||||
return !isInstance("io.grpc.inprocess.InProcessServerBuilder", builder)
|
||||
&& !isInstance("io.grpc.servlet.jakarta.ServletServerBuilder", builder);
|
||||
}
|
||||
|
||||
private boolean isInstance(String className, T builder) {
|
||||
try {
|
||||
return ClassUtils.forName(className, builder.getClass().getClassLoader()).isInstance(builder);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import io.grpc.TlsServerCredentials.ClientAuth;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.ssl.SslManagerBundle;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Server credential details to use with gRPC servers.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
* @param keyManagerFactory the key manager factory to use or {@code null}
|
||||
* @param trustManagerFactory the trust manager factory to use or {@code null}
|
||||
* @param clientAuth the client auth to use
|
||||
*/
|
||||
record ServerCredentials(@Nullable KeyManagerFactory keyManagerFactory,
|
||||
@Nullable TrustManagerFactory trustManagerFactory, ClientAuth clientAuth) {
|
||||
|
||||
/**
|
||||
* Return the credentials to use based on the given properties.
|
||||
* @param properties the SSL properties
|
||||
* @param bundles the SSL bundles
|
||||
* @param insecureTrustManagerFactory the trust manager factory to use for insecure
|
||||
* connections
|
||||
* @return the server credentials to use
|
||||
*/
|
||||
static ServerCredentials get(GrpcServerProperties.Ssl properties, SslBundles bundles,
|
||||
TrustManagerFactory insecureTrustManagerFactory) {
|
||||
Boolean enabled = properties.getEnabled();
|
||||
String bundle = properties.getBundle();
|
||||
ClientAuth clientAuth = properties.getClientAuth();
|
||||
if (Boolean.FALSE.equals(enabled) || (enabled == null && bundle == null)) {
|
||||
return new ServerCredentials(null, null, clientAuth);
|
||||
}
|
||||
Assert.state(bundle != null,
|
||||
() -> "SSL bundle-name is requested when 'spring.grpc.server.ssl.enabled' is true");
|
||||
SslManagerBundle managers = bundles.getBundle(bundle).getManagers();
|
||||
KeyManagerFactory keyManagerFactory = managers.getKeyManagerFactory();
|
||||
TrustManagerFactory trustManagerFactory = (!properties.isSecure()) ? insecureTrustManagerFactory
|
||||
: managers.getTrustManagerFactory();
|
||||
return new ServerCredentials(keyManagerFactory, trustManagerFactory, clientAuth);
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
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.grpc.server.GrpcServletRegistration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for a Servlet gRPC server.
|
||||
*
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@ConditionalOnClass(GrpcServlet.class)
|
||||
@ConditionalOnMissingNetworkGrpcServer
|
||||
@ConditionalOnBooleanProperty(name = "spring.grpc.server.servlet.enabled", matchIfMissing = true)
|
||||
class ServletGrpcServerConfiguration {
|
||||
|
||||
@Bean
|
||||
GrpcServletRegistration grpcServletRegistration(GrpcServiceDiscoverer serviceDiscoverer,
|
||||
GrpcServiceConfigurer serviceConfigurer, GrpcServerBuilderCustomizers grpcServerBuilderCustomizers) {
|
||||
return new GrpcServletRegistration(serviceDiscoverer, serviceConfigurer, grpcServerBuilderCustomizers::apply);
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
|
||||
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.server.ShadedNettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for a Shaded Netty gRPC server.
|
||||
*
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(NettyServerBuilder.class)
|
||||
@ConditionalOnMissingNetworkGrpcServer
|
||||
@ConditionalOnGrpcServerFactoryEnabled
|
||||
class ShadedNettyGrpcServerConfiguration {
|
||||
|
||||
@Bean
|
||||
ShadedNettyGrpcServerFactory shadedNettyGrpcServerFactory(GrpcServerProperties properties,
|
||||
GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer,
|
||||
GrpcServerBuilderCustomizers grpcServerBuilderCustomizers, SslBundles bundles,
|
||||
ObjectProvider<GrpcServerFactoryCustomizer> customizers) {
|
||||
NettyAddress address = NettyAddress.fromProperties(properties);
|
||||
ServerCredentials serverCredentials = ServerCredentials.get(properties.getSsl(), bundles,
|
||||
InsecureTrustManagerFactory.INSTANCE);
|
||||
ShadedNettyGrpcServerFactory factory = new ShadedNettyGrpcServerFactory(address.toString(),
|
||||
grpcServerBuilderCustomizers.forFactory(), serverCredentials.keyManagerFactory(),
|
||||
serverCredentials.trustManagerFactory(), serverCredentials.clientAuth());
|
||||
customizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
|
||||
serviceDiscoverer.findServices()
|
||||
.stream()
|
||||
.map((spec) -> serviceConfigurer.configure(spec, factory))
|
||||
.forEach(factory::addService);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "shadedNettyGrpcServerLifecycle")
|
||||
GrpcServerLifecycle shadedNettyGrpcServerLifecycle(ShadedNettyGrpcServerFactory factory,
|
||||
GrpcServerProperties properties, ApplicationEventPublisher eventPublisher) {
|
||||
return new GrpcServerLifecycle(factory, properties.getShutdown().getGracePeriod(), eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for gRPC server.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.grpc.server.autoconfigure;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring gRPC server support classes.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.grpc.server;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.grpc.server.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable gRPC server auto-configuration.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.server.factory.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable gRPC server factory bean auto-configuration.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.server.reflection.enabled",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer=\
|
||||
org.springframework.boot.grpc.server.autoconfigure.security.GrpcDisableCsrfHttpConfigurer
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration
|
||||
org.springframework.boot.grpc.server.autoconfigure.GrpcServerServicesAutoConfiguration
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.ServerServiceDefinition;
|
||||
import io.grpc.servlet.jakarta.GrpcServlet;
|
||||
import io.grpc.servlet.jakarta.ServletServerBuilder;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRegistration.Dynamic;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.grpc.server.service.GrpcServiceConfigurer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceInfo;
|
||||
import org.springframework.grpc.server.service.GrpcServiceSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcServletRegistration}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcServletRegistrationTests {
|
||||
|
||||
private final GrpcServiceConfigurer serviceConfigurer = mock();
|
||||
|
||||
private final GrpcServiceDiscoverer serviceDiscoverer = mock();
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("NullAway") // Test null check
|
||||
void createWhenServiceDiscovererIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new GrpcServletRegistration(null, this.serviceConfigurer))
|
||||
.withMessage("'serviceDiscoverer' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("NullAway") // Test null check
|
||||
void createWhenServiceConfigurerIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new GrpcServletRegistration(this.serviceDiscoverer, null))
|
||||
.withMessage("'serviceConfigurer' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenServerBuilderCustomizerIsNullDoesNotApplyCustomization() {
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> new GrpcServletRegistration(this.serviceDiscoverer, this.serviceConfigurer, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenServerBuilderCustomizerIsNotNullAppliesCustomization() {
|
||||
Consumer<ServletServerBuilder> serverBuilderCustomizer = mock();
|
||||
new GrpcServletRegistration(this.serviceDiscoverer, this.serviceConfigurer, serverBuilderCustomizer);
|
||||
then(serverBuilderCustomizer).should().accept(any(ServletServerBuilder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRegistrationAddsBuiltServlet() {
|
||||
GrpcServletRegistration registration = new GrpcServletRegistration(this.serviceDiscoverer,
|
||||
this.serviceConfigurer);
|
||||
ServletContext servletContext = mock();
|
||||
Dynamic result = mock();
|
||||
given(servletContext.addServlet(eq("grpcServlet"), any(GrpcServlet.class))).willReturn(result);
|
||||
assertThat(registration.addRegistration("test", servletContext)).isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onStartupWhenHasServicesRegistersAndAddsUrlMappingsBasedOnDescriptorName() throws ServletException {
|
||||
BindableService service1 = mock(BindableService.class);
|
||||
ServerServiceDefinition serviceDefinition1 = ServerServiceDefinition.builder("s1").build();
|
||||
given(service1.bindService()).willReturn(serviceDefinition1);
|
||||
GrpcServiceInfo info1 = new GrpcServiceInfo(emptyServiceInterceptors(), new String[0], false);
|
||||
BindableService service2 = mock(BindableService.class);
|
||||
ServerServiceDefinition serviceDefinition2 = ServerServiceDefinition.builder("s2").build();
|
||||
given(service2.bindService()).willReturn(serviceDefinition2);
|
||||
GrpcServiceInfo info2 = new GrpcServiceInfo(emptyServiceInterceptors(), new String[0], false);
|
||||
List<GrpcServiceSpec> specs = new ArrayList<>();
|
||||
specs.add(new GrpcServiceSpec(service1, info1));
|
||||
specs.add(new GrpcServiceSpec(service2, info2));
|
||||
given(this.serviceDiscoverer.findServices()).willReturn(specs);
|
||||
given(this.serviceConfigurer.configure(any(GrpcServiceSpec.class), eq(null))).willAnswer((invocation) -> {
|
||||
GrpcServiceSpec spec = invocation.getArgument(0, GrpcServiceSpec.class);
|
||||
return spec.service().bindService();
|
||||
});
|
||||
GrpcServletRegistration registration = new GrpcServletRegistration(this.serviceDiscoverer,
|
||||
this.serviceConfigurer);
|
||||
ServletContext servletContext = mock(ServletContext.class);
|
||||
Dynamic result = mock(Dynamic.class);
|
||||
given(servletContext.addServlet(eq("grpcServlet"), any(GrpcServlet.class))).willReturn(result);
|
||||
registration.onStartup(servletContext);
|
||||
then(result).should().addMapping("/s1/*", "/s2/*");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onStartupWhenHasNoServicesDoesNotAddUrlMappings() throws ServletException {
|
||||
given(this.serviceDiscoverer.findServices()).willReturn(Collections.emptyList());
|
||||
given(this.serviceConfigurer.configure(any(GrpcServiceSpec.class), eq(null))).willAnswer((invocation) -> {
|
||||
GrpcServiceSpec spec = invocation.getArgument(0, GrpcServiceSpec.class);
|
||||
return spec.service().bindService();
|
||||
});
|
||||
GrpcServletRegistration registration = new GrpcServletRegistration(this.serviceDiscoverer,
|
||||
this.serviceConfigurer);
|
||||
ServletContext servletContext = mock(ServletContext.class);
|
||||
Dynamic result = mock(Dynamic.class);
|
||||
given(servletContext.addServlet(eq("grpcServlet"), any(GrpcServlet.class))).willReturn(result);
|
||||
registration.onStartup(servletContext);
|
||||
then(result).should(never()).addMapping();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDescriptionReturnsDeducedServletName() {
|
||||
GrpcServletRegistration registration = new GrpcServletRegistration(this.serviceDiscoverer,
|
||||
this.serviceConfigurer);
|
||||
assertThat(registration.getDescription()).isEqualTo("grpcServlet");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<? extends ServerInterceptor>[] emptyServiceInterceptors() {
|
||||
return (Class<? extends ServerInterceptor>[]) new Class<?>[] {};
|
||||
}
|
||||
|
||||
}
|
||||
+465
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.ServerServiceDefinition;
|
||||
import io.grpc.ServiceDescriptor;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.netty.NettyServerBuilder;
|
||||
import io.grpc.servlet.jakarta.GrpcServlet;
|
||||
import io.grpc.servlet.jakarta.ServletServerBuilder;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.grpc.server.GrpcServletRegistration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
|
||||
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.ShadedNettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.grpc.server.service.DefaultGrpcServiceDiscoverer;
|
||||
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests fir {@link GrpcServerAutoConfiguration}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Andrey Litvitski
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcServerAutoConfigurationTests {
|
||||
|
||||
private static final AutoConfigurations autoConfigurations = AutoConfigurations
|
||||
.of(GrpcServerAutoConfiguration.class, SslAutoConfiguration.class);
|
||||
|
||||
private final BindableService service = mock();
|
||||
|
||||
private final ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder("my-service").build();
|
||||
|
||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(autoConfigurations)
|
||||
.with(this::noOpLifecycleBeans)
|
||||
.with(this::serviceBean);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
given(this.service.bindService()).willReturn(this.serviceDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGrpcNotOnClasspathAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(BindableService.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSpringGrpcNotOnClasspathAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(GrpcServerFactory.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoBindableServicesRegisteredAutoConfigurationIsSkipped() {
|
||||
new ApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.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 whenHasUserDefinedGrpcServiceDiscovererDoesNotAutoConfigureBean() {
|
||||
GrpcServiceDiscoverer customGrpcServiceDiscoverer = mock(GrpcServiceDiscoverer.class);
|
||||
new ApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.with(this::noOpLifecycleBeans)
|
||||
.withBean("customGrpcServiceDiscoverer", GrpcServiceDiscoverer.class, () -> customGrpcServiceDiscoverer)
|
||||
.withPropertyValues("spring.grpc.server.port=0")
|
||||
.run((context) -> assertThat(context).getBean(GrpcServiceDiscoverer.class)
|
||||
.isSameAs(customGrpcServiceDiscoverer));
|
||||
}
|
||||
|
||||
@Test
|
||||
void grpcServiceDiscovererAutoConfiguredAsExpected() {
|
||||
new ApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.with(this::serviceBean)
|
||||
.run((context) -> assertThat(context).getBean(GrpcServiceDiscoverer.class)
|
||||
.isInstanceOf(DefaultGrpcServiceDiscoverer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serverBuilderCustomizersAutoConfiguredAsExpected() {
|
||||
this.contextRunner.withUserConfiguration(ServerBuilderCustomizersConfig.class)
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerBuilderCustomizers.class)
|
||||
.extracting("customizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class))
|
||||
.contains(ServerBuilderCustomizersConfig.bar, ServerBuilderCustomizersConfig.foo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersAreAppliedToNettyServer() {
|
||||
AtomicReference<NettyServerBuilder> applied = new AtomicReference<>();
|
||||
ServerBuilderCustomizer<NettyServerBuilder> customizer = applied::set;
|
||||
this.contextRunner.withBean(ServerBuilderCustomizer.class, () -> customizer)
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run((context) -> {
|
||||
context.getBean(GrpcServerFactory.class).createServer();
|
||||
assertThat(applied.get()).isInstanceOf(NettyServerBuilder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersAreAppliedToShadedNettyServer() {
|
||||
AtomicReference<io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder> applied = new AtomicReference<>();
|
||||
ServerBuilderCustomizer<io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder> customizer = applied::set;
|
||||
this.contextRunner.withBean(ServerBuilderCustomizer.class, () -> customizer)
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class))
|
||||
.run((context) -> {
|
||||
context.getBean(GrpcServerFactory.class).createServer();
|
||||
assertThat(applied.get()).isInstanceOf(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersAreAppliedToInProcessServer() {
|
||||
AtomicReference<InProcessServerBuilder> applied = new AtomicReference<>();
|
||||
ServerBuilderCustomizer<InProcessServerBuilder> customizer = applied::set;
|
||||
this.contextRunner.withBean(ServerBuilderCustomizer.class, () -> customizer)
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=test")
|
||||
.run((context) -> {
|
||||
context.getBean(GrpcServerFactory.class).createServer();
|
||||
assertThat(applied.get()).isInstanceOf(InProcessServerBuilder.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedServerFactoryDoesNotAutoConfigureBean() {
|
||||
GrpcServerFactory customServerFactory = mock(GrpcServerFactory.class);
|
||||
this.contextRunner.withBean("customServerFactory", GrpcServerFactory.class, () -> customServerFactory)
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class).isSameAs(customServerFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDefinedServerFactoryWithInProcessServerFactory() {
|
||||
GrpcServerFactory customServerFactory = mock(GrpcServerFactory.class);
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.withBean("customServerFactory", GrpcServerFactory.class, () -> customServerFactory)
|
||||
.run((context) -> assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("customServerFactory", "inProcessGrpcServerFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedAndNonShadedNettyOnClasspathShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
|
||||
.isInstanceOf(ShadedNettyGrpcServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyFactoryWithInProcessServerFactory() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("shadedNettyGrpcServerFactory", "inProcessGrpcServerFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenOnlyNonShadedNettyOnClasspathNonShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
|
||||
.isInstanceOf(NettyGrpcServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonShadedNettyFactoryWithInProcessServerFactory() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcServerFactory.class)
|
||||
.containsOnlyKeys("nettyGrpcServerFactory", "inProcessGrpcServerFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedNettyAndNettyNotOnClasspathNoServerFactoryIsAutoConfigured() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noServerFactoryWithInProcessServerFactory() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
|
||||
.isInstanceOf(InProcessGrpcServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyServerFactoryAutoConfiguredWithCustomLifecycle() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
new ApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.with(this::serviceBean)
|
||||
.withBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class).isInstanceOf(ShadedNettyGrpcServerFactory.class);
|
||||
assertThat(context).getBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class)
|
||||
.isSameAs(customServerLifecycle);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyServerFactoryAutoConfiguredWithCustomLifecycle() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
new ApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.with(this::serviceBean)
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class).isInstanceOf(NettyGrpcServerFactory.class);
|
||||
assertThat(context).getBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class)
|
||||
.isSameAs(customServerLifecycle);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessServerFactoryAutoConfiguredWithCustomLifecycle() {
|
||||
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
|
||||
new ApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.with(this::serviceBean)
|
||||
.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.withBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class).isInstanceOf(InProcessGrpcServerFactory.class);
|
||||
assertThat(context).getBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class)
|
||||
.isSameAs(customServerLifecycle);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyServerFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.address=192.168.0.1", "spring.grpc.server.port=6160")
|
||||
.run(assertThatServerIsConfigured(ShadedNettyGrpcServerFactory.class, "192.168.0.1:6160",
|
||||
"shadedNettyGrpcServerLifecycle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyServerFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.address=192.168.0.1", "spring.grpc.server.port=6160")
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run(assertThatServerIsConfigured(NettyGrpcServerFactory.class, "192.168.0.1:6160",
|
||||
"nettyGrpcServerLifecycle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serverFactoryAutoConfiguredInWebAppWhenServletDisabled() {
|
||||
new WebApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.with(this::noOpLifecycleBeans)
|
||||
.with(this::serviceBean)
|
||||
.withPropertyValues("spring.grpc.server.address=192.168.0.1")
|
||||
.withPropertyValues("spring.grpc.server.port=6160")
|
||||
.withPropertyValues("spring.grpc.server.servlet.enabled=false")
|
||||
.run(assertThatServerIsConfigured(ShadedNettyGrpcServerFactory.class, "192.168.0.1:6160",
|
||||
"shadedNettyGrpcServerLifecycle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessServerFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.inprocess.name=foo")
|
||||
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run(assertThatServerIsConfigured(InProcessGrpcServerFactory.class, "foo", "inProcessGrpcServerLifecycle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyServerFactoryAutoConfiguredWithSsl() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.address=192.168.0.1", "spring.grpc.server.port=6160",
|
||||
"spring.grpc.server.ssl.bundle=ssltest",
|
||||
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:org/springframework/boot/grpc/server/autoconfigure/test.jks",
|
||||
"spring.ssl.bundle.jks.ssltest.keystore.password=secret",
|
||||
"spring.ssl.bundle.jks.ssltest.key.password=password")
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
|
||||
.run(assertThatServerIsConfigured(NettyGrpcServerFactory.class, "192.168.0.1:6160",
|
||||
"nettyGrpcServerLifecycle"));
|
||||
}
|
||||
|
||||
private ContextConsumer<? super ApplicationContextAssertProvider<?>> assertThatServerIsConfigured(
|
||||
Class<?> expectedServerFactoryType, String expectedAddress, String expectedLifecycleBeanName) {
|
||||
return (context) -> {
|
||||
assertThat(context).getBean(GrpcServerFactory.class)
|
||||
.isInstanceOf(expectedServerFactoryType)
|
||||
.hasFieldOrPropertyWithValue("address", expectedAddress)
|
||||
.extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class))
|
||||
.singleElement()
|
||||
.extracting(ServerServiceDefinition::getServiceDescriptor)
|
||||
.extracting(ServiceDescriptor::getName)
|
||||
.isEqualTo("my-service");
|
||||
assertThat(context).getBean(expectedLifecycleBeanName, GrpcServerLifecycle.class).isNotNull();
|
||||
};
|
||||
}
|
||||
|
||||
private <R extends AbstractApplicationContextRunner<R, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> R serviceBean(
|
||||
R contextRunner) {
|
||||
return contextRunner.withBean(BindableService.class, () -> this.service);
|
||||
}
|
||||
|
||||
private <R extends AbstractApplicationContextRunner<R, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> R noOpLifecycleBeans(
|
||||
R contextRunner) {
|
||||
return contextRunner.withBean("shadedNettyGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean("nettyGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean("inProcessGrpcServerLifecycle", GrpcServerLifecycle.class, Mockito::mock);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ServletServerAutoConfigurationTests {
|
||||
|
||||
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withConfiguration(autoConfigurations)
|
||||
.with(GrpcServerAutoConfigurationTests.this::serviceBean);
|
||||
|
||||
@Test
|
||||
void whenGrpcNotOnClasspathAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(BindableService.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ServletGrpcServerConfiguration.class)
|
||||
.doesNotHaveBean(ServletRegistrationBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSpringGrpcNotOnClasspathAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(GrpcServerFactory.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ServletGrpcServerConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoBindableServicesRegisteredAutoConfigurationIsSkipped() {
|
||||
new WebApplicationContextRunner().withConfiguration(autoConfigurations)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ServletGrpcServerConfiguration.class)
|
||||
.doesNotHaveBean(ServletRegistrationBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGrpcServletNotOnClasspathAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(GrpcServlet.class))
|
||||
.withPropertyValues("spring.grpc.server.port=0")
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ServletGrpcServerConfiguration.class)
|
||||
.doesNotHaveBean(ServletRegistrationBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenWebApplicationServletIsAutoConfigured() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(GrpcServletRegistration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServerBuilderCustomizerIsRegistered() {
|
||||
ServerBuilderCustomizer<ServletServerBuilder> customizer = mock();
|
||||
this.contextRunner.withBean(ServerBuilderCustomizer.class, () -> customizer)
|
||||
.run((context) -> then(customizer).should().customize(any(ServletServerBuilder.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMaxInboundMessageSizeIsSetThenItIsUsed() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.inbound.message.max-size=10KB")
|
||||
.run((context) -> assertThat(context).getBean(GrpcServletRegistration.class)
|
||||
.hasFieldOrPropertyWithValue("servlet.servletAdapter.maxInboundMessageSize",
|
||||
Math.toIntExact(DataSize.ofKilobytes(10).toBytes())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMaxInboundMessageSizeIsNotSetThenDefaultIsUsed() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(GrpcServletRegistration.class)
|
||||
.hasFieldOrPropertyWithValue("servlet.servletAdapter.maxInboundMessageSize",
|
||||
GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizersAreAppliedToServletServer() {
|
||||
AtomicReference<ServletServerBuilder> applied = new AtomicReference<>();
|
||||
ServerBuilderCustomizer<ServletServerBuilder> customizer = applied::set;
|
||||
this.contextRunner.withBean(ServerBuilderCustomizer.class, () -> customizer)
|
||||
.run((context) -> assertThat(applied.get()).isInstanceOf(ServletServerBuilder.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ServerBuilderCustomizersConfig {
|
||||
|
||||
static ServerBuilderCustomizer<?> foo = mock();
|
||||
|
||||
static ServerBuilderCustomizer<?> bar = mock();
|
||||
|
||||
@Bean
|
||||
@Order(200)
|
||||
ServerBuilderCustomizer<?> customizerFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(100)
|
||||
ServerBuilderCustomizer<?> customizerBar() {
|
||||
return bar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ServerBuilder;
|
||||
import io.grpc.netty.NettyServerBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.grpc.server.ServerBuilderCustomizer;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcServerBuilderCustomizers}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcServerBuilderCustomizersTests {
|
||||
|
||||
@Test
|
||||
void applyWhenEmptyCustomizersDoesNothing() {
|
||||
ServerBuilder<?> serverBuilder = mock(ServerBuilder.class);
|
||||
new GrpcServerBuilderCustomizers(Collections.emptyList()).apply(serverBuilder);
|
||||
then(serverBuilder).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenSimpleCustomizer() {
|
||||
GrpcServerBuilderCustomizers customizers = new GrpcServerBuilderCustomizers(
|
||||
List.of(new SimpleServerBuilderCustomizer()));
|
||||
NettyServerBuilder serverBuilder = mock(NettyServerBuilder.class);
|
||||
customizers.apply(serverBuilder);
|
||||
then(serverBuilder).should().maxConnectionAge(100L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenGenericCustomizersRespectsGeneric() {
|
||||
List<TestCustomizer<?>> list = new ArrayList<>();
|
||||
list.add(new TestCustomizer<>());
|
||||
list.add(new TestNettyServerBuilderCustomizer());
|
||||
list.add(new TestShadedNettyServerBuilderCustomizer());
|
||||
GrpcServerBuilderCustomizers customizers = new GrpcServerBuilderCustomizers(list);
|
||||
customizers.apply(mock(ServerBuilder.class));
|
||||
assertThat(list.get(0).getCount()).isOne();
|
||||
assertThat(list.get(1).getCount()).isZero();
|
||||
assertThat(list.get(2).getCount()).isZero();
|
||||
customizers.apply(mock(NettyServerBuilder.class));
|
||||
assertThat(list.get(0).getCount()).isEqualTo(2);
|
||||
assertThat(list.get(1).getCount()).isOne();
|
||||
assertThat(list.get(2).getCount()).isZero();
|
||||
customizers.apply(mock(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class));
|
||||
assertThat(list.get(0).getCount()).isEqualTo(3);
|
||||
assertThat(list.get(1).getCount()).isOne();
|
||||
assertThat(list.get(2).getCount()).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHasInjectedBeans() {
|
||||
CompressorRegistry compressorRegistry = mock();
|
||||
DecompressorRegistry decompressorRegistry = mock();
|
||||
GrpcServerExecutorProvider executorProvider = mock();
|
||||
Executor executor = mock();
|
||||
given(executorProvider.getExecutor()).willReturn(executor);
|
||||
GrpcServerProperties properties = new GrpcServerProperties();
|
||||
properties.getInbound().getMessage().setMaxSize(DataSize.ofMegabytes(10));
|
||||
GrpcServerBuilderCustomizers customizers = new GrpcServerBuilderCustomizers(properties, compressorRegistry,
|
||||
decompressorRegistry, executorProvider, List.of(new SimpleServerBuilderCustomizer()));
|
||||
NettyServerBuilder serverBuilder = mock(NettyServerBuilder.class);
|
||||
customizers.apply(serverBuilder);
|
||||
InOrder ordered = Mockito.inOrder(serverBuilder);
|
||||
then(serverBuilder).should(ordered).compressorRegistry(compressorRegistry);
|
||||
then(serverBuilder).should(ordered).decompressorRegistry(decompressorRegistry);
|
||||
then(serverBuilder).should().executor(executor);
|
||||
then(serverBuilder).should(ordered).maxConnectionAge(100L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match {@link NettyServerBuilder} and apply a simple
|
||||
* customization.
|
||||
*/
|
||||
static class SimpleServerBuilderCustomizer implements ServerBuilderCustomizer<NettyServerBuilder> {
|
||||
|
||||
@Override
|
||||
public void customize(NettyServerBuilder serverBuilder) {
|
||||
serverBuilder.maxConnectionAge(100, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match all {@link ServerBuilderCustomizer}.
|
||||
*
|
||||
* @param <T> the builder type
|
||||
*/
|
||||
static class TestCustomizer<T extends ServerBuilder<T>> implements ServerBuilderCustomizer<T> {
|
||||
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public void customize(T serverBuilder) {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match only {@link NettyServerBuilder}.
|
||||
*/
|
||||
static class TestNettyServerBuilderCustomizer extends TestCustomizer<NettyServerBuilder> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match only
|
||||
* {@link io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder}.
|
||||
*/
|
||||
static class TestShadedNettyServerBuilderCustomizer
|
||||
extends TestCustomizer<io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import io.grpc.Compressor;
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.Decompressor;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcServerCodecConfiguration}.
|
||||
*
|
||||
* @author Andrei Lisa
|
||||
*/
|
||||
class GrpcServerCodecConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcServerCodecConfiguration.class));
|
||||
|
||||
@Test
|
||||
void compressorRegistryWhenHasUserDefinedRegistryDoesNotAutoConfigureBean() {
|
||||
CompressorRegistry customRegistry = mock();
|
||||
this.contextRunner.withBean("customCompressorRegistry", CompressorRegistry.class, () -> customRegistry)
|
||||
.run((context) -> assertThat(context).getBean(CompressorRegistry.class).isSameAs(customRegistry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compressorRegistryWhenNoCompressorsAutoConfiguresDefaultInstance() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(CompressorRegistry.class)
|
||||
.isSameAs(CompressorRegistry.getDefaultInstance()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compressorRegistryWhenHasCompressorsAutoConfiguresNewInstance() {
|
||||
Compressor compressor = mock();
|
||||
given(compressor.getMessageEncoding()).willReturn("foo");
|
||||
this.contextRunner.withBean(Compressor.class, () -> compressor).run((context) -> {
|
||||
assertThat(context).hasSingleBean(CompressorRegistry.class);
|
||||
CompressorRegistry registry = context.getBean(CompressorRegistry.class);
|
||||
assertThat(registry).isNotSameAs(CompressorRegistry.getDefaultInstance());
|
||||
assertThat(registry.lookupCompressor("foo")).isSameAs(compressor);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void decompressorRegistryWhenHasUserDefinedRegistryDoesNotAutoConfigureBean() {
|
||||
DecompressorRegistry customRegistry = mock();
|
||||
this.contextRunner.withBean("customDecompressorRegistry", DecompressorRegistry.class, () -> customRegistry)
|
||||
.run((context) -> assertThat(context).getBean(DecompressorRegistry.class).isSameAs(customRegistry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decompressorRegistryWhenNoDecompressorsAutoConfiguresDefaultInstance() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(DecompressorRegistry.class)
|
||||
.isSameAs(DecompressorRegistry.getDefaultInstance()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decompressorRegistryWhenHasDecompressorsAutoConfiguresNewInstance() {
|
||||
Decompressor decompressor = mock();
|
||||
given(decompressor.getMessageEncoding()).willReturn("foo");
|
||||
this.contextRunner.withBean(Decompressor.class, () -> decompressor).run((context) -> {
|
||||
assertThat(context).hasSingleBean(DecompressorRegistry.class);
|
||||
DecompressorRegistry registry = context.getBean(DecompressorRegistry.class);
|
||||
assertThat(registry).isNotSameAs(DecompressorRegistry.getDefaultInstance());
|
||||
assertThat(registry.lookupDecompressor("foo")).isSameAs(decompressor);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.grpc.TlsServerCredentials.ClientAuth;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcServerProperties}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcServerPropertiesTests {
|
||||
|
||||
private GrpcServerProperties bindProperties(Map<String, String> map) {
|
||||
return new Binder(new MapConfigurationPropertySource(map))
|
||||
.bind("spring.grpc.server", GrpcServerProperties.class)
|
||||
.get();
|
||||
}
|
||||
|
||||
@Test
|
||||
void bind() throws Exception {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.address", "192.168.0.1");
|
||||
GrpcServerProperties properties = bindProperties(map);
|
||||
assertThat(properties.getAddress()).isEqualTo(InetAddress.getByName("192.168.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultAddressIsNull() {
|
||||
assertThat(new GrpcServerProperties().getAddress()).isNull();
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ShutdownProperties {
|
||||
|
||||
@Test
|
||||
void bind() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.shutdown.grace-period", "10m");
|
||||
GrpcServerProperties properties = bindProperties(map);
|
||||
assertThat(properties.getShutdown().getGracePeriod()).isEqualTo(Duration.ofMinutes(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindWithoutUnits() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.shutdown.grace-period", "10");
|
||||
GrpcServerProperties properties = bindProperties(map);
|
||||
assertThat(properties.getShutdown().getGracePeriod()).isEqualTo(Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InboundProperties {
|
||||
|
||||
@Test
|
||||
void bind() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.inbound.message.max-size", "20MB");
|
||||
map.put("spring.grpc.server.inbound.metadata.max-size", "1MB");
|
||||
GrpcServerProperties properties = bindProperties(map);
|
||||
assertThat(properties.getInbound().getMessage().getMaxSize()).isEqualTo(DataSize.ofMegabytes(20));
|
||||
assertThat(properties.getInbound().getMetadata().getMaxSize()).isEqualTo(DataSize.ofMegabytes(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindWithoutUnits() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.inbound.message.max-size", "1048576");
|
||||
map.put("spring.grpc.server.inbound.metadata.max-size", "1024");
|
||||
GrpcServerProperties properties = bindProperties(map);
|
||||
assertThat(properties.getInbound().getMessage().getMaxSize()).isEqualTo(DataSize.ofMegabytes(1));
|
||||
assertThat(properties.getInbound().getMetadata().getMaxSize()).isEqualTo(DataSize.ofKilobytes(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class KeepAliveProperties {
|
||||
|
||||
@Test
|
||||
void bind() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.keepalive.time", "45m");
|
||||
map.put("spring.grpc.server.keepalive.timeout", "40s");
|
||||
map.put("spring.grpc.server.keepalive.permit.time", "33s");
|
||||
map.put("spring.grpc.server.keepalive.permit.without-calls", "true");
|
||||
map.put("spring.grpc.server.keepalive.connection.max-idle-time", "1h");
|
||||
map.put("spring.grpc.server.keepalive.connection.max-age", "3h");
|
||||
map.put("spring.grpc.server.keepalive.connection.grace-period", "21s");
|
||||
GrpcServerProperties.Keepalive properties = bindProperties(map).getKeepalive();
|
||||
assertThatPropertiesSetAsExpected(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindWithoutUnits() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.keepalive.time", "2700");
|
||||
map.put("spring.grpc.server.keepalive.timeout", "40");
|
||||
map.put("spring.grpc.server.keepalive.permit.time", "33");
|
||||
map.put("spring.grpc.server.keepalive.permit.without-calls", "true");
|
||||
map.put("spring.grpc.server.keepalive.connection.max-idle-time", "3600");
|
||||
map.put("spring.grpc.server.keepalive.connection.max-age", "10800");
|
||||
map.put("spring.grpc.server.keepalive.connection.grace-period", "21");
|
||||
GrpcServerProperties.Keepalive properties = bindProperties(map).getKeepalive();
|
||||
assertThatPropertiesSetAsExpected(properties);
|
||||
}
|
||||
|
||||
private void assertThatPropertiesSetAsExpected(GrpcServerProperties.Keepalive properties) {
|
||||
assertThat(properties.getTime()).isEqualTo(Duration.ofMinutes(45));
|
||||
assertThat(properties.getTimeout()).isEqualTo(Duration.ofSeconds(40));
|
||||
assertThat(properties.getPermit().getTime()).isEqualTo(Duration.ofSeconds(33));
|
||||
assertThat(properties.getPermit().isWithoutCalls()).isTrue();
|
||||
assertThat(properties.getConnection().getMaxIdleTime()).isEqualTo(Duration.ofHours(1));
|
||||
assertThat(properties.getConnection().getMaxAge()).isEqualTo(Duration.ofHours(3));
|
||||
assertThat(properties.getConnection().getGracePeriod()).isEqualTo(Duration.ofSeconds(21));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SslProperties {
|
||||
|
||||
@Test
|
||||
void bind() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("spring.grpc.server.ssl.enabled", "true");
|
||||
map.put("spring.grpc.server.ssl.client-auth", "require");
|
||||
map.put("spring.grpc.server.ssl.bundle", "test");
|
||||
map.put("spring.grpc.server.ssl.secure", "false");
|
||||
GrpcServerProperties.Ssl properties = bindProperties(map).getSsl();
|
||||
assertThat(properties.getEnabled()).isTrue();
|
||||
assertThat(properties.getClientAuth()).isEqualTo(ClientAuth.REQUIRE);
|
||||
assertThat(properties.getBundle()).isEqualTo("test");
|
||||
assertThat(properties.isSecure()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.protobuf.services.ProtoReflectionServiceV1;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.grpc.server.autoconfigure.GrpcServerServicesAutoConfiguration.GrpcServerReflectionServiceConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcServerServicesAutoConfiguration}.
|
||||
*
|
||||
* @author Haris Zujo
|
||||
* @author Chris Bono
|
||||
* @author Andrey Litvitski
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcServerServicesAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcServerServicesAutoConfiguration.class))
|
||||
.withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
|
||||
.withBean(BindableService.class, Mockito::mock);
|
||||
|
||||
@Nested
|
||||
class GrpcServerReflectionServiceConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = GrpcServerServicesAutoConfigurationTests.this.contextRunner;
|
||||
|
||||
@Test
|
||||
void whenAutoConfigurationIsNotSkippedCreatesReflectionServiceBean() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(GrpcServerReflectionServiceConfiguration.class);
|
||||
assertThat(context).hasBean("grpcServerReflectionService");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGrpcServicesNotOnClasspathAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(ProtoReflectionServiceV1.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerReflectionServiceConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoBindableServiceDefinedAutoConfigurationIsSkipped() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcServerServicesAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerReflectionServiceConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenReflectionEnabledPropertyIsTrueAutoConfigurationIsNotSkipped() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.reflection.enabled=true")
|
||||
.run((context) -> assertThat(context).hasBean("grpcServerReflectionService"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenReflectionEnabledPropertyIsFalseAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.reflection.enabled=false").run((context) -> {
|
||||
assertThat(context).doesNotHaveBean("grpcServerReflectionService");
|
||||
assertThat(context).doesNotHaveBean(GrpcServerReflectionServiceConfiguration.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServerEnabledPropertyIsTrueAutoConfigurationIsNotSkipped() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.enabled=true")
|
||||
.run((context) -> assertThat(context).hasBean("grpcServerReflectionService"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServerEnabledPropertyIsFalseAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.server.enabled=false").run((context) -> {
|
||||
assertThat(context).doesNotHaveBean("grpcServerReflectionService");
|
||||
assertThat(context).doesNotHaveBean(GrpcServerReflectionServiceConfiguration.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.grpc.server.autoconfigure.GrpcServerProperties.Netty.Transport;
|
||||
import org.springframework.grpc.internal.GrpcUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link NettyAddress}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class NettyAddressTests {
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndNoAddressOrPortOrDomainSocketPathBindsToDefault() {
|
||||
NettyAddress address = new NettyAddress(null, null, null, null);
|
||||
assertThat(address).hasToString(GrpcUtils.ANY_IP_ADDRESS + ":" + GrpcUtils.DEFAULT_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndOnlyPortBindsToAllAddressesUsingPort() {
|
||||
NettyAddress address = new NettyAddress(null, null, 1234, null);
|
||||
assertThat(address).hasToString(GrpcUtils.ANY_IP_ADDRESS + ":1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndOnlyAddressBindsToAddressUsingPort9090() throws Exception {
|
||||
InetAddress inetAddress = InetAddress.getByName("localhost");
|
||||
NettyAddress address = new NettyAddress(null, inetAddress, null, null);
|
||||
assertThat(address).hasToString("localhost:" + GrpcUtils.DEFAULT_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndOnlyAddressWithoutNameBindsToAddressUsingPort9090() throws Exception {
|
||||
InetAddress inetAddress = InetAddress.getByName("192.168.1.0");
|
||||
NettyAddress address = new NettyAddress(null, inetAddress, null, null);
|
||||
assertThat(address).hasToString("192.168.1.0:" + GrpcUtils.DEFAULT_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndOnlyDomainSocketPathBindsToDomainSocket() {
|
||||
NettyAddress address = new NettyAddress(null, null, null, "/ds");
|
||||
assertThat(address).hasToString("unix:/ds");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndPortAndDomainSocketPathThrowsException() {
|
||||
NettyAddress address = new NettyAddress(null, null, 1234, "/ds");
|
||||
assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class)
|
||||
.isThrownBy(() -> address.toString())
|
||||
.withMessage(
|
||||
"The configuration properties 'spring.grpc.server.port, spring.grpc.server.netty.domain-socket-path' "
|
||||
+ "are mutually exclusive and 'spring.grpc.server.port, spring.grpc.server.netty.domain-socket-path' "
|
||||
+ "have been configured together");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoTransportAndAddressAndDomainSocketPathThrowsException() throws Exception {
|
||||
InetAddress inetAddress = InetAddress.getByName("192.168.1.0");
|
||||
NettyAddress address = new NettyAddress(null, inetAddress, null, "/ds");
|
||||
assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class)
|
||||
.isThrownBy(() -> address.toString())
|
||||
.withMessage(
|
||||
"The configuration properties 'spring.grpc.server.address, spring.grpc.server.netty.domain-socket-path' "
|
||||
+ "are mutually exclusive and 'spring.grpc.server.address, spring.grpc.server.netty.domain-socket-path' "
|
||||
+ "have been configured together");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenTcpTransportBindsToTcp() throws Exception {
|
||||
InetAddress inetAddress = InetAddress.getByName("192.168.1.0");
|
||||
NettyAddress address = new NettyAddress(Transport.TCP, inetAddress, 1234, "/ds");
|
||||
assertThat(address).hasToString("192.168.1.0:1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDomainSocketTransportAndNoDomainPathThrowsException() {
|
||||
NettyAddress address = new NettyAddress(Transport.DOMAIN_SOCKET, null, null, "");
|
||||
assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class).isThrownBy(() -> address.toString())
|
||||
.withMessage("Property spring.grpc.server.netty.domain-socket-path with value '' is invalid: "
|
||||
+ "A path is required when spring.grpc.server.netty.transport is set to 'domain-socket'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDomainSocketTransportAndDomainPathBindsToDomainPath() throws Exception {
|
||||
InetAddress inetAddress = InetAddress.getByName("192.168.1.0");
|
||||
NettyAddress address = new NettyAddress(Transport.DOMAIN_SOCKET, inetAddress, 1234, "/ds");
|
||||
assertThat(address).hasToString("unix:/ds");
|
||||
}
|
||||
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.grpc.ServerBuilder;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.netty.NettyServerBuilder;
|
||||
import io.grpc.servlet.jakarta.ServletServerBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.verification.VerificationMode;
|
||||
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesServerBuilderCustomizer}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class PropertiesServerBuilderCustomizerTests {
|
||||
|
||||
@Test
|
||||
void customizeWhenNettyServerBuilder() {
|
||||
NettyServerBuilder builder = mock();
|
||||
PropertiesServerBuilderCustomizer<NettyServerBuilder> customizer = new PropertiesServerBuilderCustomizer<>(
|
||||
getProperties());
|
||||
customizer.customize(builder);
|
||||
assertExpectedMapping(builder, atLeastOnce());
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenInProcessServerBuilder() {
|
||||
InProcessServerBuilder builder = mock();
|
||||
PropertiesServerBuilderCustomizer<InProcessServerBuilder> customizer = new PropertiesServerBuilderCustomizer<>(
|
||||
getProperties());
|
||||
customizer.customize(builder);
|
||||
assertExpectedMapping(builder, never());
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizerWhenServletServerBuilder() {
|
||||
ServletServerBuilder builder = mock();
|
||||
PropertiesServerBuilderCustomizer<ServletServerBuilder> customizer = new PropertiesServerBuilderCustomizer<>(
|
||||
getProperties());
|
||||
customizer.customize(builder);
|
||||
assertExpectedMapping(builder, never());
|
||||
}
|
||||
|
||||
private GrpcServerProperties getProperties() {
|
||||
GrpcServerProperties properties = new GrpcServerProperties();
|
||||
properties.getInbound().getMessage().setMaxSize(DataSize.ofMegabytes(333));
|
||||
properties.getInbound().getMetadata().setMaxSize(DataSize.ofKilobytes(111));
|
||||
properties.getKeepalive().setTime(Duration.ofHours(1));
|
||||
properties.getKeepalive().setTimeout(Duration.ofSeconds(10));
|
||||
properties.getKeepalive().getConnection().setMaxIdleTime(Duration.ofHours(2));
|
||||
properties.getKeepalive().getConnection().setMaxAge(Duration.ofHours(3));
|
||||
properties.getKeepalive().getConnection().setGracePeriod(Duration.ofSeconds(45));
|
||||
properties.getKeepalive().getPermit().setTime(Duration.ofMinutes(7));
|
||||
properties.getKeepalive().getPermit().setWithoutCalls(true);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void assertExpectedMapping(ServerBuilder<?> builder, VerificationMode keepAliveMode) {
|
||||
then(builder).should().maxInboundMessageSize(Math.toIntExact(DataSize.ofMegabytes(333).toBytes()));
|
||||
then(builder).should().maxInboundMetadataSize(Math.toIntExact(DataSize.ofKilobytes(111).toBytes()));
|
||||
then(builder).should(keepAliveMode).keepAliveTime(Duration.ofHours(1).toNanos(), TimeUnit.NANOSECONDS);
|
||||
then(builder).should(keepAliveMode).keepAliveTimeout(Duration.ofSeconds(10).toNanos(), TimeUnit.NANOSECONDS);
|
||||
then(builder).should(keepAliveMode).maxConnectionIdle(Duration.ofHours(2).toNanos(), TimeUnit.NANOSECONDS);
|
||||
then(builder).should(keepAliveMode).maxConnectionAge(Duration.ofHours(3).toNanos(), TimeUnit.NANOSECONDS);
|
||||
then(builder).should(keepAliveMode)
|
||||
.maxConnectionAgeGrace(Duration.ofSeconds(45).toNanos(), TimeUnit.NANOSECONDS);
|
||||
then(builder).should(keepAliveMode).permitKeepAliveTime(Duration.ofMinutes(7).toNanos(), TimeUnit.NANOSECONDS);
|
||||
then(builder).should(keepAliveMode).permitKeepAliveWithoutCalls(true);
|
||||
}
|
||||
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.boot.grpc.server.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import io.grpc.TlsServerCredentials.ClientAuth;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
import org.springframework.boot.grpc.server.autoconfigure.GrpcServerProperties.Ssl;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.ssl.SslManagerBundle;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ServerCredentials}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ServerCredentialsTests {
|
||||
|
||||
private final TrustManagerFactory insecureTrustManagerFactory = mock();
|
||||
|
||||
private final SslBundles bundles = mock();
|
||||
|
||||
private final TrustManagerFactory bundleTrustManagerFactory = mock();
|
||||
|
||||
private final KeyManagerFactory bundleKeyManagerFactory = mock();
|
||||
|
||||
ServerCredentialsTests() {
|
||||
SslBundle bundle = mock();
|
||||
SslManagerBundle managers = mock();
|
||||
given(this.bundles.getBundle("test")).willReturn(bundle);
|
||||
given(bundle.getManagers()).willReturn(managers);
|
||||
given(managers.getTrustManagerFactory()).willReturn(this.bundleTrustManagerFactory);
|
||||
given(managers.getKeyManagerFactory()).willReturn(this.bundleKeyManagerFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenNotEnabledAndNoBundleReturnsNullManagers() {
|
||||
ServerCredentials credentials = get((properties) -> {
|
||||
});
|
||||
assertThat(credentials.keyManagerFactory()).isNull();
|
||||
assertThat(credentials.trustManagerFactory()).isNull();
|
||||
assertThat(credentials.clientAuth()).isEqualTo(ClientAuth.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenDisabledReturnsNullManagers() {
|
||||
ServerCredentials credentials = get((properties) -> {
|
||||
properties.put("spring.grpc.server.ssl.enabled", "false");
|
||||
properties.put("spring.grpc.server.ssl.client-auth", "require");
|
||||
});
|
||||
assertThat(credentials.keyManagerFactory()).isNull();
|
||||
assertThat(credentials.trustManagerFactory()).isNull();
|
||||
assertThat(credentials.clientAuth()).isEqualTo(ClientAuth.REQUIRE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenEnabledTrueAndNoBundleNameThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> get((properties) -> {
|
||||
properties.put("spring.grpc.server.ssl.enabled", "true");
|
||||
properties.put("spring.grpc.server.ssl.client-auth", "require");
|
||||
})).withMessage("SSL bundle-name is requested when 'spring.grpc.server.ssl.enabled' is true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasBundleName() {
|
||||
ServerCredentials credentials = get((properties) -> {
|
||||
properties.put("spring.grpc.server.ssl.bundle", "test");
|
||||
properties.put("spring.grpc.server.ssl.client-auth", "require");
|
||||
});
|
||||
assertThat(credentials.keyManagerFactory()).isEqualTo(this.bundleKeyManagerFactory);
|
||||
assertThat(credentials.trustManagerFactory()).isEqualTo(this.bundleTrustManagerFactory);
|
||||
assertThat(credentials.clientAuth()).isEqualTo(ClientAuth.REQUIRE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasBundleNameAndEnabled() {
|
||||
ServerCredentials credentials = get((properties) -> {
|
||||
properties.put("spring.grpc.server.ssl.enabled", "true");
|
||||
properties.put("spring.grpc.server.ssl.bundle", "test");
|
||||
properties.put("spring.grpc.server.ssl.client-auth", "require");
|
||||
});
|
||||
assertThat(credentials.keyManagerFactory()).isEqualTo(this.bundleKeyManagerFactory);
|
||||
assertThat(credentials.trustManagerFactory()).isEqualTo(this.bundleTrustManagerFactory);
|
||||
assertThat(credentials.clientAuth()).isEqualTo(ClientAuth.REQUIRE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenHasBundleNameAndSecureFalse() {
|
||||
ServerCredentials credentials = get((properties) -> {
|
||||
properties.put("spring.grpc.server.ssl.enabled", "true");
|
||||
properties.put("spring.grpc.server.ssl.bundle", "test");
|
||||
properties.put("spring.grpc.server.ssl.secure", "false");
|
||||
});
|
||||
assertThat(credentials.keyManagerFactory()).isEqualTo(this.bundleKeyManagerFactory);
|
||||
assertThat(credentials.trustManagerFactory()).isEqualTo(this.insecureTrustManagerFactory);
|
||||
assertThat(credentials.clientAuth()).isEqualTo(ClientAuth.NONE);
|
||||
}
|
||||
|
||||
private ServerCredentials get(Consumer<Map<String, String>> properties) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
properties.accept(map);
|
||||
Ssl ssl = new Binder(new MapConfigurationPropertySource(map))
|
||||
.bind("spring.grpc.server", GrpcServerProperties.class)
|
||||
.orElseGet(GrpcServerProperties::new)
|
||||
.getSsl();
|
||||
return ServerCredentials.get(ssl, this.bundles, this.insecureTrustManagerFactory);
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -2163,6 +2163,7 @@ bom {
|
||||
"spring-boot-freemarker",
|
||||
"spring-boot-graphql",
|
||||
"spring-boot-graphql-test",
|
||||
"spring-boot-grpc-server",
|
||||
"spring-boot-groovy-templates",
|
||||
"spring-boot-gson",
|
||||
"spring-boot-h2console",
|
||||
@@ -2293,6 +2294,7 @@ bom {
|
||||
"spring-boot-starter-freemarker-test",
|
||||
"spring-boot-starter-graphql",
|
||||
"spring-boot-starter-graphql-test",
|
||||
"spring-boot-starter-grpc-server",
|
||||
"spring-boot-starter-groovy-templates",
|
||||
"spring-boot-starter-groovy-templates-test",
|
||||
"spring-boot-starter-gson",
|
||||
|
||||
@@ -120,6 +120,7 @@ include "module:spring-boot-flyway"
|
||||
include "module:spring-boot-freemarker"
|
||||
include "module:spring-boot-graphql"
|
||||
include "module:spring-boot-graphql-test"
|
||||
include "module:spring-boot-grpc-server"
|
||||
include "module:spring-boot-groovy-templates"
|
||||
include "module:spring-boot-gson"
|
||||
include "module:spring-boot-h2console"
|
||||
@@ -268,6 +269,7 @@ include "starter:spring-boot-starter-freemarker"
|
||||
include "starter:spring-boot-starter-freemarker-test"
|
||||
include "starter:spring-boot-starter-graphql"
|
||||
include "starter:spring-boot-starter-graphql-test"
|
||||
include "starter:spring-boot-starter-grpc-server"
|
||||
include "starter:spring-boot-starter-groovy-templates"
|
||||
include "starter:spring-boot-starter-groovy-templates-test"
|
||||
include "starter:spring-boot-starter-gson"
|
||||
@@ -421,6 +423,9 @@ include ":smoke-test:spring-boot-smoke-test-data-rest"
|
||||
include ":smoke-test:spring-boot-smoke-test-devtools"
|
||||
include ":smoke-test:spring-boot-smoke-test-flyway"
|
||||
include ":smoke-test:spring-boot-smoke-test-graphql"
|
||||
include ":smoke-test:spring-boot-smoke-test-grpc-server"
|
||||
include ":smoke-test:spring-boot-smoke-test-grpc-server-netty-shaded"
|
||||
include ":smoke-test:spring-boot-smoke-test-grpc-server-servlet"
|
||||
include ":smoke-test:spring-boot-smoke-test-hateoas"
|
||||
include ":smoke-test:spring-boot-smoke-test-hibernate"
|
||||
include ":smoke-test:spring-boot-smoke-test-integration"
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "java"
|
||||
id "com.google.protobuf" version "${protobufGradlePluginVersion}"
|
||||
id "org.springframework.boot.docker-test"
|
||||
}
|
||||
|
||||
description = "Spring Boot gRPC server netty shaded smoke test"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":starter:spring-boot-starter-grpc-server")) {
|
||||
exclude(group: "io.grpc", module: "grpc-netty-shaded")
|
||||
}
|
||||
implementation("io.grpc:grpc-netty-shaded")
|
||||
|
||||
dockerTestImplementation(project(":starter:spring-boot-starter-test"))
|
||||
dockerTestImplementation("org.testcontainers:testcontainers-junit-jupiter")
|
||||
}
|
||||
|
||||
def dependenciesBom = project(":platform:spring-boot-dependencies").extensions.getByName("bom")
|
||||
def grpcJava = dependenciesBom.getLibrary("Grpc Java")
|
||||
def protobufJava = dependenciesBom.getLibrary("Protobuf Java")
|
||||
|
||||
tasks.named("compileTestJava") {
|
||||
options.nullability.checking = "tests"
|
||||
}
|
||||
|
||||
nullability {
|
||||
requireExplicitNullMarking = false
|
||||
}
|
||||
|
||||
configurations.named { it.startsWith("protobufToolsLocator_") || it.toLowerCase().endsWith("protopath") }.all {
|
||||
extendsFrom(configurations.dependencyManagement)
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:${protobufJava.version}"
|
||||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:${grpcJava.version}"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all()*.plugins {
|
||||
grpc {
|
||||
option '@generated=omit'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcservernettyshaded;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.startupcheck.IndefiniteWaitOneShotStartupCheckStrategy;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
import smoketest.grpcservernettyshaded.SampleGrpcServerNettyShadedApplicationTests.GrpcServerStartedEventListener;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerStartedEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring gRPC with a shaded netty server.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SpringBootTest(properties = "spring.grpc.server.port=0")
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@Import(GrpcServerStartedEventListener.class)
|
||||
class SampleGrpcServerNettyShadedApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private GrpcServerStartedEventListener startedEventListener;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void test() {
|
||||
String address = "host.docker.internal:" + this.startedEventListener.getPort();
|
||||
try (GenericContainer<?> container = new GenericContainer<>(
|
||||
DockerImageName.parse("fullstorydev/grpcurl:v1.9.3"))
|
||||
.withCommand("-d", "{\"name\": \"spring\"}", "--plaintext", address, "HelloWorld/SayHello")
|
||||
.withStartupCheckStrategy(new IndefiniteWaitOneShotStartupCheckStrategy())) {
|
||||
container.start();
|
||||
assertThat(container.getLogs()).contains("\"message\": \"Hello 'spring'\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GrpcServerStartedEventListener implements ApplicationListener<GrpcServerStartedEvent> {
|
||||
|
||||
private int port;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(GrpcServerStartedEvent event) {
|
||||
this.port = event.getPort();
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcservernettyshaded;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import smoketest.grpcservernettyshaded.proto.HelloReply;
|
||||
import smoketest.grpcservernettyshaded.proto.HelloRequest;
|
||||
import smoketest.grpcservernettyshaded.proto.HelloWorldGrpc;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Service
|
||||
public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
|
||||
|
||||
private static Log logger = LogFactory.getLog(HelloWorldService.class);
|
||||
|
||||
@Override
|
||||
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
String name = request.getName();
|
||||
logger.info("sayHello " + name);
|
||||
Assert.isTrue(!name.startsWith("error"), () -> "Bad name: " + name);
|
||||
Assert.state(!name.startsWith("internal"), "Internal error");
|
||||
String message = "Hello '%s'".formatted(name);
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
String name = request.getName();
|
||||
logger.info("streamHello " + name);
|
||||
int count = 0;
|
||||
while (count < 10) {
|
||||
String message = "Hello(" + count + ") '%s'".formatted(name);
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
|
||||
responseObserver.onNext(reply);
|
||||
count++;
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
responseObserver.onError(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcservernettyshaded;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleGrpcServerNettyShadedApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleGrpcServerNettyShadedApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
@NullMarked
|
||||
package smoketest.grpcservernettyshaded;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "smoketest.grpcservernettyshaded.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service HelloWorld {
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
rpc StreamHello(HelloRequest) returns (stream HelloReply) {}
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "java"
|
||||
id "com.google.protobuf" version "${protobufGradlePluginVersion}"
|
||||
id "org.springframework.boot.docker-test"
|
||||
}
|
||||
|
||||
description = "Spring Boot gRPC server servlet smoke test"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(project(":starter:spring-boot-starter-grpc-server")) {
|
||||
exclude(group: "io.grpc", module: "grpc-netty-shaded")
|
||||
}
|
||||
implementation(project(":starter:spring-boot-starter-tomcat"))
|
||||
implementation("io.grpc:grpc-servlet-jakarta")
|
||||
|
||||
dockerTestImplementation(project(":starter:spring-boot-starter-test"))
|
||||
dockerTestImplementation("org.testcontainers:testcontainers-junit-jupiter")
|
||||
}
|
||||
|
||||
def dependenciesBom = project(":platform:spring-boot-dependencies").extensions.getByName("bom")
|
||||
def grpcJava = dependenciesBom.getLibrary("Grpc Java")
|
||||
def protobufJava = dependenciesBom.getLibrary("Protobuf Java")
|
||||
|
||||
tasks.named("compileTestJava") {
|
||||
options.nullability.checking = "tests"
|
||||
}
|
||||
|
||||
nullability {
|
||||
requireExplicitNullMarking = false
|
||||
}
|
||||
|
||||
configurations.named { it.startsWith("protobufToolsLocator_") || it.toLowerCase().endsWith("protopath") }.all {
|
||||
extendsFrom(configurations.dependencyManagement)
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:${protobufJava.version}"
|
||||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:${grpcJava.version}"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all()*.plugins {
|
||||
grpc {
|
||||
option '@generated=omit'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcserverservlet;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.startupcheck.IndefiniteWaitOneShotStartupCheckStrategy;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring gRPC with a servlet server.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class SampleGrpcServerServletApplicationTests {
|
||||
|
||||
@LocalServerPort
|
||||
private String localServerPort;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void test() {
|
||||
String address = "host.docker.internal:" + this.localServerPort;
|
||||
try (GenericContainer<?> container = new GenericContainer<>(
|
||||
DockerImageName.parse("fullstorydev/grpcurl:v1.9.3"))
|
||||
.withCommand("-d", "{\"name\": \"spring\"}", "--plaintext", address, "HelloWorld/SayHello")
|
||||
.withStartupCheckStrategy(new IndefiniteWaitOneShotStartupCheckStrategy())) {
|
||||
container.start();
|
||||
assertThat(container.getLogs()).contains("\"message\": \"Hello 'spring'\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcserverservlet;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import smoketest.grpcserverservlet.proto.HelloReply;
|
||||
import smoketest.grpcserverservlet.proto.HelloRequest;
|
||||
import smoketest.grpcserverservlet.proto.HelloWorldGrpc;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Service
|
||||
public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
|
||||
|
||||
private static Log logger = LogFactory.getLog(HelloWorldService.class);
|
||||
|
||||
@Override
|
||||
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
String name = request.getName();
|
||||
logger.info("sayHello " + name);
|
||||
Assert.isTrue(!name.startsWith("error"), () -> "Bad name: " + name);
|
||||
Assert.state(!name.startsWith("internal"), "Internal error");
|
||||
String message = "Hello '%s'".formatted(name);
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
String name = request.getName();
|
||||
logger.info("streamHello " + name);
|
||||
int count = 0;
|
||||
while (count < 10) {
|
||||
String message = "Hello(" + count + ") '%s'".formatted(name);
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
|
||||
responseObserver.onNext(reply);
|
||||
count++;
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
responseObserver.onError(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcserverservlet;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleGrpcServerServletApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleGrpcServerServletApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
@NullMarked
|
||||
package smoketest.grpcserverservlet;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "smoketest.grpcserverservlet.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service HelloWorld {
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
rpc StreamHello(HelloRequest) returns (stream HelloReply) {}
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
server.http2.enabled=true
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "java"
|
||||
id "com.google.protobuf" version "${protobufGradlePluginVersion}"
|
||||
id "org.springframework.boot.docker-test"
|
||||
}
|
||||
|
||||
description = "Spring Boot gRPC server smoke test"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":starter:spring-boot-starter-grpc-server"))
|
||||
|
||||
dockerTestImplementation(project(":starter:spring-boot-starter-test"))
|
||||
dockerTestImplementation("org.testcontainers:testcontainers-junit-jupiter")
|
||||
}
|
||||
|
||||
def dependenciesBom = project(":platform:spring-boot-dependencies").extensions.getByName("bom")
|
||||
def grpcJava = dependenciesBom.getLibrary("Grpc Java")
|
||||
def protobufJava = dependenciesBom.getLibrary("Protobuf Java")
|
||||
|
||||
tasks.named("compileTestJava") {
|
||||
options.nullability.checking = "tests"
|
||||
}
|
||||
|
||||
nullability {
|
||||
requireExplicitNullMarking = false
|
||||
}
|
||||
|
||||
configurations.named { it.startsWith("protobufToolsLocator_") || it.toLowerCase().endsWith("protopath") }.all {
|
||||
extendsFrom(configurations.dependencyManagement)
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:${protobufJava.version}"
|
||||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:${grpcJava.version}"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all()*.plugins {
|
||||
grpc {
|
||||
option '@generated=omit'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcserver;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.startupcheck.IndefiniteWaitOneShotStartupCheckStrategy;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
import smoketest.grpcserver.SampleGrpcServerApplicationTests.GrpcServerStartedEventListener;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerStartedEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for the default Spring gRPC netty server.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SpringBootTest(properties = "spring.grpc.server.port=0")
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@Import(GrpcServerStartedEventListener.class)
|
||||
class SampleGrpcServerApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private GrpcServerStartedEventListener startedEventListener;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void test() {
|
||||
String address = "host.docker.internal:" + this.startedEventListener.getPort();
|
||||
try (GenericContainer<?> container = new GenericContainer<>(
|
||||
DockerImageName.parse("fullstorydev/grpcurl:v1.9.3"))
|
||||
.withCommand("-d", "{\"name\": \"spring\"}", "--plaintext", address, "HelloWorld/SayHello")
|
||||
.withStartupCheckStrategy(new IndefiniteWaitOneShotStartupCheckStrategy())) {
|
||||
container.start();
|
||||
assertThat(container.getLogs()).contains("\"message\": \"Hello 'spring'\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GrpcServerStartedEventListener implements ApplicationListener<GrpcServerStartedEvent> {
|
||||
|
||||
private int port;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(GrpcServerStartedEvent event) {
|
||||
this.port = event.getPort();
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcserver;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import smoketest.grpcserver.proto.HelloReply;
|
||||
import smoketest.grpcserver.proto.HelloRequest;
|
||||
import smoketest.grpcserver.proto.HelloWorldGrpc;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Service
|
||||
public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
|
||||
|
||||
private static Log logger = LogFactory.getLog(HelloWorldService.class);
|
||||
|
||||
@Override
|
||||
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
String name = request.getName();
|
||||
logger.info("sayHello " + name);
|
||||
Assert.isTrue(!name.startsWith("error"), () -> "Bad name: " + name);
|
||||
Assert.state(!name.startsWith("internal"), "Internal error");
|
||||
String message = "Hello '%s'".formatted(name);
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
String name = request.getName();
|
||||
logger.info("streamHello " + name);
|
||||
int count = 0;
|
||||
while (count < 10) {
|
||||
String message = "Hello(" + count + ") '%s'".formatted(name);
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
|
||||
responseObserver.onNext(reply);
|
||||
count++;
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
responseObserver.onError(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.grpcserver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleGrpcServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleGrpcServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
@NullMarked
|
||||
package smoketest.grpcserver;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "smoketest.grpcserver.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service HelloWorld {
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
rpc StreamHello(HelloRequest) returns (stream HelloReply) {}
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id "org.springframework.boot.starter"
|
||||
}
|
||||
|
||||
description = "Starter for using Spring gRPC server"
|
||||
|
||||
dependencies {
|
||||
api(project(":starter:spring-boot-starter"))
|
||||
api(project(":module:spring-boot-grpc-server"))
|
||||
api("io.grpc:grpc-netty")
|
||||
api("io.grpc:grpc-services")
|
||||
}
|
||||
Reference in New Issue
Block a user