Add keep-alive and inbound limits server properties

Resolves #6
This commit is contained in:
Chris Bono
2024-10-05 23:26:43 -05:00
committed by Dave Syer
parent 1529658106
commit 9bcc3390cc
4 changed files with 215 additions and 20 deletions

View File

@@ -25,6 +25,7 @@ import io.grpc.ServerBuilder;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.grpc.server.DefaultGrpcServerFactory;
import org.springframework.util.unit.DataSize;
/**
* Helper class used to map {@link GrpcServerProperties} to
@@ -46,10 +47,39 @@ class DefaultServerFactoryPropertyMapper<T extends ServerBuilder<T>> {
* @param serverBuilder the builder
*/
void customizeServerBuilder(T serverBuilder) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
GrpcServerProperties.KeepAlive keepAlive = this.properties.getKeepAlive();
map.from(keepAlive.getTime()).to(durationProperty(serverBuilder::keepAliveTime));
map.from(keepAlive.getTimeout()).to(durationProperty(serverBuilder::keepAliveTimeout));
PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
customizeKeepAlive(serverBuilder, mapper);
customizeInboundLimits(serverBuilder, mapper);
}
/**
* Map the keep-alive properties to the server factory's server builder.
* @param serverBuilder the builder
* @param mapper the property mapper
*/
void customizeKeepAlive(T serverBuilder, PropertyMapper mapper) {
GrpcServerProperties.KeepAlive keepAliveProps = this.properties.getKeepAlive();
mapper.from(keepAliveProps.getTime()).to(durationProperty(serverBuilder::keepAliveTime));
mapper.from(keepAliveProps.getTimeout()).to(durationProperty(serverBuilder::keepAliveTimeout));
mapper.from(keepAliveProps.getMaxIdle()).to(durationProperty(serverBuilder::maxConnectionIdle));
mapper.from(keepAliveProps.getMaxAge()).to(durationProperty(serverBuilder::maxConnectionAge));
mapper.from(keepAliveProps.getMaxAgeGrace()).to(durationProperty(serverBuilder::maxConnectionAgeGrace));
mapper.from(keepAliveProps.getPermitTime()).to(durationProperty(serverBuilder::permitKeepAliveTime));
mapper.from(keepAliveProps.isPermitWithoutCalls()).to(serverBuilder::permitKeepAliveWithoutCalls);
}
/**
* Map the inbound limits properties to the server factory's server builder.
* @param serverBuilder the builder
* @param mapper the property mapper
*/
void customizeInboundLimits(T serverBuilder, PropertyMapper mapper) {
mapper.from(properties.getMaxInboundMessageSize())
.asInt(DataSize::toBytes)
.to(serverBuilder::maxInboundMessageSize);
mapper.from(properties.getMaxInboundMetadataSize())
.asInt(DataSize::toBytes)
.to(serverBuilder::maxInboundMetadataSize);
}
Consumer<Duration> durationProperty(BiConsumer<Long, TimeUnit> setter) {

View File

@@ -19,7 +19,10 @@ import java.time.Duration;
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.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties(prefix = "spring.grpc.server")
public class GrpcServerProperties {
@@ -29,16 +32,6 @@ public class GrpcServerProperties {
*/
public static final String ANY_IP_ADDRESS = "*";
/**
* Server should listen to any IPv4 address.
*/
public static final String ANY_IPv4_ADDRESS = "0.0.0.0";
/**
* Server should listen to any IPv6 address.
*/
public static final String ANY_IPv6_ADDRESS = "::";
/**
* Server address to bind to. The default is any IP address ('*').
*/
@@ -46,6 +39,7 @@ public class GrpcServerProperties {
/**
* Server port to listen on. When the value is 0, a random available port is selected.
* The default is 9090.
*/
private int port = 9090;
@@ -57,10 +51,22 @@ public class GrpcServerProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration shutdownGracePeriod = Duration.of(30, ChronoUnit.SECONDS);
/**
* Maximum message size allowed to be received by the server (default 4MiB).
*/
@DataSizeUnit(DataUnit.BYTES)
private DataSize maxInboundMessageSize = DataSize.ofBytes(4 * 1024 * 1024);
/**
* Maximum metadata size allowed to be received by the server (default 8KiB).
*/
@DataSizeUnit(DataUnit.BYTES)
private DataSize maxInboundMetadataSize = DataSize.ofBytes(8192);
private final KeepAlive keepAlive = new KeepAlive();
public String getAddress() {
return address;
return this.address;
}
public void setAddress(String address) {
@@ -68,7 +74,7 @@ public class GrpcServerProperties {
}
public int getPort() {
return port;
return this.port;
}
public void setPort(int port) {
@@ -76,13 +82,29 @@ public class GrpcServerProperties {
}
public Duration getShutdownGracePeriod() {
return shutdownGracePeriod;
return this.shutdownGracePeriod;
}
public void setShutdownGracePeriod(Duration shutdownGracePeriod) {
this.shutdownGracePeriod = shutdownGracePeriod;
}
public DataSize getMaxInboundMessageSize() {
return this.maxInboundMessageSize;
}
public void setMaxInboundMessageSize(DataSize maxInboundMessageSize) {
this.maxInboundMessageSize = maxInboundMessageSize;
}
public DataSize getMaxInboundMetadataSize() {
return this.maxInboundMetadataSize;
}
public void setMaxInboundMetadataSize(DataSize maxInboundMetadataSize) {
this.maxInboundMetadataSize = maxInboundMetadataSize;
}
public KeepAlive getKeepAlive() {
return this.keepAlive;
}
@@ -103,8 +125,40 @@ public class GrpcServerProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration timeout = Duration.of(20, ChronoUnit.SECONDS);
/**
* Maximum time a connection can remain idle before being gracefully terminated
* (default infinite).
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxIdle = null;
/**
* Maximum time a connection may exist before being gracefully terminated (default
* infinite).
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge = null;
/**
* Maximum time for graceful connection termination (default infinite).
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAgeGrace = null;
/**
* Maximum keep-alive time clients are permitted to configure (default 5m).
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration permitTime = Duration.of(5, ChronoUnit.MINUTES);
/**
* Whether clients are permitted to send keep alive pings when there are no
* outstanding RPCs on the connection (default false).
*/
private boolean permitWithoutCalls = false;
public Duration getTime() {
return time;
return this.time;
}
public void setTime(Duration time) {
@@ -112,13 +166,53 @@ public class GrpcServerProperties {
}
public Duration getTimeout() {
return timeout;
return this.timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
public Duration getMaxIdle() {
return this.maxIdle;
}
public void setMaxIdle(Duration maxIdle) {
this.maxIdle = maxIdle;
}
public Duration getMaxAge() {
return this.maxAge;
}
public void setMaxAge(Duration maxAge) {
this.maxAge = maxAge;
}
public Duration getMaxAgeGrace() {
return this.maxAgeGrace;
}
public void setMaxAgeGrace(Duration maxAgeGrace) {
this.maxAgeGrace = maxAgeGrace;
}
public Duration getPermitTime() {
return this.permitTime;
}
public void setPermitTime(Duration permitTime) {
this.permitTime = permitTime;
}
public boolean isPermitWithoutCalls() {
return this.permitWithoutCalls;
}
public void setPermitWithoutCalls(boolean permitWithoutCalls) {
this.permitWithoutCalls = permitWithoutCalls;
}
}
}

View File

@@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat;
@@ -49,10 +50,11 @@ class GrpcServerPropertiesTests {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.address", "my-server-ip");
map.put("spring.grpc.server.port", "3130");
map.put("spring.grpc.server.shutdown-grace-period", "15s");
map.put("spring.grpc.server.shutdown-grace-period", "15");
GrpcServerProperties properties = bindProperties(map);
assertThat(properties.getAddress()).isEqualTo("my-server-ip");
assertThat(properties.getPort()).isEqualTo(3130);
assertThat(properties.getShutdownGracePeriod()).isEqualTo(Duration.ofSeconds(15));
}
}
@@ -65,9 +67,62 @@ class GrpcServerPropertiesTests {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.keep-alive.time", "45m");
map.put("spring.grpc.server.keep-alive.timeout", "40s");
map.put("spring.grpc.server.keep-alive.max-idle", "1h");
map.put("spring.grpc.server.keep-alive.max-age", "3h");
map.put("spring.grpc.server.keep-alive.max-age-grace", "21s");
map.put("spring.grpc.server.keep-alive.permit-time", "33s");
map.put("spring.grpc.server.keep-alive.permit-without-calls", "true");
GrpcServerProperties.KeepAlive properties = bindProperties(map).getKeepAlive();
assertThatPropertiesSetAsExpected(properties);
}
@Test
void bindWithoutUnitsSpecified() {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.keep-alive.time", "2700");
map.put("spring.grpc.server.keep-alive.timeout", "40");
map.put("spring.grpc.server.keep-alive.max-idle", "3600");
map.put("spring.grpc.server.keep-alive.max-age", "10800");
map.put("spring.grpc.server.keep-alive.max-age-grace", "21");
map.put("spring.grpc.server.keep-alive.permit-time", "33");
map.put("spring.grpc.server.keep-alive.permit-without-calls", "true");
GrpcServerProperties.KeepAlive properties = bindProperties(map).getKeepAlive();
assertThatPropertiesSetAsExpected(properties);
}
private void assertThatPropertiesSetAsExpected(GrpcServerProperties.KeepAlive properties) {
assertThat(properties.getTime()).isEqualTo(Duration.ofMinutes(45));
assertThat(properties.getTimeout()).isEqualTo(Duration.ofSeconds(40));
assertThat(properties.getMaxIdle()).isEqualTo(Duration.ofHours(1));
assertThat(properties.getMaxAge()).isEqualTo(Duration.ofHours(3));
assertThat(properties.getMaxAgeGrace()).isEqualTo(Duration.ofSeconds(21));
assertThat(properties.getPermitTime()).isEqualTo(Duration.ofSeconds(33));
assertThat(properties.isPermitWithoutCalls()).isTrue();
}
}
@Nested
class InboundLimitsProperties {
@Test
void bind() {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.max-inbound-message-size", "20MB");
map.put("spring.grpc.server.max-inbound-metadata-size", "1MB");
GrpcServerProperties properties = bindProperties(map);
assertThat(properties.getMaxInboundMessageSize()).isEqualTo(DataSize.ofMegabytes(20));
assertThat(properties.getMaxInboundMetadataSize()).isEqualTo(DataSize.ofMegabytes(1));
}
@Test
void bindWithoutUnits() {
Map<String, String> map = new HashMap<>();
map.put("spring.grpc.server.max-inbound-message-size", "1048576");
map.put("spring.grpc.server.max-inbound-metadata-size", "1024");
GrpcServerProperties properties = bindProperties(map);
assertThat(properties.getMaxInboundMessageSize()).isEqualTo(DataSize.ofMegabytes(1));
assertThat(properties.getMaxInboundMetadataSize()).isEqualTo(DataSize.ofKilobytes(1));
}
}

View File

@@ -24,6 +24,8 @@ import java.util.function.Supplier;
import io.grpc.ServerBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.util.unit.DataSize;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
@@ -59,11 +61,25 @@ class ServerFactoryPropertyMappersTests {
GrpcServerProperties properties = new GrpcServerProperties();
properties.getKeepAlive().setTime(Duration.ofHours(1));
properties.getKeepAlive().setTimeout(Duration.ofSeconds(10));
properties.getKeepAlive().setMaxIdle(Duration.ofHours(2));
properties.getKeepAlive().setMaxAge(Duration.ofHours(3));
properties.getKeepAlive().setMaxAgeGrace(Duration.ofSeconds(45));
properties.getKeepAlive().setPermitTime(Duration.ofMinutes(7));
properties.getKeepAlive().setPermitWithoutCalls(true);
properties.setMaxInboundMessageSize(DataSize.ofMegabytes(333));
properties.setMaxInboundMetadataSize(DataSize.ofKilobytes(111));
X mapper = mapperFactory.apply(properties);
T builder = mockBuilderToCustomize.get();
mapper.customizeServerBuilder(builder);
then(builder).should().keepAliveTime(Duration.ofHours(1).toNanos(), TimeUnit.NANOSECONDS);
then(builder).should().keepAliveTimeout(Duration.ofSeconds(10).toNanos(), TimeUnit.NANOSECONDS);
then(builder).should().maxConnectionIdle(Duration.ofHours(2).toNanos(), TimeUnit.NANOSECONDS);
then(builder).should().maxConnectionAge(Duration.ofHours(3).toNanos(), TimeUnit.NANOSECONDS);
then(builder).should().maxConnectionAgeGrace(Duration.ofSeconds(45).toNanos(), TimeUnit.NANOSECONDS);
then(builder).should().permitKeepAliveTime(Duration.ofMinutes(7).toNanos(), TimeUnit.NANOSECONDS);
then(builder).should().permitKeepAliveWithoutCalls(true);
then(builder).should().maxInboundMessageSize(Math.toIntExact(DataSize.ofMegabytes(333).toBytes()));
then(builder).should().maxInboundMetadataSize(Math.toIntExact(DataSize.ofKilobytes(111).toBytes()));
}
}