mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Add Spring gRPC client support
Add support for Spring gRPC client applications. Closes gh-49045 Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
committed by
Phillip Webb
co-authored by
Phillip Webb
parent
e61bb6df5b
commit
c35b21adb4
@@ -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-client"))
|
||||
implementation(project(path: ":module:spring-boot-grpc-server"))
|
||||
implementation(project(path: ":module:spring-boot-health"))
|
||||
implementation(project(path: ":module:spring-boot-hibernate"))
|
||||
|
||||
@@ -97,6 +97,9 @@ dependencies {
|
||||
api(project(":module:spring-boot-graphql")) {
|
||||
transitive = false
|
||||
}
|
||||
api(project(":module:spring-boot-grpc-client")) {
|
||||
transitive = false
|
||||
}
|
||||
api(project(":module:spring-boot-grpc-server")) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 Client"
|
||||
|
||||
dependencies {
|
||||
api(project(":core:spring-boot"))
|
||||
api("org.springframework.grpc:spring-grpc-core")
|
||||
|
||||
optional(project(":core:spring-boot-autoconfigure"))
|
||||
optional(project(":module:spring-boot-micrometer-observation"))
|
||||
optional("io.grpc:grpc-stub")
|
||||
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("io.grpc:grpc-xds") {
|
||||
exclude group: "javax.annotation", module: "javax.annotation-api"
|
||||
}
|
||||
|
||||
testImplementation(project(":core:spring-boot-test"))
|
||||
testImplementation(project(":test-support:spring-boot-test-support"))
|
||||
testImplementation("org.yaml:snakeyaml")
|
||||
|
||||
testRuntimeOnly("ch.qos.logback:logback-classic")
|
||||
}
|
||||
|
||||
tasks.named("compileTestJava") {
|
||||
options.nullability.checking = "tests"
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import io.grpc.stub.AbstractStub;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.grpc.client.CompositeGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for a
|
||||
* {@link CompositeGrpcChannelFactory}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ AbstractStub.class, GrpcChannelBuilderCustomizer.class })
|
||||
@ConditionalOnProperty(name = "spring.grpc.client.enabled", matchIfMissing = true)
|
||||
@Conditional(CompositeChannelFactoryAutoConfiguration.MultipleNonPrimaryChannelFactoriesCondition.class)
|
||||
public final class CompositeChannelFactoryAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
CompositeGrpcChannelFactory compositeChannelFactory(ObjectProvider<GrpcChannelFactory> channelFactoriesProvider) {
|
||||
return new CompositeGrpcChannelFactory(channelFactoriesProvider.orderedStream().toList());
|
||||
}
|
||||
|
||||
static class MultipleNonPrimaryChannelFactoriesCondition extends NoneNestedConditions {
|
||||
|
||||
MultipleNonPrimaryChannelFactoriesCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(GrpcChannelFactory.class)
|
||||
static class NoChannelFactoryCondition {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnSingleCandidate(GrpcChannelFactory.class)
|
||||
static class SingleInjectableChannelFactoryCondition {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+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.client.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.client.channelfactory.enabled} is {@code true} or missing.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@ConditionalOnBooleanProperty(name = "spring.grpc.client.channelfactory.enabled", matchIfMissing = true)
|
||||
@interface ConditionalOnGrpcClientChannelFactoryEnabled {
|
||||
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.util.LambdaSafe;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
|
||||
/**
|
||||
* Invokes the customizations to a {@link ManagedChannelBuilder} based on the provided
|
||||
* beans.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
class GrpcChannelBuilderCustomizers {
|
||||
|
||||
private final List<GrpcChannelBuilderCustomizer<?>> customizers;
|
||||
|
||||
GrpcChannelBuilderCustomizers(GrpcClientProperties grpcClientProperties,
|
||||
ObjectProvider<CompressorRegistry> compressorRegistry,
|
||||
ObjectProvider<DecompressorRegistry> decompressorRegistry,
|
||||
ObjectProvider<GrpcChannelBuilderCustomizer<?>> customizers,
|
||||
ObjectProvider<GrpcClientDefaultServiceConfigCustomizer> defaultServiceConfigCustomizers) {
|
||||
this(grpcClientProperties, compressorRegistry.getIfAvailable(), decompressorRegistry.getIfAvailable(),
|
||||
customizers.orderedStream().toList(), defaultServiceConfigCustomizers.orderedStream().toList());
|
||||
}
|
||||
|
||||
GrpcChannelBuilderCustomizers(List<? extends GrpcChannelBuilderCustomizer<?>> customizers) {
|
||||
this(null, null, null, customizers, Collections.emptyList());
|
||||
}
|
||||
|
||||
GrpcChannelBuilderCustomizers(@Nullable GrpcClientProperties grpcClientProperties,
|
||||
@Nullable CompressorRegistry compressorRegistry, @Nullable DecompressorRegistry decompressorRegistry,
|
||||
List<? extends GrpcChannelBuilderCustomizer<?>> customizers,
|
||||
List<? extends GrpcClientDefaultServiceConfigCustomizer> defaultServiceConfigCustomizers) {
|
||||
List<GrpcChannelBuilderCustomizer<?>> all = new ArrayList<>();
|
||||
addCustomizer(all, compressorRegistry, ManagedChannelBuilder::compressorRegistry);
|
||||
addCustomizer(all, decompressorRegistry, ManagedChannelBuilder::decompressorRegistry);
|
||||
if (grpcClientProperties != null) {
|
||||
all.add(new PropertiesGrpcChannelBuilderCustomizer<>(grpcClientProperties));
|
||||
}
|
||||
all.addAll(customizers);
|
||||
all.add(customizeDefaultServiceConfig(grpcClientProperties, defaultServiceConfigCustomizers));
|
||||
this.customizers = List.copyOf(all);
|
||||
}
|
||||
|
||||
private static <B extends ManagedChannelBuilder<B>, T> void addCustomizer(
|
||||
List<GrpcChannelBuilderCustomizer<?>> customizers, @Nullable T bean, BiConsumer<B, T> action) {
|
||||
if (bean != null) {
|
||||
GrpcChannelBuilderCustomizer<B> customizer = (target, builder) -> action.accept(builder, bean);
|
||||
customizers.add(customizer);
|
||||
}
|
||||
}
|
||||
|
||||
private <B extends ManagedChannelBuilder<B>> GrpcChannelBuilderCustomizer<B> customizeDefaultServiceConfig(
|
||||
@Nullable GrpcClientProperties properties,
|
||||
List<? extends GrpcClientDefaultServiceConfigCustomizer> customizers) {
|
||||
PropertiesGrpcClientDefaultServiceConfigCustomizer propertiesCustomizer = (properties != null)
|
||||
? new PropertiesGrpcClientDefaultServiceConfigCustomizer(properties) : null;
|
||||
return (target, builder) -> {
|
||||
Map<String, Object> defaultServiceConfig = new LinkedHashMap<>();
|
||||
if (propertiesCustomizer != null) {
|
||||
propertiesCustomizer.customize(target, defaultServiceConfig);
|
||||
}
|
||||
customizers.forEach((customizer) -> customizer.customize(target, defaultServiceConfig));
|
||||
if (!defaultServiceConfig.isEmpty()) {
|
||||
builder.defaultServiceConfig(defaultServiceConfig);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
<T extends ManagedChannelBuilder<T>> List<GrpcChannelBuilderCustomizer<T>> forFactory() {
|
||||
return List.of(this::apply);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T extends ManagedChannelBuilder<?>> void apply(String target, T builder) {
|
||||
LambdaSafe.callbacks(GrpcChannelBuilderCustomizer.class, this.customizers, builder)
|
||||
.withLogger(GrpcChannelBuilderCustomizers.class)
|
||||
.invoke((customizer) -> customizer.customize(target, builder));
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize the
|
||||
* {@link GrpcChannelFactory} before it is fully initialized, in particular to tune its
|
||||
* configuration.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GrpcChannelFactoryCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the given {@link GrpcChannelFactory}.
|
||||
* @param factory the factory to customize
|
||||
*/
|
||||
void customize(GrpcChannelFactory factory);
|
||||
|
||||
}
|
||||
+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.client.autoconfigure;
|
||||
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
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.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.CoroutineStubFactory;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for gRPC clients.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@AutoConfiguration(before = CompositeChannelFactoryAutoConfiguration.class)
|
||||
@ConditionalOnClass({ AbstractStub.class, GrpcChannelBuilderCustomizer.class })
|
||||
@ConditionalOnProperty(name = "spring.grpc.client.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(GrpcClientProperties.class)
|
||||
@Import({ GrpcClientCodecConfiguration.class, ShadedNettyGrpcClientConfiguration.class,
|
||||
NettyGrpcClientConfiguration.class, InProcessGrpcClientConfiguration.class })
|
||||
public final class GrpcClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ClientInterceptorsConfigurer grpcClientInterceptorsConfigurer(ApplicationContext applicationContext) {
|
||||
return new ClientInterceptorsConfigurer(applicationContext);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ChannelCredentialsProvider.class)
|
||||
PropertiesChannelCredentialsProvider grpcChannelCredentialsProvider(SslBundles bundles,
|
||||
GrpcClientProperties properties) {
|
||||
return new PropertiesChannelCredentialsProvider(properties, bundles);
|
||||
}
|
||||
|
||||
@Bean
|
||||
<T extends ManagedChannelBuilder<T>> PropertiesGrpcChannelBuilderCustomizer<T> grpcClientPropertiesChannelCustomizer(
|
||||
GrpcClientProperties properties) {
|
||||
return new PropertiesGrpcChannelBuilderCustomizer<>(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
GrpcChannelBuilderCustomizers grpcDefaultServicesChannelBuilderCustomizer(GrpcClientProperties grpcClientProperties,
|
||||
ObjectProvider<CompressorRegistry> compressorRegistry,
|
||||
ObjectProvider<DecompressorRegistry> decompressorRegistry,
|
||||
ObjectProvider<GrpcChannelBuilderCustomizer<?>> customizers,
|
||||
ObjectProvider<GrpcClientDefaultServiceConfigCustomizer> defaultServiceConfigCustomizers) {
|
||||
return new GrpcChannelBuilderCustomizers(grpcClientProperties, compressorRegistry, decompressorRegistry,
|
||||
customizers, defaultServiceConfigCustomizers);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = "io.grpc.kotlin.AbstractCoroutineStub")
|
||||
static class GrpcClientCoroutineStubConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CoroutineStubFactory coroutineStubFactory() {
|
||||
return new CoroutineStubFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.grpc.Codec;
|
||||
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.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* The configuration that contains all codec related beans for clients.
|
||||
*
|
||||
* @author Andrei Lisa
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Codec.class)
|
||||
class GrpcClientCodecConfiguration {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.VirtualTargets;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize the default service config of the
|
||||
* {@link GrpcChannelBuilderCustomizer}.
|
||||
* <p>
|
||||
* This customizer should be used instead of calling
|
||||
* {@link ManagedChannelBuilder#defaultServiceConfig(Map)} from a
|
||||
* {@link GrpcChannelBuilderCustomizer} since it allows multiple customizers to update the
|
||||
* same default service config, rather than having a "last wins" outcome.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.1.0
|
||||
* @see GrpcChannelBuilderCustomizer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GrpcClientDefaultServiceConfigCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the given default service config.
|
||||
* @param target the target (which may be a {@link VirtualTargets virtual target}).
|
||||
* @param defaultServiceConfig the default service config to customize
|
||||
*/
|
||||
void customize(String target, Map<String, Object> defaultServiceConfig);
|
||||
|
||||
}
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.Name;
|
||||
import org.springframework.boot.convert.DurationUnit;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
/**
|
||||
* Configuration properties for gRPC clients.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.grpc.client")
|
||||
public class GrpcClientProperties {
|
||||
|
||||
/**
|
||||
* Map of channel configured by name.
|
||||
*/
|
||||
private final Map<String, Channel> channel = new LinkedHashMap<>();
|
||||
|
||||
public Map<String, Channel> getChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel Properties.
|
||||
*/
|
||||
public static class Channel {
|
||||
|
||||
static final String DEFAULT_TARGET = "static://localhost:9090";
|
||||
|
||||
/**
|
||||
* The channel target address.
|
||||
*/
|
||||
private String target = DEFAULT_TARGET;
|
||||
|
||||
/**
|
||||
* The custom User-Agent for the channel.
|
||||
*/
|
||||
private @Nullable String userAgent;
|
||||
|
||||
/**
|
||||
* Bypass certificate validation for easier testing (so the remote certificate
|
||||
* could be anonymous). Should not be set in production.
|
||||
*/
|
||||
private boolean bypassCertificateValidation;
|
||||
|
||||
private final Inbound inbound = new Inbound();
|
||||
|
||||
@Name("default")
|
||||
private final Default defaultProperties = new Default();
|
||||
|
||||
private final Idle idle = new Idle();
|
||||
|
||||
private final Keepalive keepalive = new Keepalive();
|
||||
|
||||
private final Ssl ssl = new Ssl();
|
||||
|
||||
private final Health health = new Health();
|
||||
|
||||
public String getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
public void setTarget(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public @Nullable String getUserAgent() {
|
||||
return this.userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(@Nullable String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public boolean isBypassCertificateValidation() {
|
||||
return this.bypassCertificateValidation;
|
||||
}
|
||||
|
||||
public void setBypassCertificateValidation(boolean bypassCertificateValidation) {
|
||||
this.bypassCertificateValidation = bypassCertificateValidation;
|
||||
}
|
||||
|
||||
public Inbound getInbound() {
|
||||
return this.inbound;
|
||||
}
|
||||
|
||||
public Default getDefault() {
|
||||
return this.defaultProperties;
|
||||
}
|
||||
|
||||
public Idle getIdle() {
|
||||
return this.idle;
|
||||
}
|
||||
|
||||
public Keepalive getKeepalive() {
|
||||
return this.keepalive;
|
||||
}
|
||||
|
||||
public Ssl getSsl() {
|
||||
return this.ssl;
|
||||
}
|
||||
|
||||
public Health getHealth() {
|
||||
return this.health;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 channel. Set to '-1'
|
||||
* to use the highest possible limit (not recommended).
|
||||
*/
|
||||
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 channel. Set to
|
||||
* '-1' to use the highest possible limit (not recommended).
|
||||
*/
|
||||
private DataSize maxSize = DataSize.ofBytes(8192);
|
||||
|
||||
public DataSize getMaxSize() {
|
||||
return this.maxSize;
|
||||
}
|
||||
|
||||
public void setMaxSize(DataSize maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for client defaults.
|
||||
*/
|
||||
public static class Default {
|
||||
|
||||
/**
|
||||
* The default deadline for RPCs performed on this channel.
|
||||
*/
|
||||
private @Nullable Duration deadline;
|
||||
|
||||
/**
|
||||
* The load balancing policy the channel should use.
|
||||
*/
|
||||
private String loadBalancingPolicy = "round_robin";
|
||||
|
||||
public @Nullable Duration getDeadline() {
|
||||
return this.deadline;
|
||||
}
|
||||
|
||||
public void setDeadline(@Nullable Duration deadline) {
|
||||
this.deadline = deadline;
|
||||
}
|
||||
|
||||
public String getLoadBalancingPolicy() {
|
||||
return this.loadBalancingPolicy;
|
||||
}
|
||||
|
||||
public void setLoadBalancingPolicy(String loadBalancingPolicy) {
|
||||
this.loadBalancingPolicy = loadBalancingPolicy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Idle properties.
|
||||
*/
|
||||
public static class Idle {
|
||||
|
||||
/**
|
||||
* The duration without ongoing RPCs before going to idle mode.
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private Duration timeout = Duration.ofSeconds(20);
|
||||
|
||||
public Duration getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep-alive properties.
|
||||
*/
|
||||
public static class Keepalive {
|
||||
|
||||
/**
|
||||
* The delay before sending a keepAlive. Note that shorter intervals increase
|
||||
* the network burden for the server and this value can not be lower than
|
||||
* 'permitKeepAliveTime' on the server.
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private Duration time = Duration.ofMinutes(5);
|
||||
|
||||
/**
|
||||
* The default timeout for a keepAlives ping request.
|
||||
*/
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private Duration timeout = Duration.ofSeconds(20);
|
||||
|
||||
/**
|
||||
* Whether a keepAlive will be performed when there are no outstanding RPC on
|
||||
* a connection.
|
||||
*/
|
||||
private boolean withoutCalls;
|
||||
|
||||
public Duration getTime() {
|
||||
return this.time;
|
||||
}
|
||||
|
||||
public void setTime(Duration time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Duration getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public boolean isWithoutCalls() {
|
||||
return this.withoutCalls;
|
||||
}
|
||||
|
||||
public void setWithoutCalls(boolean withoutCalls) {
|
||||
this.withoutCalls = withoutCalls;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Health properties.
|
||||
*/
|
||||
public static class Health {
|
||||
|
||||
/**
|
||||
* Whether to enable client-side health check for the channel.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Name of the service to check health on.
|
||||
*/
|
||||
private @Nullable String serviceName;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public @Nullable String getServiceName() {
|
||||
return this.serviceName;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL properties.
|
||||
*/
|
||||
public static class Ssl {
|
||||
|
||||
/**
|
||||
* Whether to enable SSL support. Enabled automatically if "bundle" is
|
||||
* provided unless specified otherwise.
|
||||
*/
|
||||
private @Nullable Boolean enabled;
|
||||
|
||||
/**
|
||||
* SSL bundle name.
|
||||
*/
|
||||
private @Nullable String bundle;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.grpc.client.ClientInterceptorFilter;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for an in-process gRPC client.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(InProcessChannelBuilder.class)
|
||||
@ConditionalOnMissingBean(InProcessGrpcChannelFactory.class)
|
||||
@ConditionalOnGrpcClientChannelFactoryEnabled
|
||||
@ConditionalOnProperty(name = "spring.grpc.client.inprocess.enabled", havingValue = "true", matchIfMissing = true)
|
||||
class InProcessGrpcClientConfiguration {
|
||||
|
||||
@Bean
|
||||
InProcessGrpcChannelFactory inProcessGrpcChannelFactory(GrpcChannelBuilderCustomizers grpcChannelBuilderCustomizers,
|
||||
ClientInterceptorsConfigurer interceptorsConfigurer,
|
||||
ObjectProvider<ClientInterceptorFilter> interceptorFilter,
|
||||
ObjectProvider<GrpcChannelFactoryCustomizer> channelFactoryCustomizers) {
|
||||
InProcessGrpcChannelFactory factory = new InProcessGrpcChannelFactory(
|
||||
grpcChannelBuilderCustomizers.forFactory(), interceptorsConfigurer);
|
||||
interceptorFilter.ifAvailable(factory::setInterceptorFilter);
|
||||
channelFactoryCustomizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.NettyGrpcChannelFactory;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for a Netty gRPC client.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Channel.class, NettyChannelBuilder.class })
|
||||
@ConditionalOnMissingBean(value = GrpcChannelFactory.class, ignored = InProcessGrpcChannelFactory.class)
|
||||
@ConditionalOnGrpcClientChannelFactoryEnabled
|
||||
class NettyGrpcClientConfiguration {
|
||||
|
||||
@Bean
|
||||
NettyGrpcChannelFactory nettyGrpcChannelFactory(Environment environment, GrpcClientProperties properties,
|
||||
GrpcChannelBuilderCustomizers grpcChannelBuilderCustomizers,
|
||||
ClientInterceptorsConfigurer interceptorsConfigurer,
|
||||
ObjectProvider<GrpcChannelFactoryCustomizer> channelFactoryCustomizers,
|
||||
ChannelCredentialsProvider credentials) {
|
||||
NettyGrpcChannelFactory factory = new NettyGrpcChannelFactory(grpcChannelBuilderCustomizers.forFactory(),
|
||||
interceptorsConfigurer);
|
||||
factory.setCredentialsProvider(credentials);
|
||||
factory.setVirtualTargets(new PropertiesVirtualTargets(environment, properties));
|
||||
channelFactoryCustomizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.InsecureChannelCredentials;
|
||||
import io.grpc.TlsChannelCredentials;
|
||||
import io.grpc.TlsChannelCredentials.Builder;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel.Ssl;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.internal.InsecureTrustManagerFactory;
|
||||
|
||||
/**
|
||||
* {@link ChannelCredentialsProvider} backed by {@link GrpcClientProperties}.
|
||||
*
|
||||
* @param properties the client properties
|
||||
* @param bundles the SSL bundles
|
||||
* @author David Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
record PropertiesChannelCredentialsProvider(GrpcClientProperties properties,
|
||||
SslBundles bundles) implements ChannelCredentialsProvider {
|
||||
|
||||
@Override
|
||||
public ChannelCredentials getChannelCredentials(String target) {
|
||||
Channel channel = this.properties.getChannel().get(target);
|
||||
channel = (channel != null) ? channel : this.properties.getChannel().get("default");
|
||||
if (channel == null || isInsecure(channel.getSsl())) {
|
||||
return InsecureChannelCredentials.create();
|
||||
}
|
||||
Builder builder = TlsChannelCredentials.newBuilder();
|
||||
if (channel.getSsl().getBundle() != null) {
|
||||
SslBundle bundle = this.bundles.getBundle(channel.getSsl().getBundle());
|
||||
builder.trustManager(bundle.getManagers().getTrustManagerFactory().getTrustManagers());
|
||||
builder.keyManager(bundle.getManagers().getKeyManagerFactory().getKeyManagers());
|
||||
}
|
||||
if (channel.isBypassCertificateValidation()) {
|
||||
builder.trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private boolean isInsecure(Ssl ssl) {
|
||||
return Boolean.FALSE.equals(ssl.getEnabled()) || (ssl.getBundle() == null && ssl.getEnabled() == null);
|
||||
}
|
||||
|
||||
}
|
||||
+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.client.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 org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.interceptor.DefaultDeadlineSetupClientInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
/**
|
||||
* {@link GrpcChannelBuilderCustomizer} that maps {@link GrpcClientProperties} to a
|
||||
* {@link ManagedChannelBuilder}.
|
||||
*
|
||||
* @param <T> the type of the builder
|
||||
* @param properties the properties to map
|
||||
* @author David Syer
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
record PropertiesGrpcChannelBuilderCustomizer<T extends ManagedChannelBuilder<T>>(
|
||||
GrpcClientProperties properties) implements GrpcChannelBuilderCustomizer<T> {
|
||||
|
||||
private static final Channel STOCK_DEFAULT_CHANNEL = new Channel();
|
||||
|
||||
@Override
|
||||
public void customize(String target, T builder) {
|
||||
Channel channel = getChannel(target);
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(channel::getUserAgent).to(builder::userAgent);
|
||||
map.from(channel.getInbound().getMessage()::getMaxSize).asInt(this::maxSize).to(builder::maxInboundMessageSize);
|
||||
map.from(channel.getInbound().getMetadata()::getMaxSize)
|
||||
.asInt(this::maxSize)
|
||||
.to(builder::maxInboundMetadataSize);
|
||||
map.from(channel.getDefault()::getDeadline)
|
||||
.when((deadline) -> deadline.toMillis() > 0L)
|
||||
.as(DefaultDeadlineSetupClientInterceptor::new)
|
||||
.to(builder::intercept);
|
||||
map.from(channel.getDefault()::getLoadBalancingPolicy)
|
||||
.when((policy) -> supportsLoadBalancing(target, channel))
|
||||
.to(builder::defaultLoadBalancingPolicy);
|
||||
map.from(channel.getIdle()::getTimeout).to(durationProperty(builder::idleTimeout));
|
||||
map.from(channel.getKeepalive()::getTime).to(durationProperty(builder::keepAliveTime));
|
||||
map.from(channel.getKeepalive()::getTimeout).to(durationProperty(builder::keepAliveTimeout));
|
||||
map.from(channel.getKeepalive()::isWithoutCalls).to(builder::keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
private Channel getChannel(String target) {
|
||||
Channel channel = this.properties.getChannel().get(target);
|
||||
channel = (channel != null) ? channel : this.properties.getChannel().get("default");
|
||||
return (channel != null) ? channel : STOCK_DEFAULT_CHANNEL;
|
||||
}
|
||||
|
||||
private boolean supportsLoadBalancing(String target, Channel channel) {
|
||||
return !(isUnixOrInProcessTarget(target) || isUnixOrInProcessTarget(channel.getTarget()));
|
||||
}
|
||||
|
||||
private boolean isUnixOrInProcessTarget(String target) {
|
||||
return target.startsWith("unix:") || target.startsWith("in-process:");
|
||||
}
|
||||
|
||||
Consumer<Duration> durationProperty(BiConsumer<Long, TimeUnit> setter) {
|
||||
return (duration) -> setter.accept(duration.toNanos(), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
private int maxSize(DataSize maxSize) {
|
||||
long bytes = maxSize.toBytes();
|
||||
Assert.state(bytes >= 0 || bytes == -1, () -> "Unsupported max size value " + maxSize);
|
||||
return (bytes >= 0 && bytes <= Integer.MAX_VALUE) ? (int) bytes : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
|
||||
/**
|
||||
* {@link GrpcClientDefaultServiceConfigCustomizer} to apply {@link GrpcClientProperties}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
* @param properties the client properties
|
||||
*/
|
||||
record PropertiesGrpcClientDefaultServiceConfigCustomizer(
|
||||
GrpcClientProperties properties) implements GrpcClientDefaultServiceConfigCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(String target, Map<String, Object> defaultServiceConfig) {
|
||||
Channel channel = this.properties.getChannel().get(target);
|
||||
channel = (channel != null) ? channel : this.properties.getChannel().get("default");
|
||||
if (channel != null && channel.getHealth().isEnabled()) {
|
||||
String serviceName = channel.getHealth().getServiceName();
|
||||
Map<String, String> healthCheckConfig = Map.of("serviceName", (serviceName != null) ? serviceName : "");
|
||||
defaultServiceConfig.put("healthCheckConfig", healthCheckConfig);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.grpc.client.VirtualTargets;
|
||||
|
||||
/**
|
||||
* {@link VirtualTargets} supporting named channels from {@link GrpcClientProperties} and
|
||||
* directly specified targets (which may include property placeholders).
|
||||
*
|
||||
* @param propertyResolver the property resolver
|
||||
* @param properties the client properties
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
record PropertiesVirtualTargets(PropertyResolver propertyResolver,
|
||||
GrpcClientProperties properties) implements VirtualTargets {
|
||||
|
||||
@Override
|
||||
public String getTarget(String target) {
|
||||
Channel channel = this.properties.getChannel().get(target);
|
||||
if (channel != null) {
|
||||
return clean(this.propertyResolver.resolvePlaceholders(channel.getTarget()));
|
||||
}
|
||||
if ("default".equals(target)) {
|
||||
return clean(Channel.DEFAULT_TARGET);
|
||||
}
|
||||
target = this.propertyResolver.resolvePlaceholders(target);
|
||||
if (target.contains(":/") || target.startsWith("unix:")) {
|
||||
return clean(target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private String clean(String target) {
|
||||
if (target.startsWith("static:") || target.startsWith("tcp:")) {
|
||||
String withoutScheme = target.substring(target.indexOf(":") + 1);
|
||||
return withoutScheme.replaceFirst("/*", "");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
+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.client.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.ShadedNettyGrpcChannelFactory;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for a Shaded Netty gRPC client.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ io.grpc.netty.shaded.io.netty.channel.Channel.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class })
|
||||
@ConditionalOnMissingBean(value = GrpcChannelFactory.class, ignored = InProcessGrpcChannelFactory.class)
|
||||
@ConditionalOnGrpcClientChannelFactoryEnabled
|
||||
class ShadedNettyGrpcClientConfiguration {
|
||||
|
||||
@Bean
|
||||
ShadedNettyGrpcChannelFactory shadedNettyGrpcChannelFactory(Environment environment,
|
||||
GrpcClientProperties properties, GrpcChannelBuilderCustomizers grpcChannelBuilderCustomizers,
|
||||
ClientInterceptorsConfigurer interceptorsConfigurer,
|
||||
ObjectProvider<GrpcChannelFactoryCustomizer> channelFactoryCustomizers,
|
||||
ChannelCredentialsProvider credentials) {
|
||||
ShadedNettyGrpcChannelFactory factory = new ShadedNettyGrpcChannelFactory(
|
||||
grpcChannelBuilderCustomizers.forFactory(), interceptorsConfigurer);
|
||||
factory.setCredentialsProvider(credentials);
|
||||
factory.setVirtualTargets(new PropertiesVirtualTargets(environment, properties));
|
||||
channelFactoryCustomizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
+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 clients.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.grpc.client.autoconfigure;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.grpc.client.channelfactory.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable gRPC channel factory bean auto-configuration.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.client.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable gRPC client auto-configuration.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.grpc.client.inprocess.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to configure the in-process channel factory.",
|
||||
"defaultValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.grpc.client.autoconfigure.CompositeChannelFactoryAutoConfiguration
|
||||
org.springframework.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.client.CompositeGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link CompositeChannelFactoryAutoConfiguration}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
class CompositeChannelFactoryAutoConfigurationTests {
|
||||
|
||||
private ApplicationContextRunner contextRunnerWithoutChannelFactories() {
|
||||
return new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcClientAutoConfiguration.class, SslAutoConfiguration.class,
|
||||
CompositeChannelFactoryAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class,
|
||||
NettyChannelBuilder.class, InProcessChannelBuilder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoChannelFactoriesDoesNotAutoconfigureComposite() {
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSingleChannelFactoryDoesNotAutoconfigureComposite() {
|
||||
GrpcChannelFactory channelFactory1 = mock();
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withBean("channelFactory1", GrpcChannelFactory.class, () -> channelFactory1)
|
||||
.run((context) -> assertThat(context).hasSingleBean(GrpcChannelFactory.class)
|
||||
.getBean(GrpcChannelFactory.class)
|
||||
.isNotInstanceOf(CompositeGrpcChannelFactory.class)
|
||||
.isSameAs(channelFactory1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMultipleChannelFactoriesWithPrimaryDoesNotAutoconfigureComposite() {
|
||||
GrpcChannelFactory channelFactory1 = mock();
|
||||
GrpcChannelFactory channelFactory2 = mock();
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withBean("channelFactory1", GrpcChannelFactory.class, () -> channelFactory1)
|
||||
.withBean("channelFactory2", GrpcChannelFactory.class, () -> channelFactory2, (bd) -> bd.setPrimary(true))
|
||||
.run((context) -> {
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("channelFactory1", "channelFactory2");
|
||||
assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isNotInstanceOf(CompositeGrpcChannelFactory.class)
|
||||
.isSameAs(channelFactory2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMultipleChannelFactoriesDoesAutoconfigureComposite() {
|
||||
GrpcChannelFactory channelFactory1 = mock();
|
||||
GrpcChannelFactory channelFactory2 = mock();
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withBean("channelFactory1", GrpcChannelFactory.class, () -> channelFactory1)
|
||||
.withBean("channelFactory2", GrpcChannelFactory.class, () -> channelFactory2)
|
||||
.run((context) -> {
|
||||
assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("channelFactory1", "channelFactory2", "compositeChannelFactory");
|
||||
assertThat(context).getBean(GrpcChannelFactory.class).isInstanceOf(CompositeGrpcChannelFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void compositeAutoconfiguredAsExpected() {
|
||||
this.contextRunnerWithoutChannelFactories()
|
||||
.withUserConfiguration(MultipleFactoriesTestConfig.class)
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(CompositeGrpcChannelFactory.class)
|
||||
.extracting("channelFactories")
|
||||
.asInstanceOf(InstanceOfAssertFactories.list(GrpcChannelFactory.class))
|
||||
.containsExactly(MultipleFactoriesTestConfig.CHANNEL_FACTORY_BAR,
|
||||
MultipleFactoriesTestConfig.CHANNEL_FACTORY_BAZ,
|
||||
MultipleFactoriesTestConfig.CHANNEL_FACTORY_FOO));
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MultipleFactoriesTestConfig {
|
||||
|
||||
static GrpcChannelFactory CHANNEL_FACTORY_FOO = mock();
|
||||
static GrpcChannelFactory CHANNEL_FACTORY_BAR = mock();
|
||||
static GrpcChannelFactory CHANNEL_FACTORY_BAZ = mock();
|
||||
|
||||
@Bean
|
||||
@Order(3)
|
||||
GrpcChannelFactory channelFactoryFoo() {
|
||||
return CHANNEL_FACTORY_FOO;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
GrpcChannelFactory channelFactoryBar() {
|
||||
return CHANNEL_FACTORY_BAR;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
GrpcChannelFactory channelFactoryBaz() {
|
||||
return CHANNEL_FACTORY_BAZ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcChannelBuilderCustomizers}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcChannelBuilderCustomizersTests {
|
||||
|
||||
@Test
|
||||
void applyWhenHasProperties() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channel = new Channel();
|
||||
channel.setUserAgent("spring-boot");
|
||||
properties.getChannel().put("target", channel);
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(properties, null, null,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
then(builder).should().userAgent("spring-boot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHasCompressorRegistry() {
|
||||
CompressorRegistry compressorRegistry = mock();
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(null, compressorRegistry, null,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
then(builder).should().compressorRegistry(compressorRegistry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHasDecompressorRegistry() {
|
||||
DecompressorRegistry decompressorRegistry = mock();
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(null, null, decompressorRegistry,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
then(builder).should().decompressorRegistry(decompressorRegistry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenEmptyCustomizersDoesNothing() {
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
new GrpcChannelBuilderCustomizers(Collections.emptyList()).apply("target", builder);
|
||||
then(builder).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenSimpleChannelBuilder() {
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(
|
||||
List.of(new SimpleChannelBuilderCustomizer()));
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
then(builder).should().flowControlWindow(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void applyWhenGenericCustomizersRespectsGeneric() {
|
||||
List<TestCustomizer<?>> list = new ArrayList<>();
|
||||
list.add(new TestCustomizer<>());
|
||||
list.add(new TestNettyChannelBuilderCustomizer());
|
||||
list.add(new TestShadedNettyChannelBuilderCustomizer());
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(list);
|
||||
customizers.apply("target", mock(ManagedChannelBuilder.class));
|
||||
assertThat(list.get(0).getCount()).isOne();
|
||||
assertThat(list.get(1).getCount()).isZero();
|
||||
assertThat(list.get(2).getCount()).isZero();
|
||||
customizers.apply("target", mock(NettyChannelBuilder.class));
|
||||
assertThat(list.get(0).getCount()).isEqualTo(2);
|
||||
assertThat(list.get(1).getCount()).isOne();
|
||||
assertThat(list.get(2).getCount()).isZero();
|
||||
customizers.apply("target", mock(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class));
|
||||
assertThat(list.get(0).getCount()).isEqualTo(3);
|
||||
assertThat(list.get(1).getCount()).isOne();
|
||||
assertThat(list.get(2).getCount()).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHasGrpcClientDefaultServiceConfigCustomizers() {
|
||||
GrpcClientDefaultServiceConfigCustomizer defaultConfigCustomizer1 = (target, defaultServiceConfig) -> {
|
||||
defaultServiceConfig.put("c", "v1");
|
||||
defaultServiceConfig.put("c1", "v1");
|
||||
};
|
||||
GrpcClientDefaultServiceConfigCustomizer defaultConfigCustomizer2 = (target, defaultServiceConfig) -> {
|
||||
defaultServiceConfig.put("c", "v2");
|
||||
defaultServiceConfig.put("c2", "v2");
|
||||
};
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(null, null, null,
|
||||
Collections.emptyList(), List.of(defaultConfigCustomizer1, defaultConfigCustomizer2));
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
expected.put("c", "v2");
|
||||
expected.put("c1", "v1");
|
||||
expected.put("c2", "v2");
|
||||
then(builder).should().defaultServiceConfig(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHasChannelHealthAddsHealthServiceConfig() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channel = new Channel();
|
||||
channel.getHealth().setEnabled(true);
|
||||
channel.getHealth().setServiceName("testservice");
|
||||
properties.getChannel().put("target", channel);
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(properties, null, null,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
expected.put("healthCheckConfig", Map.of("serviceName", "testservice"));
|
||||
then(builder).should().defaultServiceConfig(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHasDefaultHealthAddsHealthServiceConfig() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channel = new Channel();
|
||||
channel.getHealth().setEnabled(true);
|
||||
channel.getHealth().setServiceName("testdefaultservice");
|
||||
properties.getChannel().put("default", channel);
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(properties, null, null,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
expected.put("healthCheckConfig", Map.of("serviceName", "testdefaultservice"));
|
||||
then(builder).should().defaultServiceConfig(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenHealthEnabledAndNoServiceNameAddsHealthConfig() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channel = new Channel();
|
||||
channel.getHealth().setEnabled(true);
|
||||
properties.getChannel().put("target", channel);
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(properties, null, null,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
expected.put("healthCheckConfig", Map.of("serviceName", ""));
|
||||
then(builder).should().defaultServiceConfig(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenNoCustomizersOrHealthDoesSetDefaultServiceConfig() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
GrpcChannelBuilderCustomizers customizers = new GrpcChannelBuilderCustomizers(properties, null, null,
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
NettyChannelBuilder builder = mock(NettyChannelBuilder.class);
|
||||
customizers.apply("target", builder);
|
||||
then(builder).should(never()).defaultServiceConfig(any());
|
||||
}
|
||||
|
||||
static class SimpleChannelBuilderCustomizer implements GrpcChannelBuilderCustomizer<NettyChannelBuilder> {
|
||||
|
||||
@Override
|
||||
public void customize(String target, NettyChannelBuilder channelBuilder) {
|
||||
channelBuilder.flowControlWindow(100);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match any {@link GrpcChannelBuilderCustomizer}.
|
||||
*
|
||||
* @param <T> the builder type
|
||||
*/
|
||||
static class TestCustomizer<T extends ManagedChannelBuilder<T>> implements GrpcChannelBuilderCustomizer<T> {
|
||||
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public void customize(String targetOrChannelName, T channelBuilder) {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match only {@link NettyChannelBuilder}.
|
||||
*/
|
||||
static class TestNettyChannelBuilderCustomizer extends TestCustomizer<NettyChannelBuilder> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test customizer that will match only
|
||||
* {@link io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder}.
|
||||
*/
|
||||
static class TestShadedNettyChannelBuilderCustomizer
|
||||
extends TestCustomizer<io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.grpc.Codec;
|
||||
import io.grpc.CompressorRegistry;
|
||||
import io.grpc.DecompressorRegistry;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.kotlin.AbstractCoroutineStub;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration.GrpcClientCoroutineStubConfiguration;
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.boot.grpc.client.autoconfigure.test.scan.DummyBlockingGrpc;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.client.InProcessGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.NettyGrpcChannelFactory;
|
||||
import org.springframework.grpc.client.ShadedNettyGrpcChannelFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcClientAutoConfiguration}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
class GrpcClientAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcClientAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
|
||||
private final ApplicationContextRunner contextRunnerWithoutInProcessChannelFactory = this.contextRunner
|
||||
.withPropertyValues("spring.grpc.client.inprocess.enabled=false");
|
||||
|
||||
@Test
|
||||
void whenGrpcStubNotOnClasspathThenAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(AbstractStub.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcClientAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGrpcKotlinIsNotOnClasspathThenAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(AbstractCoroutineStub.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcClientCoroutineStubConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClientEnabledPropertySetFalseThenAutoConfigurationIsSkipped() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.client.enabled=false")
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcClientAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClientEnabledPropertyNotSetThenAutoConfigurationIsNotSkipped() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(GrpcClientAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenClientEnabledPropertySetTrueThenAutoConfigurationIsNotSkipped() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.client.enabled=true")
|
||||
.run((context) -> assertThat(context).hasSingleBean(GrpcClientAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedCredentialsProviderDoesNotAutoConfigureBean() {
|
||||
ChannelCredentialsProvider customCredentialsProvider = mock(ChannelCredentialsProvider.class);
|
||||
this.contextRunner
|
||||
.withBean("customCredentialsProvider", ChannelCredentialsProvider.class, () -> customCredentialsProvider)
|
||||
.run((context) -> assertThat(context).getBean(ChannelCredentialsProvider.class)
|
||||
.isSameAs(customCredentialsProvider));
|
||||
}
|
||||
|
||||
@Test
|
||||
void credentialsProviderAutoConfiguredAsExpected() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(PropertiesChannelCredentialsProvider.class)
|
||||
.hasFieldOrPropertyWithValue("properties", context.getBean(GrpcClientProperties.class))
|
||||
.extracting("bundles")
|
||||
.isInstanceOf(SslBundles.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientPropertiesAutoConfiguredResolvesPlaceholders() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.grpc.client.channel.c1.target=my-server-${channelName}:8888", "channelName=foo")
|
||||
.run((context) -> assertThat(context).getBean(GrpcClientProperties.class).satisfies((properties) -> {
|
||||
Channel channel = properties.getChannel().get("c1");
|
||||
assertThat(channel).isNotNull();
|
||||
assertThat(channel.getTarget()).isEqualTo("my-server-foo:8888");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientPropertiesChannelCustomizerAutoConfiguredWithHealthAsExpected() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.grpc.client.channel.test.health.enabled=true",
|
||||
"spring.grpc.client.channel.test.health.service-name=my-service")
|
||||
.run((context) -> {
|
||||
GrpcChannelBuilderCustomizers customizers = context.getBean(GrpcChannelBuilderCustomizers.class);
|
||||
ManagedChannelBuilder<?> builder = Mockito.mock();
|
||||
customizers.apply("test", builder);
|
||||
Map<String, ?> healthCheckConfig = Map.of("healthCheckConfig", Map.of("serviceName", "my-service"));
|
||||
then(builder).should().defaultServiceConfig(healthCheckConfig);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientPropertiesChannelCustomizerAutoConfiguredWithoutHealthAsExpected() {
|
||||
this.contextRunner.run((context) -> {
|
||||
GrpcChannelBuilderCustomizers customizers = context.getBean(GrpcChannelBuilderCustomizers.class);
|
||||
ManagedChannelBuilder<?> builder = Mockito.mock();
|
||||
customizers.apply("test", builder);
|
||||
then(builder).should(never()).defaultServiceConfig(anyMap());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void compressionCustomizerAutoConfiguredAsExpected() {
|
||||
this.contextRunner.run((context) -> {
|
||||
GrpcChannelBuilderCustomizers customizers = context.getBean(GrpcChannelBuilderCustomizers.class);
|
||||
CompressorRegistry compressorRegistry = context.getBean(CompressorRegistry.class);
|
||||
ManagedChannelBuilder<?> builder = Mockito.mock();
|
||||
customizers.apply("testChannel", builder);
|
||||
then(builder).should().compressorRegistry(compressorRegistry);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void compressionCustomizerWhenNoRegistrry() {
|
||||
// Codec class guards the imported GrpcCodecConfiguration to hide registry
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(Codec.class)).run((context) -> {
|
||||
GrpcChannelBuilderCustomizers customizers = context.getBean(GrpcChannelBuilderCustomizers.class);
|
||||
ManagedChannelBuilder<?> builder = Mockito.mock();
|
||||
customizers.apply("testChannel", builder);
|
||||
then(builder).should(never()).compressorRegistry(any());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void decompressionCustomizerAutoConfiguredAsExpected() {
|
||||
this.contextRunner.run((context) -> {
|
||||
GrpcChannelBuilderCustomizers customizers = context.getBean(GrpcChannelBuilderCustomizers.class);
|
||||
DecompressorRegistry decompressorRegistry = context.getBean(DecompressorRegistry.class);
|
||||
ManagedChannelBuilder<?> builder = Mockito.mock();
|
||||
customizers.apply("testChannel", builder);
|
||||
then(builder).should().decompressorRegistry(decompressorRegistry);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoDecompressorRegistryThenDecompressionCustomizerIsNotConfigured() {
|
||||
// Codec class guards the imported GrpcCodecConfiguration to hide registry
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(Codec.class)).run((context) -> {
|
||||
GrpcChannelBuilderCustomizers customizers = context.getBean(GrpcChannelBuilderCustomizers.class);
|
||||
ManagedChannelBuilder<?> builder = Mockito.mock();
|
||||
customizers.apply("testChannel", builder);
|
||||
then(builder).should(never()).compressorRegistry(any());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessEnabledPropNotSetDoesAutoconfigureInProcess() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessEnabledPropSetToTrueDoesAutoconfigureInProcess() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.client.inprocess.enabled=true")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessEnabledPropSetToFalseDoesNotAutoconfigureInProcess() {
|
||||
this.contextRunner.withPropertyValues("spring.grpc.client.inprocess.enabled=false")
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.doesNotContainKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInProcessIsNotOnClasspathDoesNotAutoconfigureInProcess() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(InProcessChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.doesNotContainKey("inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedInProcessChannelFactoryDoesNotAutoConfigureBean() {
|
||||
InProcessGrpcChannelFactory customChannelFactory = mock();
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.withBean("customChannelFactory", InProcessGrpcChannelFactory.class, () -> customChannelFactory)
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class).isSameAs(customChannelFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasUserDefinedChannelFactoryDoesNotAutoConfigureNettyOrShadedNetty() {
|
||||
GrpcChannelFactory customChannelFactory = mock();
|
||||
this.contextRunnerWithoutInProcessChannelFactory
|
||||
.withBean("customChannelFactory", GrpcChannelFactory.class, () -> customChannelFactory)
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class).isSameAs(customChannelFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userDefinedChannelFactoryWithInProcessChannelFactory() {
|
||||
GrpcChannelFactory customChannelFactory = mock();
|
||||
this.contextRunner.withBean("customChannelFactory", GrpcChannelFactory.class, () -> customChannelFactory)
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("customChannelFactory", "inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedAndNonShadedNettyOnClasspathShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(ShadedNettyGrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyWithInProcessChannelFactory() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("shadedNettyGrpcChannelFactory", "inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenOnlyNonShadedNettyOnClasspathNonShadedNettyFactoryIsAutoConfigured() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(NettyGrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonShadedNettyWithInProcessChannelFactory() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBeans(GrpcChannelFactory.class)
|
||||
.containsOnlyKeys("nettyGrpcChannelFactory", "inProcessGrpcChannelFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenShadedNettyAndNettyNotOnClasspathNoChannelFactoryIsAutoConfigured() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noChannelFactoryWithInProcessChannelFactory() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(InProcessGrpcChannelFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyChannelFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(ShadedNettyGrpcChannelFactory.class)
|
||||
.hasFieldOrPropertyWithValue("credentials", context.getBean(PropertiesChannelCredentialsProvider.class))
|
||||
.extracting("targets")
|
||||
.isInstanceOf(PropertiesVirtualTargets.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyChannelFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunnerWithoutInProcessChannelFactory
|
||||
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(NettyGrpcChannelFactory.class)
|
||||
.hasFieldOrPropertyWithValue("credentials", context.getBean(PropertiesChannelCredentialsProvider.class))
|
||||
.extracting("targets")
|
||||
.isInstanceOf(PropertiesVirtualTargets.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessChannelFactoryAutoConfiguredAsExpected() {
|
||||
this.contextRunner
|
||||
.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class))
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(InProcessGrpcChannelFactory.class)
|
||||
.extracting("credentials")
|
||||
.isSameAs(ChannelCredentialsProvider.INSECURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shadedNettyChannelFactoryAutoConfiguredWithCustomizers() {
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder builder = mock();
|
||||
channelFactoryAutoConfiguredWithCustomizers(this.contextRunnerWithoutInProcessChannelFactory, builder,
|
||||
ShadedNettyGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nettyChannelFactoryAutoConfiguredWithCustomizers() {
|
||||
NettyChannelBuilder builder = mock();
|
||||
channelFactoryAutoConfiguredWithCustomizers(
|
||||
this.contextRunnerWithoutInProcessChannelFactory.withClassLoader(
|
||||
new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)),
|
||||
builder, NettyGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inProcessChannelFactoryAutoConfiguredWithCustomizers() {
|
||||
InProcessChannelBuilder builder = mock();
|
||||
channelFactoryAutoConfiguredWithCustomizers(
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(NettyChannelBuilder.class,
|
||||
io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class)),
|
||||
builder, InProcessGrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> void channelFactoryAutoConfiguredWithCustomizers(
|
||||
ApplicationContextRunner contextRunner, ManagedChannelBuilder<T> mockChannelBuilder,
|
||||
Class<?> expectedChannelFactoryType) {
|
||||
contextRunner.withUserConfiguration(ChannelBuilderCustomizersConfig.class)
|
||||
.run((context) -> assertThat(context).getBean(GrpcChannelFactory.class)
|
||||
.isInstanceOf(expectedChannelFactoryType)
|
||||
.extracting("globalCustomizers", InstanceOfAssertFactories.list(GrpcChannelBuilderCustomizer.class))
|
||||
.satisfies((allCustomizers) -> {
|
||||
allCustomizers.forEach((c) -> c.customize("channel1", mockChannelBuilder));
|
||||
InOrder ordered = inOrder(mockChannelBuilder);
|
||||
ordered.verify(mockChannelBuilder).keepAliveTime(40L, TimeUnit.SECONDS);
|
||||
ordered.verify(mockChannelBuilder).keepAliveTime(50L, TimeUnit.SECONDS);
|
||||
}));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigurationPackage(basePackageClasses = DummyBlockingGrpc.class)
|
||||
static class AutoConfigurePackagesConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ChannelBuilderCustomizersConfig {
|
||||
|
||||
@Bean
|
||||
@Order(100)
|
||||
<T extends ManagedChannelBuilder<T>> GrpcChannelBuilderCustomizer<T> customizerOne() {
|
||||
return (target, builder) -> builder.keepAliveTime(40L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(200)
|
||||
<T extends ManagedChannelBuilder<T>> GrpcChannelBuilderCustomizer<T> customizerTwo() {
|
||||
return (target, builder) -> builder.keepAliveTime(50L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GrpcClientProperties}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GrpcClientPropertiesTests {
|
||||
|
||||
@Test
|
||||
@WithResource(name = "client.yaml", content = """
|
||||
channel:
|
||||
a:
|
||||
target: static://my-server:8888
|
||||
b:
|
||||
user-agent: me""")
|
||||
void defaultValues() throws Exception {
|
||||
GrpcClientProperties properties = bind();
|
||||
Channel channelA = properties.getChannel().get("a");
|
||||
assertThat(channelA).isNotNull();
|
||||
assertThat(channelA.getUserAgent()).isNull();
|
||||
assertThat(channelA.isBypassCertificateValidation()).isFalse();
|
||||
assertThat(channelA.getInbound().getMessage().getMaxSize()).isEqualTo(DataSize.ofBytes(4194304));
|
||||
assertThat(channelA.getInbound().getMetadata().getMaxSize()).isEqualTo(DataSize.ofBytes(8192));
|
||||
assertThat(channelA.getDefault().getDeadline()).isNull();
|
||||
assertThat(channelA.getIdle().getTimeout()).isEqualTo(Duration.ofSeconds(20));
|
||||
assertThat(channelA.getKeepalive().getTime()).isEqualTo(Duration.ofMinutes(5));
|
||||
assertThat(channelA.getKeepalive().getTimeout()).isEqualTo(Duration.ofSeconds(20));
|
||||
assertThat(channelA.getKeepalive().isWithoutCalls()).isFalse();
|
||||
assertThat(channelA.getSsl().getEnabled()).isNull();
|
||||
assertThat(channelA.getSsl().getBundle()).isNull();
|
||||
Channel channelB = properties.getChannel().get("b");
|
||||
assertThat(channelB).isNotNull();
|
||||
assertThat(channelB.getTarget()).isEqualTo("static://localhost:9090");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "client.yaml", content = """
|
||||
channel:
|
||||
test:
|
||||
target: static://my-server:8888
|
||||
user-agent: me
|
||||
bypass-certificate-validation: true
|
||||
inbound:
|
||||
message:
|
||||
max-size: 200MB
|
||||
metadata:
|
||||
max-size: 1GB
|
||||
default:
|
||||
deadline: 1s
|
||||
load-balancing-policy: pick_first
|
||||
idle:
|
||||
timeout: 1m
|
||||
keepalive:
|
||||
time: 200s
|
||||
timeout: 60000ms
|
||||
without-calls: true
|
||||
ssl:
|
||||
enabled: true
|
||||
bundle: my-bundle
|
||||
health:
|
||||
enabled: true
|
||||
service-name: my-service""")
|
||||
void specificProperties() throws Exception {
|
||||
GrpcClientProperties properties = bind();
|
||||
Channel channel = properties.getChannel().get("test");
|
||||
assertThat(channel).isNotNull();
|
||||
assertThat(channel.getTarget()).isEqualTo("static://my-server:8888");
|
||||
assertThat(channel.getUserAgent()).isEqualTo("me");
|
||||
assertThat(channel.isBypassCertificateValidation()).isTrue();
|
||||
assertThat(channel.getInbound().getMessage().getMaxSize()).isEqualTo(DataSize.ofMegabytes(200));
|
||||
assertThat(channel.getInbound().getMetadata().getMaxSize()).isEqualTo(DataSize.ofGigabytes(1));
|
||||
assertThat(channel.getDefault().getDeadline()).isEqualTo(Duration.ofSeconds(1));
|
||||
assertThat(channel.getIdle().getTimeout()).isEqualTo(Duration.ofMinutes(1));
|
||||
assertThat(channel.getKeepalive().getTime()).isEqualTo(Duration.ofSeconds(200));
|
||||
assertThat(channel.getKeepalive().getTimeout()).isEqualTo(Duration.ofMillis(60000));
|
||||
assertThat(channel.getKeepalive().isWithoutCalls()).isTrue();
|
||||
assertThat(channel.getSsl().getEnabled()).isTrue();
|
||||
assertThat(channel.getSsl().getBundle()).isEqualTo("my-bundle");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "client.yaml", content = """
|
||||
channel:
|
||||
test:
|
||||
idle:
|
||||
timeout: 1
|
||||
keepalive:
|
||||
time: 60
|
||||
timeout: 5""")
|
||||
void withoutKeepAliveUnitsSpecified() throws Exception {
|
||||
GrpcClientProperties properties = bind();
|
||||
Channel channel = properties.getChannel().get("test");
|
||||
assertThat(channel).isNotNull();
|
||||
assertThat(channel.getIdle().getTimeout()).isEqualTo(Duration.ofSeconds(1));
|
||||
assertThat(channel.getKeepalive().getTime()).isEqualTo(Duration.ofSeconds(60));
|
||||
assertThat(channel.getKeepalive().getTimeout()).isEqualTo(Duration.ofSeconds(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "client.yaml", content = """
|
||||
channel:
|
||||
test:
|
||||
inbound:
|
||||
message:
|
||||
max-size: 1000
|
||||
metadata:
|
||||
max-size: 256""")
|
||||
void withoutInboundSizeUnitsSpecified() throws Exception {
|
||||
GrpcClientProperties properties = bind();
|
||||
Channel channel = properties.getChannel().get("test");
|
||||
assertThat(channel).isNotNull();
|
||||
assertThat(channel.getInbound().getMessage().getMaxSize()).isEqualTo(DataSize.ofBytes(1000));
|
||||
assertThat(channel.getInbound().getMetadata().getMaxSize()).isEqualTo(DataSize.ofBytes(256));
|
||||
}
|
||||
|
||||
private GrpcClientProperties bind() throws Exception {
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
new YamlPropertySourceLoader().load("client.yaml", new ClassPathResource("client.yaml"))
|
||||
.forEach(environment.getPropertySources()::addLast);
|
||||
return Binder.get(environment)
|
||||
.bind("", Bindable.of(GrpcClientProperties.class))
|
||||
.orElseGet(GrpcClientProperties::new);
|
||||
}
|
||||
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import io.grpc.Codec;
|
||||
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.FilteredClassLoader;
|
||||
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 GrpcClientCodecConfiguration}.
|
||||
*
|
||||
* @author Andrei Lisa
|
||||
*/
|
||||
class GrpcCodecConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GrpcClientCodecConfiguration.class));
|
||||
|
||||
@Test
|
||||
void whenCodecNotOnClasspathThenAutoconfigurationSkipped() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(Codec.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(GrpcClientCodecConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenHasCustomCompressorRegistryDoesNotAutoConfigureBean() {
|
||||
CompressorRegistry customRegistry = mock();
|
||||
this.contextRunner.withBean("customCompressorRegistry", CompressorRegistry.class, () -> customRegistry)
|
||||
.run((context) -> assertThat(context).getBean(CompressorRegistry.class).isSameAs(customRegistry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compressorRegistryAutoConfiguredAsExpected() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(CompressorRegistry.class)
|
||||
.isSameAs(CompressorRegistry.getDefaultInstance()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCustomCompressorsThenCompressorRegistryIsNewInstance() {
|
||||
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 whenHasCustomDecompressorRegistryDoesNotAutoConfigureBean() {
|
||||
DecompressorRegistry customRegistry = mock();
|
||||
this.contextRunner.withBean("customDecompressorRegistry", DecompressorRegistry.class, () -> customRegistry)
|
||||
.run((context) -> assertThat(context).getBean(DecompressorRegistry.class).isSameAs(customRegistry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decompressorRegistryAutoConfiguredAsExpected() {
|
||||
this.contextRunner.run((context) -> assertThat(context).getBean(DecompressorRegistry.class)
|
||||
.isSameAs(DecompressorRegistry.getDefaultInstance()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCustomDecompressorsThenDecompressorRegistryIsNewInstance() {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.InsecureChannelCredentials;
|
||||
import io.grpc.TlsChannelCredentials;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.ssl.SslManagerBundle;
|
||||
import org.springframework.grpc.client.ChannelCredentialsProvider;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesChannelCredentialsProvider}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class PropertiesChannelCredentialsProviderTests {
|
||||
|
||||
private final TrustManager[] trustManagers = { mock() };
|
||||
|
||||
private final KeyManager[] keyManagers = { mock() };
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenTargetMatchesChannel() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.getSsl().setEnabled(true);
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
TlsChannelCredentials credentials = (TlsChannelCredentials) provider.getChannelCredentials("test");
|
||||
assertThat(credentials.getTrustManagers()).isNull();
|
||||
assertThat(credentials.getKeyManagers()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenTargetDoesNotMatchChannelAndHasDefault() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.getSsl().setEnabled(true);
|
||||
properties.getChannel().put("default", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
TlsChannelCredentials credentials = (TlsChannelCredentials) provider.getChannelCredentials("test");
|
||||
assertThat(credentials.getTrustManagers()).isNull();
|
||||
assertThat(credentials.getKeyManagers()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenTargetDoesNotMatchChannelAndHasNoDefaultUsesInsecure() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
ChannelCredentials credentials = provider.getChannelCredentials("test");
|
||||
assertThat(credentials).isInstanceOf(InsecureChannelCredentials.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenSslExplictlyDisabled() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.getSsl().setEnabled(false);
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
ChannelCredentials credentials = provider.getChannelCredentials("test");
|
||||
assertThat(credentials).isInstanceOf(InsecureChannelCredentials.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenSslExplictlyEnabledAndNoBundle() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.getSsl().setEnabled(true);
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
TlsChannelCredentials credentials = (TlsChannelCredentials) provider.getChannelCredentials("test");
|
||||
assertThat(credentials.getTrustManagers()).isNull();
|
||||
assertThat(credentials.getKeyManagers()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenNoSslEnabledSetButHasBundle() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.getSsl().setBundle("test");
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry("test", mockBundle());
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
TlsChannelCredentials credentials = (TlsChannelCredentials) provider.getChannelCredentials("test");
|
||||
assertThat(credentials.getTrustManagers()).containsExactly(this.trustManagers);
|
||||
assertThat(credentials.getKeyManagers()).containsExactly(this.keyManagers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenNoSslEnabledSetAndNoBundle() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
ChannelCredentials credentials = provider.getChannelCredentials("test");
|
||||
assertThat(credentials).isInstanceOf(InsecureChannelCredentials.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenSslEnabledAndHasBundle() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.getSsl().setEnabled(true);
|
||||
channelProperties.getSsl().setBundle("test");
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry("test", mockBundle());
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
TlsChannelCredentials credentials = (TlsChannelCredentials) provider.getChannelCredentials("test");
|
||||
assertThat(credentials.getTrustManagers()).containsExactly(this.trustManagers);
|
||||
assertThat(credentials.getKeyManagers()).containsExactly(this.keyManagers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannelCredentialsWhenBypassCertificateValidation() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
channelProperties.setBypassCertificateValidation(true);
|
||||
channelProperties.getSsl().setBundle("test");
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
SslBundles sslBundles = new DefaultSslBundleRegistry("test", mockBundle());
|
||||
ChannelCredentialsProvider provider = new PropertiesChannelCredentialsProvider(properties, sslBundles);
|
||||
TlsChannelCredentials credentials = (TlsChannelCredentials) provider.getChannelCredentials("test");
|
||||
TrustManager trustManager = credentials.getTrustManagers().get(0);
|
||||
assertThat(trustManager.getClass().getName()).contains("InsecureTrustManager");
|
||||
assertThat(((X509ExtendedTrustManager) trustManager).getAcceptedIssuers()).isEmpty();
|
||||
assertThat(credentials.getKeyManagers()).containsExactly(this.keyManagers);
|
||||
}
|
||||
|
||||
private SslBundle mockBundle() {
|
||||
SslBundle bundle = mock();
|
||||
SslManagerBundle managerBundle = mock();
|
||||
TrustManagerFactory trustManagerFactory = mock();
|
||||
KeyManagerFactory keyManagerFactory = mock();
|
||||
given(bundle.getManagers()).willReturn(managerBundle);
|
||||
given(managerBundle.getTrustManagerFactory()).willReturn(trustManagerFactory);
|
||||
given(managerBundle.getKeyManagerFactory()).willReturn(keyManagerFactory);
|
||||
given(trustManagerFactory.getTrustManagers()).willReturn(this.trustManagers);
|
||||
given(keyManagerFactory.getKeyManagers()).willReturn(this.keyManagers);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.interceptor.DefaultDeadlineSetupClientInterceptor;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesGrpcChannelBuilderCustomizer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class PropertiesGrpcChannelBuilderCustomizerTests {
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenHasMatchingChannel() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
properties.getChannel().put("test", createTestChannelProperties());
|
||||
GrpcChannelBuilderCustomizer<T> customizer = new PropertiesGrpcChannelBuilderCustomizer<>(properties);
|
||||
T builder = mock();
|
||||
customizer.customize("test", builder);
|
||||
assertMapped(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenHasDefaultChannel() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
properties.getChannel().put("default", createTestChannelProperties());
|
||||
GrpcChannelBuilderCustomizer<T> customizer = new PropertiesGrpcChannelBuilderCustomizer<>(properties);
|
||||
T builder = mock();
|
||||
customizer.customize("test", builder);
|
||||
assertMapped(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenHasNoMatchAndNoDefault() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
properties.getChannel().put("other", createTestChannelProperties());
|
||||
GrpcChannelBuilderCustomizer<T> customizer = new PropertiesGrpcChannelBuilderCustomizer<>(properties);
|
||||
T builder = mock();
|
||||
customizer.customize("test", builder);
|
||||
assertMappedStockDefaults(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenChannelDoesNotSupportLoadBalancingDoesNotMapDefaultLoadBalancer() {
|
||||
assertNoLoadBalancerMappedBasedOnChannel("unix:test");
|
||||
assertNoLoadBalancerMappedBasedOnChannel("in-process:test");
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> void assertNoLoadBalancerMappedBasedOnChannel(String target) {
|
||||
T builder = getBuilder((channelProperties) -> {
|
||||
channelProperties.setTarget(target);
|
||||
channelProperties.getDefault().setLoadBalancingPolicy("testlbp");
|
||||
});
|
||||
then(builder).should(never()).defaultLoadBalancingPolicy(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenTargetDoesNotSupportLoadBalancingDoesNotMapDefaultLoadBalancer() {
|
||||
GrpcChannelBuilderCustomizer<?> customizer = getCustomizer(
|
||||
(channelProperties) -> channelProperties.setTarget("static://localhost:1234"));
|
||||
assertNoLoadBalancerMappedBasedOnTarget(customizer, "unix:test");
|
||||
assertNoLoadBalancerMappedBasedOnTarget(customizer, "in-process:test");
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> void assertNoLoadBalancerMappedBasedOnTarget(
|
||||
GrpcChannelBuilderCustomizer<T> customizer, String target) {
|
||||
T builder = mock();
|
||||
customizer.customize(target, builder);
|
||||
then(builder).should(never()).defaultLoadBalancingPolicy(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenMaxMessageSizeIsMinusOneUsesMaxValue() {
|
||||
T builder = getBuilder(
|
||||
(channelProperties) -> channelProperties.getInbound().getMessage().setMaxSize(DataSize.ofBytes(-1)));
|
||||
then(builder).should().maxInboundMessageSize(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenMaxMessageSizeIsTooBigUsesMaxValue() {
|
||||
T builder = getBuilder((channelProperties) -> channelProperties.getInbound()
|
||||
.getMessage()
|
||||
.setMaxSize(DataSize.ofBytes((long) Integer.MAX_VALUE + 100)));
|
||||
then(builder).should().maxInboundMessageSize(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenMaxMessageSizeIsNegativeAndNotMinusOneThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> getBuilder(
|
||||
(channelProperties) -> channelProperties.getInbound().getMessage().setMaxSize(DataSize.ofBytes(-2))))
|
||||
.withMessage("Unsupported max size value -2B");
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenMaxMetadataSizeIsMinusOneUsesMaxValue() {
|
||||
T builder = getBuilder(
|
||||
(channelProperties) -> channelProperties.getInbound().getMetadata().setMaxSize(DataSize.ofBytes(-1)));
|
||||
then(builder).should().maxInboundMetadataSize(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenMaxMetadataSizeIsTooBigUsesMaxValue() {
|
||||
T builder = getBuilder((channelProperties) -> channelProperties.getInbound()
|
||||
.getMetadata()
|
||||
.setMaxSize(DataSize.ofBytes((long) Integer.MAX_VALUE + 100)));
|
||||
then(builder).should().maxInboundMetadataSize(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
<T extends ManagedChannelBuilder<T>> void customizeWhenMaxMetadataSizeIsNegativeAndNotMinusOneThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> getBuilder(
|
||||
(channelProperties) -> channelProperties.getInbound().getMetadata().setMaxSize(DataSize.ofBytes(-2))))
|
||||
.withMessage("Unsupported max size value -2B");
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> T getBuilder(Consumer<Channel> setup) {
|
||||
GrpcChannelBuilderCustomizer<T> customizer = getCustomizer(setup);
|
||||
T builder = mock();
|
||||
customizer.customize("test", builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> GrpcChannelBuilderCustomizer<T> getCustomizer(
|
||||
Consumer<Channel> setup) {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channelProperties = new Channel();
|
||||
setup.accept(channelProperties);
|
||||
properties.getChannel().put("test", channelProperties);
|
||||
GrpcChannelBuilderCustomizer<T> customizer = new PropertiesGrpcChannelBuilderCustomizer<>(properties);
|
||||
return customizer;
|
||||
}
|
||||
|
||||
private Channel createTestChannelProperties() {
|
||||
Channel properties = new Channel();
|
||||
properties.setUserAgent("testua");
|
||||
properties.getInbound().getMessage().setMaxSize(DataSize.ofBytes(10));
|
||||
properties.getInbound().getMetadata().setMaxSize(DataSize.ofBytes(20));
|
||||
properties.getDefault().setDeadline(Duration.ofMinutes(5));
|
||||
properties.getDefault().setLoadBalancingPolicy("testlbp");
|
||||
properties.getIdle().setTimeout(Duration.ofMinutes(6));
|
||||
properties.getKeepalive().setTime(Duration.ofMinutes(7));
|
||||
properties.getKeepalive().setTimeout(Duration.ofMinutes(8));
|
||||
properties.getKeepalive().setWithoutCalls(true);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> void assertMapped(T builder) {
|
||||
then(builder).should().userAgent("testua");
|
||||
then(builder).should().maxInboundMessageSize(10);
|
||||
then(builder).should().maxInboundMetadataSize(20);
|
||||
ArgumentCaptor<ClientInterceptor[]> interceptors = ArgumentCaptor.captor();
|
||||
then(builder).should().intercept(interceptors.capture());
|
||||
ClientInterceptor interceptor = interceptors.getValue()[0];
|
||||
assertThat(interceptor).isInstanceOf(DefaultDeadlineSetupClientInterceptor.class)
|
||||
.extracting("defaultDeadline")
|
||||
.isEqualTo(Duration.ofMinutes(5));
|
||||
then(builder).should().defaultLoadBalancingPolicy("testlbp");
|
||||
then(builder).should().idleTimeout(360000000000L, TimeUnit.NANOSECONDS);
|
||||
then(builder).should().keepAliveTime(420000000000L, TimeUnit.NANOSECONDS);
|
||||
then(builder).should().keepAliveTimeout(480000000000L, TimeUnit.NANOSECONDS);
|
||||
then(builder).should().keepAliveWithoutCalls(true);
|
||||
}
|
||||
|
||||
private <T extends ManagedChannelBuilder<T>> void assertMappedStockDefaults(T builder) {
|
||||
then(builder).should().maxInboundMessageSize(4194304);
|
||||
then(builder).should().maxInboundMetadataSize(8192);
|
||||
then(builder).should().defaultLoadBalancingPolicy("round_robin");
|
||||
then(builder).should().idleTimeout(20000000000L, TimeUnit.NANOSECONDS);
|
||||
then(builder).should().keepAliveTime(300000000000L, TimeUnit.NANOSECONDS);
|
||||
then(builder).should().keepAliveTimeout(20000000000L, TimeUnit.NANOSECONDS);
|
||||
then(builder).should().keepAliveWithoutCalls(false);
|
||||
then(builder).shouldHaveNoMoreInteractions();
|
||||
}
|
||||
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.client.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.grpc.client.autoconfigure.GrpcClientProperties.Channel;
|
||||
import org.springframework.grpc.client.VirtualTargets;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesVirtualTargets}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class PropertiesVirtualTargetsTests {
|
||||
|
||||
@Test
|
||||
void getTargetWhenHasMatchingChannel() {
|
||||
GrpcClientProperties properties = createProperties("test", "my-server:8888");
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("test")).isEqualTo("my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenDefaultAndDefaultChannelDefined() {
|
||||
GrpcClientProperties properties = createProperties("default", "my-server:8888");
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("default")).isEqualTo("my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenDefaultAndNoDefaultChannelDefined() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("default")).isEqualTo("localhost:9090");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenChannelHasStaticTargetReturnsStrippedTarget() {
|
||||
GrpcClientProperties properties = createProperties("test", "static://my-server:8888");
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("test")).isEqualTo("my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenChannelHasTcpTargetReturnsStrippedTarget() {
|
||||
GrpcClientProperties properties = createProperties("test", "tcp://my-server:8888");
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("test")).isEqualTo("my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenChannelHasOtherUrlTarget() {
|
||||
GrpcClientProperties properties = createProperties("test", "foo://my-server:8888");
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("test")).isEqualTo("foo://my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenStaticReturnsStripped() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("static://my-server:8888")).isEqualTo("my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenTcpReturnsStripped() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("tcp://my-server:8888")).isEqualTo("my-server:8888");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenUnixUrlDoesNotPrependStatic() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("foo://bar")).isEqualTo("foo://bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenUrlReturnsAsIs() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("unix:foo")).isEqualTo("unix:foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetUrlWhenHasColonWithoutSlashReturnsAsIs() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(new MockEnvironment(), properties);
|
||||
assertThat(targets.getTarget("localhost:123/bar")).isEqualTo("localhost:123/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetWhenNotChannelNameResolvesPlaceholders() {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("channelName", "foo");
|
||||
VirtualTargets targets = new PropertiesVirtualTargets(environment, properties);
|
||||
assertThat(targets.getTarget("my-server-${channelName}:8888")).isEqualTo("my-server-foo:8888");
|
||||
}
|
||||
|
||||
private GrpcClientProperties createProperties(String name, String target) {
|
||||
GrpcClientProperties properties = new GrpcClientProperties();
|
||||
Channel channel = new Channel();
|
||||
channel.setTarget(target);
|
||||
properties.getChannel().put(name, channel);
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.client.autoconfigure.test.scan;
|
||||
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.stub.AbstractBlockingStub;
|
||||
import io.grpc.stub.AbstractStub.StubFactory;
|
||||
|
||||
public final class DummyBlockingGrpc {
|
||||
|
||||
private DummyBlockingGrpc() {
|
||||
}
|
||||
|
||||
public static DummyBlockingStub newBlockingStub(io.grpc.Channel channel) {
|
||||
return AbstractBlockingStub.newStub((StubFactory<DummyBlockingStub>) DummyBlockingStub::new, channel);
|
||||
}
|
||||
|
||||
public static class DummyBlockingStub extends AbstractBlockingStub<DummyBlockingStub> {
|
||||
|
||||
protected DummyBlockingStub(Channel channel, CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DummyBlockingStub build(Channel channel, CallOptions callOptions) {
|
||||
return new DummyBlockingStub(channel, callOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.client.autoconfigure.test.scan;
|
||||
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.stub.AbstractBlockingStub;
|
||||
import io.grpc.stub.AbstractStub.StubFactory;
|
||||
|
||||
public final class DummyBlockingV2Grpc {
|
||||
|
||||
private DummyBlockingV2Grpc() {
|
||||
}
|
||||
|
||||
public static DummyBlockingV2Stub newBlockingV2Stub(io.grpc.Channel channel) {
|
||||
return AbstractBlockingStub.newStub((StubFactory<DummyBlockingV2Stub>) DummyBlockingV2Stub::new, channel);
|
||||
}
|
||||
|
||||
public static class DummyBlockingV2Stub extends AbstractBlockingStub<DummyBlockingV2Stub> {
|
||||
|
||||
protected DummyBlockingV2Stub(Channel channel, CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DummyBlockingV2Stub build(Channel channel, CallOptions callOptions) {
|
||||
return new DummyBlockingV2Stub(channel, callOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+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 org.springframework.boot.grpc.client.autoconfigure.test.scan;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -2163,6 +2163,7 @@ bom {
|
||||
"spring-boot-freemarker",
|
||||
"spring-boot-graphql",
|
||||
"spring-boot-graphql-test",
|
||||
"spring-boot-grpc-client",
|
||||
"spring-boot-grpc-server",
|
||||
"spring-boot-groovy-templates",
|
||||
"spring-boot-gson",
|
||||
@@ -2294,6 +2295,7 @@ bom {
|
||||
"spring-boot-starter-freemarker-test",
|
||||
"spring-boot-starter-graphql",
|
||||
"spring-boot-starter-graphql-test",
|
||||
"spring-boot-starter-grpc-client",
|
||||
"spring-boot-starter-grpc-server",
|
||||
"spring-boot-starter-groovy-templates",
|
||||
"spring-boot-starter-groovy-templates-test",
|
||||
|
||||
@@ -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-client"
|
||||
include "module:spring-boot-grpc-server"
|
||||
include "module:spring-boot-groovy-templates"
|
||||
include "module:spring-boot-gson"
|
||||
@@ -269,6 +270,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-client"
|
||||
include "starter:spring-boot-starter-grpc-server"
|
||||
include "starter:spring-boot-starter-groovy-templates"
|
||||
include "starter:spring-boot-starter-groovy-templates-test"
|
||||
@@ -423,6 +425,7 @@ 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-client"
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 client smoke test"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":starter:spring-boot-starter-grpc-client"))
|
||||
|
||||
testImplementation(project(":starter:spring-boot-starter-test"))
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.grpcclient;
|
||||
|
||||
import smoketest.grpcclient.proto.HelloReply;
|
||||
import smoketest.grpcclient.proto.HelloRequest;
|
||||
import smoketest.grpcclient.proto.HelloWorldGrpc.HelloWorldBlockingStub;
|
||||
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.grpc.client.ImportGrpcClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@ImportGrpcClients(types = HelloWorldBlockingStub.class)
|
||||
public class SampleGrpcClientApplication {
|
||||
|
||||
@Bean
|
||||
ApplicationRunner applicationRunner(HelloWorldBlockingStub hello) {
|
||||
return (args) -> {
|
||||
HelloRequest request = HelloRequest.newBuilder().setName("Spring").build();
|
||||
HelloReply reply = hello.sayHello(request);
|
||||
System.out.println(">>> " + reply.getMessage());
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleGrpcClientApplication.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.grpcclient;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "smoketest.grpcclient.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,6 @@
|
||||
spring:
|
||||
grpc:
|
||||
client:
|
||||
channel:
|
||||
default:
|
||||
target: "static://localhost:9090"
|
||||
+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 smoketest.grpcclient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import smoketest.grpcclient.SampleGrpcClientApplicationTests.MockServerInitializer;
|
||||
import smoketest.grpcclient.proto.HelloReply;
|
||||
import smoketest.grpcclient.proto.HelloRequest;
|
||||
import smoketest.grpcclient.proto.HelloWorldGrpc;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.grpc.server.NettyGrpcServerFactory;
|
||||
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
@ContextConfiguration(initializers = MockServerInitializer.class)
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SampleGrpcClientApplicationTests {
|
||||
|
||||
@Test
|
||||
void applicationRunsAndCallsGrpcServer(CapturedOutput output) {
|
||||
assertThat(output).contains(">>> Hello 'Spring'");
|
||||
}
|
||||
|
||||
static class MockServerInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
NettyGrpcServerFactory serverFactory = new NettyGrpcServerFactory("*:0", Collections.emptyList(), null,
|
||||
null, null);
|
||||
HelloWorldService helloWorldService = new HelloWorldService();
|
||||
serverFactory.addService(helloWorldService.bindService());
|
||||
GrpcServerLifecycle lifecycle = new GrpcServerLifecycle(serverFactory, Duration.ofSeconds(30),
|
||||
applicationContext);
|
||||
lifecycle.start();
|
||||
String target = "static://localhost:%s".formatted(lifecycle.getPort());
|
||||
applicationContext.getEnvironment()
|
||||
.getPropertySources()
|
||||
.addFirst(new MapPropertySource("grpc", Map.of("spring.grpc.client.channel.default.target", target)));
|
||||
applicationContext.getBeanFactory().registerSingleton("grpcServerLifecyce", lifecycle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
|
||||
|
||||
@Override
|
||||
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
HelloReply reply = HelloReply.newBuilder().setMessage("Hello '%s'".formatted(request.getName())).build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 client"
|
||||
|
||||
dependencies {
|
||||
api(project(":starter:spring-boot-starter"))
|
||||
api(project(":module:spring-boot-grpc-client"))
|
||||
api("io.grpc:grpc-netty")
|
||||
api("io.grpc:grpc-stub")
|
||||
}
|
||||
Reference in New Issue
Block a user