From 3cb6ce39124d81333f05bb016751e19a8bc4b6cc Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 18 Oct 2024 11:16:54 +0100 Subject: [PATCH] Move code around so factory gets address not port --- .../sample/GrpcServerIntegrationTests.java | 6 +- .../grpc/internal/GrpcUtils.java | 39 ++++++++ .../grpc/server/DefaultGrpcServerFactory.java | 36 +++++--- .../grpc/server/NettyGrpcServerFactory.java | 47 +++++----- .../server/ShadedNettyGrpcServerFactory.java | 47 +++++----- .../grpc/internal/GrpcUtilsTests.java | 43 +++++++++ .../modules/ROOT/partials/_configprops.adoc | 5 +- .../GrpcChannelFactoryConfigurations.java | 17 ---- .../client/GrpcClientAutoConfiguration.java | 3 +- .../server/GrpcServerAutoConfiguration.java | 5 +- .../GrpcServerFactoryConfigurations.java | 90 ++++--------------- .../server/GrpcServerProperties.java | 30 ++++++- .../GrpcServerAutoConfigurationTests.java | 79 ++++++---------- .../server/GrpcServerPropertiesTests.java | 29 +++++- 14 files changed, 262 insertions(+), 214 deletions(-) create mode 100644 spring-grpc-core/src/main/java/org/springframework/grpc/internal/GrpcUtils.java create mode 100644 spring-grpc-core/src/test/java/org/springframework/grpc/internal/GrpcUtilsTests.java diff --git a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java index cce6e9f..58176ee 100644 --- a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java +++ b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java @@ -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 diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/internal/GrpcUtils.java b/spring-grpc-core/src/main/java/org/springframework/grpc/internal/GrpcUtils.java new file mode 100644 index 0000000..2a32dac --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/internal/GrpcUtils.java @@ -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; + } + +} diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java index 2fb0e26..1a91ef0 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java @@ -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> implements Grp private final String address; - private final int port; - private final List> serverBuilderCustomizers; - public DefaultGrpcServerFactory(String address, int port, - List> serverBuilderCustomizers) { + public DefaultGrpcServerFactory(String address, List> 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> 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(); } /** diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java index fdae6a3..b5ca59d 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java @@ -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 { - private static final String ANY_IP_ADDRESS = "*"; + private KeyManagerFactory keyManager; - public NettyGrpcServerFactory(String address, int port, + public NettyGrpcServerFactory(String address, KeyManagerFactory keyManager, List> 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(); + } + } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java index a7e4930..a5265da 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java @@ -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 { - private static final String ANY_IP_ADDRESS = "*"; + private KeyManagerFactory keyManager; - public ShadedNettyGrpcServerFactory(String address, int port, + public ShadedNettyGrpcServerFactory(String address, KeyManagerFactory keyManager, List> 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(); + } + } diff --git a/spring-grpc-core/src/test/java/org/springframework/grpc/internal/GrpcUtilsTests.java b/spring-grpc-core/src/test/java/org/springframework/grpc/internal/GrpcUtilsTests.java new file mode 100644 index 0000000..08d196a --- /dev/null +++ b/spring-grpc-core/src/test/java/org/springframework/grpc/internal/GrpcUtilsTests.java @@ -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? + } + +} \ No newline at end of file diff --git a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc index dfa6a06..485ebc5 100644 --- a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc +++ b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc @@ -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. diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java index 3e40210..51ed5fe 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java @@ -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 configurers, - GrpcClientProperties channels) { - DefaultGrpcChannelFactory factory = new DefaultGrpcChannelFactory(configurers); - factory.setVirtualTargets(new NamedChannelVirtualTargets(channels)); - return factory; - } - - } - static class NamedChannelVirtualTargets implements VirtualTargets { private final GrpcClientProperties channels; diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java index cd3ae8c..e4b08d5 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java @@ -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 diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java index 7a547de..f1b9a20 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfiguration.java @@ -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) { diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java index 6337308..3bd6f3a 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java @@ -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 grpcServicesProvider, - ServerBuilderCustomizers serverBuilderCustomizers) { + ObjectProvider grpcServicesProvider, ServerBuilderCustomizers serverBuilderCustomizers, + SslBundles bundles) { ShadedNettyServerFactoryPropertyMapper mapper = new ShadedNettyServerFactoryPropertyMapper(properties); List> 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 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 grpcServicesProvider, - ServerBuilderCustomizers serverBuilderCustomizers) { + ObjectProvider grpcServicesProvider, ServerBuilderCustomizers serverBuilderCustomizers, + SslBundles bundles) { NettyServerFactoryPropertyMapper mapper = new NettyServerFactoryPropertyMapper(properties); List> 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 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 - > DefaultGrpcServerFactory serviceProviderGrpcServerFactory( - GrpcServerProperties properties, ObjectProvider grpcServicesProvider, - ServerBuilderCustomizers serverBuilderCustomizers) { - DefaultServerFactoryPropertyMapper mapper = new DefaultServerFactoryPropertyMapper<>(properties); - List> builderCustomizers = List.of(mapper::customizeServerBuilder, - serverBuilderCustomizers::customize); - DefaultGrpcServerFactory 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; } diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java index c0b0ca8..95acf0f 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java @@ -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; } diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java index 7928251..79df63b 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java @@ -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 serverBuilderForPort = Mockito.mockStatic(ServerBuilder.class)) { - serverBuilderForPort.when(() -> ServerBuilder.forPort(anyInt())) + try (MockedStatic serverBuilderForPort = Mockito.mockStatic(Grpc.class)) { + serverBuilderForPort.when(() -> Grpc.newServerBuilderForPort(anyInt(), any())) .thenAnswer((Answer) invocation -> NettyServerBuilder .forPort(invocation.getArgument(0))); NettyServerBuilder builder = mock(); @@ -222,16 +209,6 @@ class GrpcServerAutoConfigurationTests { } } - @Test - > void baseServerFactoryAutoConfiguredWithCustomizers() { - ServerBuilder 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 > void serverFactoryAutoConfiguredWithCustomizers( ApplicationContextRunner contextRunner, ServerBuilder 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); diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java index 38366f1..c732451 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java @@ -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 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 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 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)); + } + + } + }