Move code around so factory gets address not port

This commit is contained in:
Dave Syer
2024-10-18 11:16:54 +01:00
committed by Dave Syer
parent 85de440e50
commit 3cb6ce3912
14 changed files with 262 additions and 214 deletions

View File

@@ -43,7 +43,7 @@ import io.grpc.ManagedChannel;
class GrpcServerIntegrationTests {
@Nested
@SpringBootTest(properties = { "spring.grpc.server.address=0.0.0.0", "spring.grpc.server.port=0" })
@SpringBootTest(properties = { "spring.grpc.server.host=0.0.0.0", "spring.grpc.server.port=0" })
class ServerWithAnyIPv4AddressAndRandomPort {
@Test
@@ -55,7 +55,7 @@ class GrpcServerIntegrationTests {
}
@Nested
@SpringBootTest(properties = { "spring.grpc.server.address=::", "spring.grpc.server.port=0" })
@SpringBootTest(properties = { "spring.grpc.server.host=::", "spring.grpc.server.port=0" })
class ServerWithAnyIPv6AddressAndRandomPort {
@Test
@@ -67,7 +67,7 @@ class GrpcServerIntegrationTests {
}
@Nested
@SpringBootTest(properties = { "spring.grpc.server.address=127.0.0.1", "spring.grpc.server.port=0" })
@SpringBootTest(properties = { "spring.grpc.server.host=127.0.0.1", "spring.grpc.server.port=0" })
class ServerWithLocalhostAndRandomPort {
@Test

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.grpc.internal;
public class GrpcUtils {
public static int DEFAULT_PORT = 9090;
public static int getPort(String address) {
String value = address;
if (value.contains(":")) {
value = value.substring(value.lastIndexOf(":") + 1);
}
if (value.contains("/")) {
value = value.substring(0, value.indexOf("/"));
}
if (value.matches("[0-9]+")) {
return Integer.parseInt(value);
}
if (address.startsWith("unix:")) {
return -1;
}
return DEFAULT_PORT;
}
}

View File

@@ -22,12 +22,18 @@ import java.util.Objects;
import java.util.Set;
import com.google.common.collect.Lists;
import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerCredentials;
import io.grpc.ServerProvider;
import io.grpc.ServerServiceDefinition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.grpc.internal.GrpcUtils;
/**
* Default implementation for {@link GrpcServerFactory gRPC service factories}.
@@ -48,25 +54,17 @@ public class DefaultGrpcServerFactory<T extends ServerBuilder<T>> implements Grp
private final String address;
private final int port;
private final List<ServerBuilderCustomizer<T>> serverBuilderCustomizers;
public DefaultGrpcServerFactory(String address, int port,
List<ServerBuilderCustomizer<T>> serverBuilderCustomizers) {
public DefaultGrpcServerFactory(String address, List<ServerBuilderCustomizer<T>> serverBuilderCustomizers) {
this.address = address;
this.port = port;
this.serverBuilderCustomizers = Objects.requireNonNull(serverBuilderCustomizers, "serverBuilderCustomizers");
}
protected String getAddress() {
protected String address() {
return this.address;
}
protected int getPort() {
return this.port;
}
@Override
public Server createServer() {
T builder = newServerBuilder();
@@ -85,7 +83,23 @@ public class DefaultGrpcServerFactory<T extends ServerBuilder<T>> implements Grp
*/
@SuppressWarnings("unchecked")
protected T newServerBuilder() {
return (T) ServerBuilder.forPort(port);
return (T) Grpc.newServerBuilderForPort(port(), credentials());
}
/**
* Returns the port number on which the server should listen. Use 0 to let the system
* choose a port. Use -1 to denote that this server does not listen on a socket.
* @return the port number
*/
protected int port() {
return GrpcUtils.getPort(address());
}
/**
* @return some server credentials (default is insecure)
*/
protected ServerCredentials credentials() {
return InsecureServerCredentials.create();
}
/**

View File

@@ -16,10 +16,12 @@
package org.springframework.grpc.server;
import java.net.InetSocketAddress;
import java.util.List;
import com.google.common.net.InetAddresses;
import javax.net.ssl.KeyManagerFactory;
import io.grpc.ServerCredentials;
import io.grpc.TlsServerCredentials;
import io.grpc.netty.NettyServerBuilder;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerDomainSocketChannel;
@@ -33,34 +35,33 @@ import io.netty.channel.unix.DomainSocketAddress;
*/
public class NettyGrpcServerFactory extends DefaultGrpcServerFactory<NettyServerBuilder> {
private static final String ANY_IP_ADDRESS = "*";
private KeyManagerFactory keyManager;
public NettyGrpcServerFactory(String address, int port,
public NettyGrpcServerFactory(String address, KeyManagerFactory keyManager,
List<ServerBuilderCustomizer<NettyServerBuilder>> serverBuilderCustomizers) {
super(address, port, serverBuilderCustomizers);
super(address, serverBuilderCustomizers);
this.keyManager = keyManager;
}
/**
* Creates a new server builder.
* @return The newly created server builder.
*/
@Override
protected NettyServerBuilder newServerBuilder() {
String address = getAddress();
int port = getPort();
if (address != null) {
if (address.startsWith("unix:")) {
String path = address.substring(5);
return NettyServerBuilder.forAddress(new DomainSocketAddress(path))
.channelType(EpollServerDomainSocketChannel.class)
.bossEventLoopGroup(new EpollEventLoopGroup(1))
.workerEventLoopGroup(new EpollEventLoopGroup());
}
if (!ANY_IP_ADDRESS.equals(address)) {
return NettyServerBuilder.forAddress(new InetSocketAddress(InetAddresses.forString(address), port));
}
// TODO: Add more support for address resolution
String address = address();
if (address.startsWith("unix:")) {
String path = address.substring(5);
return NettyServerBuilder.forAddress(new DomainSocketAddress(path))
.channelType(EpollServerDomainSocketChannel.class)
.bossEventLoopGroup(new EpollEventLoopGroup(1))
.workerEventLoopGroup(new EpollEventLoopGroup());
}
return super.newServerBuilder();
}
@Override
protected ServerCredentials credentials() {
if (this.keyManager == null || port() == -1) {
return super.credentials();
}
return TlsServerCredentials.newBuilder().keyManager(this.keyManager.getKeyManagers()).build();
}
}

View File

@@ -16,10 +16,12 @@
package org.springframework.grpc.server;
import java.net.InetSocketAddress;
import java.util.List;
import com.google.common.net.InetAddresses;
import javax.net.ssl.KeyManagerFactory;
import io.grpc.ServerCredentials;
import io.grpc.TlsServerCredentials;
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup;
import io.grpc.netty.shaded.io.netty.channel.epoll.EpollServerDomainSocketChannel;
@@ -33,34 +35,33 @@ import io.grpc.netty.shaded.io.netty.channel.unix.DomainSocketAddress;
*/
public class ShadedNettyGrpcServerFactory extends DefaultGrpcServerFactory<NettyServerBuilder> {
private static final String ANY_IP_ADDRESS = "*";
private KeyManagerFactory keyManager;
public ShadedNettyGrpcServerFactory(String address, int port,
public ShadedNettyGrpcServerFactory(String address, KeyManagerFactory keyManager,
List<ServerBuilderCustomizer<NettyServerBuilder>> serverBuilderCustomizers) {
super(address, port, serverBuilderCustomizers);
super(address, serverBuilderCustomizers);
this.keyManager = keyManager;
}
/**
* Creates a new server builder.
* @return The newly created server builder.
*/
@Override
protected NettyServerBuilder newServerBuilder() {
String address = getAddress();
int port = getPort();
if (address != null) {
if (address.startsWith("unix:")) {
String path = address.substring(5);
return NettyServerBuilder.forAddress(new DomainSocketAddress(path))
.channelType(EpollServerDomainSocketChannel.class)
.bossEventLoopGroup(new EpollEventLoopGroup(1))
.workerEventLoopGroup(new EpollEventLoopGroup());
}
if (!ANY_IP_ADDRESS.equals(address)) {
return NettyServerBuilder.forAddress(new InetSocketAddress(InetAddresses.forString(address), port));
}
// TODO: Add more support for address resolution
String address = address();
if (address.startsWith("unix:")) {
String path = address.substring(5);
return NettyServerBuilder.forAddress(new DomainSocketAddress(path))
.channelType(EpollServerDomainSocketChannel.class)
.bossEventLoopGroup(new EpollEventLoopGroup(1))
.workerEventLoopGroup(new EpollEventLoopGroup());
}
return super.newServerBuilder();
}
@Override
protected ServerCredentials credentials() {
if (this.keyManager == null || port() == -1) {
return super.credentials();
}
return TlsServerCredentials.newBuilder().keyManager(this.keyManager.getKeyManagers()).build();
}
}

View File

@@ -0,0 +1,43 @@
package org.springframework.grpc.internal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class GrpcUtilsTests {
@Test
void testGetPortFromAddress() {
assertEquals(8080, GrpcUtils.getPort("localhost:8080"));
}
@Test
void testGetNoPort() {
assertEquals(9090, GrpcUtils.getPort("localhost"));
}
@Test
void testGetPortFromAddressWithPath() {
String address = "example.com:1234/path";
assertEquals(1234, GrpcUtils.getPort(address));
}
@Test
void testGetDomainAddress() {
String address = "unix:/some/file/somewhere";
assertEquals(-1, GrpcUtils.getPort(address));
}
@Test
void testGetStaticSchema() {
String address = "static://localhost";
assertEquals(9090, GrpcUtils.getPort(address));
}
@Test
void testGetInvalidAddress() {
String address = "invalid:broken";
assertEquals(9090, GrpcUtils.getPort(address)); // -1?
}
}

View File

@@ -16,7 +16,8 @@
|spring.grpc.client.default-channel.ssl.bundle | | SSL bundle name.
|spring.grpc.client.default-channel.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise.
|spring.grpc.client.default-channel.user-agent | |
|spring.grpc.server.address | `+++*+++` | Server address to bind to. The default is any IP address ('*').
|spring.grpc.server.address | | The address to bind to. could be a host:port combination or a pseudo URL like static://host:port. Can not be set if host or port are set independently.
|spring.grpc.server.host | `+++*+++` | Server address to bind to. The default is any IP address ('*').
|spring.grpc.server.keep-alive.max-age | | Maximum time a connection may exist before being gracefully terminated (default infinite).
|spring.grpc.server.keep-alive.max-age-grace | | Maximum time for graceful connection termination (default infinite).
|spring.grpc.server.keep-alive.max-idle | | Maximum time a connection can remain idle before being gracefully terminated (default infinite).
@@ -26,7 +27,7 @@
|spring.grpc.server.keep-alive.timeout | `+++20s+++` | Maximum time to wait for read activity after sending a keep alive ping. If sender does not receive an acknowledgment within this time, it will close the connection (default 20s).
|spring.grpc.server.max-inbound-message-size | `+++4194304B+++` | Maximum message size allowed to be received by the server (default 4MiB).
|spring.grpc.server.max-inbound-metadata-size | `+++8192B+++` | Maximum metadata size allowed to be received by the server (default 8KiB).
|spring.grpc.server.port | `+++9090+++` | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090.
|spring.grpc.server.port | | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090.
|spring.grpc.server.shutdown-grace-period | `+++30s+++` | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds.
|spring.grpc.server.ssl.bundle | | SSL bundle name.
|spring.grpc.server.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise.

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.grpc.autoconfigure.client;
import java.net.URI;
import java.util.List;
import javax.net.ssl.SSLException;
@@ -33,7 +32,6 @@ import org.springframework.grpc.client.NettyGrpcChannelFactory;
import org.springframework.grpc.client.ShadedNettyGrpcChannelFactory;
import org.springframework.grpc.client.VirtualTargets;
import io.grpc.ManagedChannelBuilder;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@@ -142,21 +140,6 @@ public class GrpcChannelFactoryConfigurations {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ManagedChannelBuilder.class)
@ConditionalOnMissingBean(GrpcChannelFactory.class)
public static class DefaultChannelFactoryConfiguration {
@Bean
public DefaultGrpcChannelFactory defaultGrpcChannelFactory(final List<GrpcChannelConfigurer> configurers,
GrpcClientProperties channels) {
DefaultGrpcChannelFactory factory = new DefaultGrpcChannelFactory(configurers);
factory.setVirtualTargets(new NamedChannelVirtualTargets(channels));
return factory;
}
}
static class NamedChannelVirtualTargets implements VirtualTargets {
private final GrpcClientProperties channels;

View File

@@ -29,8 +29,7 @@ import org.springframework.grpc.client.GrpcChannelConfigurer;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(GrpcClientProperties.class)
@Import({ GrpcChannelFactoryConfigurations.ShadedNettyChannelFactoryConfiguration.class,
GrpcChannelFactoryConfigurations.NettyChannelFactoryConfiguration.class,
GrpcChannelFactoryConfigurations.DefaultChannelFactoryConfiguration.class })
GrpcChannelFactoryConfigurations.NettyChannelFactoryConfiguration.class })
public class GrpcClientAutoConfiguration {
@Bean

View File

@@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.grpc.server.GrpcServerFactory;
@@ -48,8 +49,7 @@ import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
@ConditionalOnBean(BindableService.class)
@EnableConfigurationProperties(GrpcServerProperties.class)
@Import({ GrpcServerFactoryConfigurations.ShadedNettyServerFactoryConfiguration.class,
GrpcServerFactoryConfigurations.NettyServerFactoryConfiguration.class,
GrpcServerFactoryConfigurations.ServiceProviderServerFactoryConfiguration.class })
GrpcServerFactoryConfigurations.NettyServerFactoryConfiguration.class })
public class GrpcServerAutoConfiguration {
private final GrpcServerProperties properties;
@@ -58,6 +58,7 @@ public class GrpcServerAutoConfiguration {
this.properties = properties;
}
@ConditionalOnBean(GrpcServerFactory.class)
@ConditionalOnMissingBean
@Bean
GrpcServerLifecycle grpcServerLifecycle(GrpcServerFactory factory, ApplicationEventPublisher eventPublisher) {

View File

@@ -18,7 +18,7 @@ package org.springframework.grpc.autoconfigure.server;
import java.util.List;
import javax.net.ssl.SSLException;
import javax.net.ssl.KeyManagerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -28,17 +28,13 @@ import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.grpc.server.DefaultGrpcServerFactory;
import org.springframework.grpc.server.GrpcServerFactory;
import org.springframework.grpc.server.NettyGrpcServerFactory;
import org.springframework.grpc.server.ServerBuilderCustomizer;
import org.springframework.grpc.server.ShadedNettyGrpcServerFactory;
import io.grpc.BindableService;
import io.grpc.ServerBuilder;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyServerBuilder;
import io.netty.handler.ssl.SslContextBuilder;
/**
* Configurations for {@link GrpcServerFactory gRPC server factories}.
@@ -55,38 +51,20 @@ class GrpcServerFactoryConfigurations {
@Bean
ShadedNettyGrpcServerFactory shadedNettyGrpcServerFactory(GrpcServerProperties properties,
ObjectProvider<BindableService> grpcServicesProvider,
ServerBuilderCustomizers serverBuilderCustomizers) {
ObjectProvider<BindableService> grpcServicesProvider, ServerBuilderCustomizers serverBuilderCustomizers,
SslBundles bundles) {
ShadedNettyServerFactoryPropertyMapper mapper = new ShadedNettyServerFactoryPropertyMapper(properties);
List<ServerBuilderCustomizer<io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder>> builderCustomizers = List
.of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize);
ShadedNettyGrpcServerFactory factory = new ShadedNettyGrpcServerFactory(properties.getAddress(),
properties.getPort(), builderCustomizers);
grpcServicesProvider.orderedStream().map(BindableService::bindService).forEach(factory::addService);
return factory;
}
@Bean
ServerBuilderCustomizer<io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder> sslServerCustomizer(
GrpcServerProperties properties, SslBundles bundles) {
KeyManagerFactory keyManager = null;
if (properties.getSsl().isEnabled()) {
SslBundle bundle = bundles.getBundle(properties.getSsl().getBundle());
return builder -> {
try {
builder.sslContext(io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts
.configure(io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder
.forServer(bundle.getManagers().getKeyManagerFactory()))
.build());
}
catch (SSLException e) {
throw new IllegalStateException("Failed to create SSL context", e);
}
};
}
else {
return builder -> {
};
keyManager = bundle.getManagers().getKeyManagerFactory();
}
ShadedNettyGrpcServerFactory factory = new ShadedNettyGrpcServerFactory(properties.getAddress(), keyManager,
builderCustomizers);
grpcServicesProvider.orderedStream().map(BindableService::bindService).forEach(factory::addService);
return factory;
}
}
@@ -99,56 +77,18 @@ class GrpcServerFactoryConfigurations {
@Bean
NettyGrpcServerFactory nettyGrpcServerFactory(GrpcServerProperties properties,
ObjectProvider<BindableService> grpcServicesProvider,
ServerBuilderCustomizers serverBuilderCustomizers) {
ObjectProvider<BindableService> grpcServicesProvider, ServerBuilderCustomizers serverBuilderCustomizers,
SslBundles bundles) {
NettyServerFactoryPropertyMapper mapper = new NettyServerFactoryPropertyMapper(properties);
List<ServerBuilderCustomizer<NettyServerBuilder>> builderCustomizers = List
.of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize);
NettyGrpcServerFactory factory = new NettyGrpcServerFactory(properties.getAddress(), properties.getPort(),
builderCustomizers);
grpcServicesProvider.orderedStream().map(BindableService::bindService).forEach(factory::addService);
return factory;
}
@Bean
ServerBuilderCustomizer<NettyServerBuilder> sslServerCustomizer(GrpcServerProperties properties,
SslBundles bundles) {
KeyManagerFactory keyManager = null;
if (properties.getSsl().isEnabled()) {
SslBundle bundle = bundles.getBundle(properties.getSsl().getBundle());
return builder -> {
try {
builder.sslContext(GrpcSslContexts
.configure(SslContextBuilder.forServer(bundle.getManagers().getKeyManagerFactory()))
.build());
}
catch (SSLException e) {
throw new IllegalStateException("Failed to create SSL context", e);
}
};
keyManager = bundle.getManagers().getKeyManagerFactory();
}
else {
return builder -> {
};
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ServerBuilder.class)
@ConditionalOnMissingBean(GrpcServerFactory.class)
@EnableConfigurationProperties(GrpcServerProperties.class)
static class ServiceProviderServerFactoryConfiguration {
@Bean
<T extends ServerBuilder<T>> DefaultGrpcServerFactory<T> serviceProviderGrpcServerFactory(
GrpcServerProperties properties, ObjectProvider<BindableService> grpcServicesProvider,
ServerBuilderCustomizers serverBuilderCustomizers) {
DefaultServerFactoryPropertyMapper<T> mapper = new DefaultServerFactoryPropertyMapper<>(properties);
List<ServerBuilderCustomizer<T>> builderCustomizers = List.of(mapper::customizeServerBuilder,
serverBuilderCustomizers::customize);
DefaultGrpcServerFactory<T> factory = new DefaultGrpcServerFactory<>(properties.getAddress(),
properties.getPort(), builderCustomizers);
NettyGrpcServerFactory factory = new NettyGrpcServerFactory(properties.getAddress(), keyManager,
builderCustomizers);
grpcServicesProvider.orderedStream().map(BindableService::bindService).forEach(factory::addService);
return factory;
}

View File

@@ -21,6 +21,7 @@ import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.grpc.internal.GrpcUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@@ -35,13 +36,13 @@ public class GrpcServerProperties {
/**
* Server address to bind to. The default is any IP address ('*').
*/
private String address = ANY_IP_ADDRESS;
private String host = ANY_IP_ADDRESS;
/**
* Server port to listen on. When the value is 0, a random available port is selected.
* The default is 9090.
*/
private int port = 9090;
private int port = GrpcUtils.DEFAULT_PORT;
/**
* Maximum time to wait for the server to gracefully shutdown. When the value is
@@ -65,19 +66,42 @@ public class GrpcServerProperties {
private final KeepAlive keepAlive = new KeepAlive();
/**
* The address to bind to. could be a host:port combination or a pseudo URL like
* static://host:port. Can not be set if host or port are set independently.
*/
private String address;
public String getAddress() {
return this.address;
return this.address == null ? this.host + ":" + this.port : this.address;
}
public void setAddress(String address) {
this.address = address;
}
public String getHost() {
return host;
}
public void setHost(String host) {
if (this.address != null) {
throw new IllegalStateException("Cannot set host when address is already set");
}
this.host = host;
}
public int getPort() {
if (this.address != null) {
return GrpcUtils.getPort(this.address);
}
return this.port;
}
public void setPort(int port) {
if (this.address != null) {
throw new IllegalStateException("Cannot set port when address is already set");
}
this.port = port;
}

View File

@@ -16,22 +16,23 @@
package org.springframework.grpc.autoconfigure.server;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.grpc.BindableService;
import io.grpc.ServerBuilder;
import io.grpc.ServerServiceDefinition;
import io.grpc.ServiceDescriptor;
import io.grpc.netty.NettyServerBuilder;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -39,18 +40,18 @@ 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.server.DefaultGrpcServerFactory;
import org.springframework.grpc.server.GrpcServerFactory;
import org.springframework.grpc.server.NettyGrpcServerFactory;
import org.springframework.grpc.server.ServerBuilderCustomizer;
import org.springframework.grpc.server.ShadedNettyGrpcServerFactory;
import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import io.grpc.BindableService;
import io.grpc.Grpc;
import io.grpc.ServerBuilder;
import io.grpc.ServerServiceDefinition;
import io.grpc.ServiceDescriptor;
import io.grpc.netty.NettyServerBuilder;
/**
* Tests for {@link GrpcServerAutoConfiguration}.
@@ -149,15 +150,6 @@ class GrpcServerAutoConfigurationTests {
.isInstanceOf(NettyGrpcServerFactory.class));
}
@Test
void whenNeitherShadedNorNonShadedNettyOnClasspathBaseServerFactoryIsAutoConfigured() {
this.contextRunner()
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(DefaultGrpcServerFactory.class));
}
@Test
void shadedNettyServerFactoryAutoConfiguredAsExpected() {
serverFactoryAutoConfiguredAsExpected(this.contextRunner(), ShadedNettyGrpcServerFactory.class);
@@ -171,21 +163,19 @@ class GrpcServerAutoConfigurationTests {
}
@Test
void baseServerFactoryAutoConfiguredAsExpected() {
serverFactoryAutoConfiguredAsExpected(
this.contextRunner()
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
DefaultGrpcServerFactory.class);
void noServerFactoryAutoConfiguredAsExpected() {
this.contextRunner()
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerFactory.class));
}
private void serverFactoryAutoConfiguredAsExpected(ApplicationContextRunner contextRunner,
Class<?> expectedServerFactoryType) {
contextRunner.withPropertyValues("spring.grpc.server.address=myhost", "spring.grpc.server.port=6160")
contextRunner.withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160")
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(expectedServerFactoryType)
.hasFieldOrPropertyWithValue("address", "myhost")
.hasFieldOrPropertyWithValue("port", 6160)
.hasFieldOrPropertyWithValue("address", "myhost:6160")
.extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class))
.singleElement()
.extracting(ServerServiceDefinition::getServiceDescriptor)
@@ -200,19 +190,16 @@ class GrpcServerAutoConfigurationTests {
ShadedNettyGrpcServerFactory.class);
}
@SuppressWarnings("rawtypes")
@Test
void nettyServerFactoryAutoConfiguredWithCustomizers() {
// FilteredClassLoader hides the class from the auto-configuration but not from
// the Java SPI
// used by ServerBuilder.forPort(int) which by default returns shaded Netty. This
// results in
// class cast exception when NettyGrpcServerFactory is expecting a non-shaded
// server builder.
// We static mock the builder to return non-shaded Netty - which would happen in
// the Java SPI used by ServerBuilder.forPort(int) which by default returns
// shaded Netty. This results in class cast exception when
// NettyGrpcServerFactory is expecting a non-shaded server builder. We static
// mock the builder to return non-shaded Netty - which would happen in
// real world.
try (MockedStatic<ServerBuilder> serverBuilderForPort = Mockito.mockStatic(ServerBuilder.class)) {
serverBuilderForPort.when(() -> ServerBuilder.forPort(anyInt()))
try (MockedStatic<Grpc> serverBuilderForPort = Mockito.mockStatic(Grpc.class)) {
serverBuilderForPort.when(() -> Grpc.newServerBuilderForPort(anyInt(), any()))
.thenAnswer((Answer<NettyServerBuilder>) invocation -> NettyServerBuilder
.forPort(invocation.getArgument(0)));
NettyServerBuilder builder = mock();
@@ -222,16 +209,6 @@ class GrpcServerAutoConfigurationTests {
}
}
@Test
<T extends ServerBuilder<T>> void baseServerFactoryAutoConfiguredWithCustomizers() {
ServerBuilder<T> builder = mock();
serverFactoryAutoConfiguredWithCustomizers(
this.contextRunnerWithLifecyle()
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
builder, DefaultGrpcServerFactory.class);
}
@SuppressWarnings("unchecked")
private <T extends ServerBuilder<T>> void serverFactoryAutoConfiguredWithCustomizers(
ApplicationContextRunner contextRunner, ServerBuilder<T> mockServerBuilder,
@@ -259,7 +236,9 @@ class GrpcServerAutoConfigurationTests {
serverFactoryAutoConfiguredAsExpected(
this.contextRunner()
.withPropertyValues("spring.grpc.server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks")
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks",
"spring.ssl.bundle.jks.ssltest.keystore.password=secret",
"spring.ssl.bundle.jks.ssltest.key.password=password")
.withClassLoader(
new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
NettyGrpcServerFactory.class);

View File

@@ -22,12 +22,13 @@ import java.util.Map;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
/**
* Tests for {@link GrpcServerProperties}.
@@ -48,11 +49,11 @@ class GrpcServerPropertiesTests {
@Test
void bind() {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.address", "my-server-ip");
map.put("spring.grpc.server.host", "my-server-ip");
map.put("spring.grpc.server.port", "3130");
map.put("spring.grpc.server.shutdown-grace-period", "15");
GrpcServerProperties properties = bindProperties(map);
assertThat(properties.getAddress()).isEqualTo("my-server-ip");
assertThat(properties.getAddress()).isEqualTo("my-server-ip:3130");
assertThat(properties.getPort()).isEqualTo(3130);
assertThat(properties.getShutdownGracePeriod()).isEqualTo(Duration.ofSeconds(15));
}
@@ -127,4 +128,26 @@ class GrpcServerPropertiesTests {
}
@Nested
class AddressProperties {
@Test
void bind() {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.address", "my-server-ip:3130");
GrpcServerProperties properties = bindProperties(map);
assertThat(properties.getAddress()).isEqualTo("my-server-ip:3130");
assertThat(properties.getPort()).isEqualTo(3130);
}
@Test
void illegalBecauseAddressAndPortSpecified() {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.address", "my-server-ip:3130");
map.put("spring.grpc.server.port", "10000");
assertThrows(BindException.class, () -> bindProperties(map));
}
}
}