From 417f4dd7fae8de8c1b5849a76904b0083e666a8e Mon Sep 17 00:00:00 2001 From: HaiTao Zhang Date: Fri, 28 Jun 2019 13:50:20 -0700 Subject: [PATCH] Allow Undertow's options to be configured via the environment See gh-17356 --- .../autoconfigure/web/ServerProperties.java | 23 +++++++++++ .../UndertowWebServerFactoryCustomizer.java | 40 +++++++++++++++++++ .../web/ServerPropertiesTests.java | 14 +++++++ ...dertowWebServerFactoryCustomizerTests.java | 20 ++++++++++ 4 files changed, 97 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 40ebb5b80f..8eebb50a24 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -24,6 +24,7 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -1119,6 +1120,8 @@ public class ServerProperties { private final Accesslog accesslog = new Accesslog(); + private final Options options = new Options(); + public DataSize getMaxHttpPostSize() { return this.maxHttpPostSize; } @@ -1227,6 +1230,10 @@ public class ServerProperties { return this.accesslog; } + public Options getOptions() { + return this.options; + } + /** * Undertow access log properties. */ @@ -1312,6 +1319,22 @@ public class ServerProperties { } + public static class Options { + + private Map socket = new LinkedHashMap<>(); + + private Map server = new LinkedHashMap<>(); + + public Map getServer() { + return this.server; + } + + public Map getSocket() { + return this.socket; + } + + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java index 0024b80cfc..4a6b6bdc0a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.web.embedded; +import java.lang.reflect.Field; + import io.undertow.UndertowOptions; import org.xnio.Option; @@ -61,6 +63,7 @@ public class UndertowWebServerFactoryCustomizer public void customize(ConfigurableUndertowWebServerFactory factory) { ServerProperties properties = this.serverProperties; ServerProperties.Undertow undertowProperties = properties.getUndertow(); + ServerProperties.Undertow.Options undertowOptions = undertowProperties.getOptions(); ServerProperties.Undertow.Accesslog accesslogProperties = undertowProperties.getAccesslog(); PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); propertyMapper.from(undertowProperties::getBufferSize).whenNonNull().asInt(DataSize::toBytes) @@ -109,6 +112,12 @@ public class UndertowWebServerFactoryCustomizer .to((alwaysSetKeepAlive) -> customizeServerOption(factory, UndertowOptions.ALWAYS_SET_KEEP_ALIVE, alwaysSetKeepAlive)); + propertyMapper.from(undertowOptions::getServer) + .to((server) -> server.forEach((key, value) -> setCustomOption(factory, key, value, "server"))); + + propertyMapper.from(undertowOptions::getSocket) + .to((socket) -> socket.forEach((key, value) -> setCustomOption(factory, key, value, "socket"))); + factory.addDeploymentInfoCustomizers( (deploymentInfo) -> deploymentInfo.setEagerFilterInit(undertowProperties.isEagerFilterInit())); } @@ -121,6 +130,10 @@ public class UndertowWebServerFactoryCustomizer factory.addBuilderCustomizers((builder) -> builder.setServerOption(option, value)); } + private void customizeSocketOption(ConfigurableUndertowWebServerFactory factory, Option option, T value) { + factory.addBuilderCustomizers((builder) -> builder.setSocketOption(option, value)); + } + private boolean getOrDeduceUseForwardHeaders() { if (this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NONE)) { CloudPlatform platform = CloudPlatform.getActive(this.environment); @@ -129,4 +142,31 @@ public class UndertowWebServerFactoryCustomizer return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE); } + private void setCustomOption(ConfigurableUndertowWebServerFactory factory, String key, String value, + String type) { + Field[] fields = UndertowOptions.class.getDeclaredFields(); + for (Field field : fields) { + String name = getLetterAndNumber(key); + if (getLetterAndNumber(field.getName()).equals(name)) { + Option option = (Option) Option.fromString( + UndertowOptions.class.getName() + '.' + field.getName(), getClass().getClassLoader()); + T parsed = option.parseValue(value, getClass().getClassLoader()); + if (type.equals("server")) { + customizeServerOption(factory, option, parsed); + } + else if (type.equals("socket")) { + customizeSocketOption(factory, option, parsed); + } + return; + } + } + } + + private String getLetterAndNumber(String name) { + StringBuilder canonicalName = new StringBuilder(name.length()); + name.chars().map((c) -> (char) c).filter(Character::isLetterOrDigit).map(Character::toLowerCase) + .forEach((c) -> canonicalName.append((char) c)); + return canonicalName.toString(); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index 08e5749c79..b1bc91973a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -214,6 +214,20 @@ class ServerPropertiesTests { assertThat(this.properties.getJetty().getSelectors()).isEqualTo(10); } + @Test + void testCustomizeUndertowServerOption() { + bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE", "true"); + assertThat(this.properties.getUndertow().getOptions().getServer().containsKey("ALWAYS_SET_KEEP_ALIVE")); + assertThat(this.properties.getUndertow().getOptions().getServer().get("ALWAYS_SET_KEEP_ALIVE").equals("true")); + } + + @Test + void testCustomizeUndertowSocketOption() { + bind("server.undertow.options.socket.ALWAYS_SET_KEEP_ALIVE", "true"); + assertThat(this.properties.getUndertow().getOptions().getSocket().containsKey("ALWAYS_SET_KEEP_ALIVE")); + assertThat(this.properties.getUndertow().getOptions().getSocket().get("ALWAYS_SET_KEEP_ALIVE").equals("true")); + } + @Test void testCustomizeJettyAccessLog() { Map map = new HashMap<>(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java index 3d19ccaa5d..f499b54a8f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java @@ -155,6 +155,18 @@ class UndertowWebServerFactoryCustomizerTests { assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse(); } + @Test + void customServerOption() { + bind("server.undertow.options.server.ALWAYS_SET_KEEP_ALIVE=false"); + assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse(); + } + + @Test + void customSocketOption() { + bind("server.undertow.options.socket.ALWAYS_SET_KEEP_ALIVE=false"); + assertThat(boundSocketOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse(); + } + @Test void deduceUseForwardHeaders() { this.environment.setProperty("DYNO", "-"); @@ -186,6 +198,14 @@ class UndertowWebServerFactoryCustomizerTests { return map.get(option); } + private T boundSocketOption(Option option) { + Builder builder = Undertow.builder(); + ConfigurableUndertowWebServerFactory factory = mockFactory(builder); + this.customizer.customize(factory); + OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder, "socketOptions")).getMap(); + return map.get(option); + } + private ConfigurableUndertowWebServerFactory mockFactory(Builder builder) { ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class); willAnswer((invocation) -> {