Allow Undertow's options to be configured via the environment

See gh-17356
pull/17431/head
HaiTao Zhang 5 years ago committed by Madhura Bhave
parent f0e934e5ac
commit 417f4dd7fa

@ -24,6 +24,7 @@ import java.time.Duration;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -1119,6 +1120,8 @@ public class ServerProperties {
private final Accesslog accesslog = new Accesslog(); private final Accesslog accesslog = new Accesslog();
private final Options options = new Options();
public DataSize getMaxHttpPostSize() { public DataSize getMaxHttpPostSize() {
return this.maxHttpPostSize; return this.maxHttpPostSize;
} }
@ -1227,6 +1230,10 @@ public class ServerProperties {
return this.accesslog; return this.accesslog;
} }
public Options getOptions() {
return this.options;
}
/** /**
* Undertow access log properties. * Undertow access log properties.
*/ */
@ -1312,6 +1319,22 @@ public class ServerProperties {
} }
public static class Options {
private Map<String, String> socket = new LinkedHashMap<>();
private Map<String, String> server = new LinkedHashMap<>();
public Map<String, String> getServer() {
return this.server;
}
public Map<String, String> getSocket() {
return this.socket;
}
}
} }
/** /**

@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.web.embedded; package org.springframework.boot.autoconfigure.web.embedded;
import java.lang.reflect.Field;
import io.undertow.UndertowOptions; import io.undertow.UndertowOptions;
import org.xnio.Option; import org.xnio.Option;
@ -61,6 +63,7 @@ public class UndertowWebServerFactoryCustomizer
public void customize(ConfigurableUndertowWebServerFactory factory) { public void customize(ConfigurableUndertowWebServerFactory factory) {
ServerProperties properties = this.serverProperties; ServerProperties properties = this.serverProperties;
ServerProperties.Undertow undertowProperties = properties.getUndertow(); ServerProperties.Undertow undertowProperties = properties.getUndertow();
ServerProperties.Undertow.Options undertowOptions = undertowProperties.getOptions();
ServerProperties.Undertow.Accesslog accesslogProperties = undertowProperties.getAccesslog(); ServerProperties.Undertow.Accesslog accesslogProperties = undertowProperties.getAccesslog();
PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
propertyMapper.from(undertowProperties::getBufferSize).whenNonNull().asInt(DataSize::toBytes) propertyMapper.from(undertowProperties::getBufferSize).whenNonNull().asInt(DataSize::toBytes)
@ -109,6 +112,12 @@ public class UndertowWebServerFactoryCustomizer
.to((alwaysSetKeepAlive) -> customizeServerOption(factory, UndertowOptions.ALWAYS_SET_KEEP_ALIVE, .to((alwaysSetKeepAlive) -> customizeServerOption(factory, UndertowOptions.ALWAYS_SET_KEEP_ALIVE,
alwaysSetKeepAlive)); 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( factory.addDeploymentInfoCustomizers(
(deploymentInfo) -> deploymentInfo.setEagerFilterInit(undertowProperties.isEagerFilterInit())); (deploymentInfo) -> deploymentInfo.setEagerFilterInit(undertowProperties.isEagerFilterInit()));
} }
@ -121,6 +130,10 @@ public class UndertowWebServerFactoryCustomizer
factory.addBuilderCustomizers((builder) -> builder.setServerOption(option, value)); factory.addBuilderCustomizers((builder) -> builder.setServerOption(option, value));
} }
private <T> void customizeSocketOption(ConfigurableUndertowWebServerFactory factory, Option<T> option, T value) {
factory.addBuilderCustomizers((builder) -> builder.setSocketOption(option, value));
}
private boolean getOrDeduceUseForwardHeaders() { private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NONE)) { if (this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NONE)) {
CloudPlatform platform = CloudPlatform.getActive(this.environment); CloudPlatform platform = CloudPlatform.getActive(this.environment);
@ -129,4 +142,31 @@ public class UndertowWebServerFactoryCustomizer
return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE); return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
} }
private <T> 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<T> option = (Option<T>) 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();
}
} }

@ -214,6 +214,20 @@ class ServerPropertiesTests {
assertThat(this.properties.getJetty().getSelectors()).isEqualTo(10); 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 @Test
void testCustomizeJettyAccessLog() { void testCustomizeJettyAccessLog() {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();

@ -155,6 +155,18 @@ class UndertowWebServerFactoryCustomizerTests {
assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse(); 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 @Test
void deduceUseForwardHeaders() { void deduceUseForwardHeaders() {
this.environment.setProperty("DYNO", "-"); this.environment.setProperty("DYNO", "-");
@ -186,6 +198,14 @@ class UndertowWebServerFactoryCustomizerTests {
return map.get(option); return map.get(option);
} }
private <T> T boundSocketOption(Option<T> 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) { private ConfigurableUndertowWebServerFactory mockFactory(Builder builder) {
ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class); ConfigurableUndertowWebServerFactory factory = mock(ConfigurableUndertowWebServerFactory.class);
willAnswer((invocation) -> { willAnswer((invocation) -> {

Loading…
Cancel
Save