This commit is contained in:
Phillip Webb
2019-07-17 21:38:44 +01:00
parent dad7fb4f6d
commit d567261790
6 changed files with 117 additions and 90 deletions

View File

@@ -54,6 +54,7 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Michael Simons
* @author Madhura Bhave
* @author HaiTao Zhang
*/
@ManagementContextConfiguration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)

View File

@@ -16,18 +16,29 @@
package org.springframework.boot.autoconfigure.web.embedded;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import io.undertow.UndertowOptions;
import org.xnio.Option;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties.Undertow;
import org.springframework.boot.autoconfigure.web.ServerProperties.Undertow.Accesslog;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.undertow.ConfigurableUndertowWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.unit.DataSize;
/**
@@ -40,6 +51,7 @@ import org.springframework.util.unit.DataSize;
* @author Phillip Webb
* @author Arstiom Yudovin
* @author Rafiullah Hamedy
* @author HaiTao Zhang
* @since 2.0.0
*/
public class UndertowWebServerFactoryCustomizer
@@ -61,77 +73,56 @@ public class UndertowWebServerFactoryCustomizer
@Override
public void customize(ConfigurableUndertowWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
FactoryOptions options = new FactoryOptions(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)
.to(factory::setBufferSize);
propertyMapper.from(undertowProperties::getIoThreads).to(factory::setIoThreads);
propertyMapper.from(undertowProperties::getWorkerThreads).to(factory::setWorkerThreads);
propertyMapper.from(undertowProperties::getDirectBuffers).to(factory::setUseDirectBuffers);
propertyMapper.from(accesslogProperties::isEnabled).to(factory::setAccessLogEnabled);
propertyMapper.from(accesslogProperties::getDir).to(factory::setAccessLogDirectory);
propertyMapper.from(accesslogProperties::getPattern).to(factory::setAccessLogPattern);
propertyMapper.from(accesslogProperties::getPrefix).to(factory::setAccessLogPrefix);
propertyMapper.from(accesslogProperties::getSuffix).to(factory::setAccessLogSuffix);
propertyMapper.from(accesslogProperties::isRotate).to(factory::setAccessLogRotate);
propertyMapper.from(this::getOrDeduceUseForwardHeaders).to(factory::setUseForwardHeaders);
map.from(properties::getMaxHttpHeaderSize).asInt(DataSize::toBytes).when(this::isPositive)
.to(options.server(UndertowOptions.MAX_HEADER_SIZE));
map.from(properties::getConnectionTimeout).asInt(Duration::toMillis)
.to(options.server(UndertowOptions.NO_REQUEST_TIMEOUT));
mapUndertowProperties(factory, options);
mapAccessLogProperties(factory);
map.from(this::getOrDeduceUseForwardHeaders).to(factory::setUseForwardHeaders);
}
propertyMapper.from(properties::getMaxHttpHeaderSize).whenNonNull().asInt(DataSize::toBytes)
.when(this::isPositive).to((maxHttpHeaderSize) -> customizeServerOption(factory,
UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize));
private void mapUndertowProperties(ConfigurableUndertowWebServerFactory factory, FactoryOptions options) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Undertow properties = this.serverProperties.getUndertow();
map.from(properties::getBufferSize).whenNonNull().asInt(DataSize::toBytes).to(factory::setBufferSize);
map.from(properties::getIoThreads).to(factory::setIoThreads);
map.from(properties::getWorkerThreads).to(factory::setWorkerThreads);
map.from(properties::getDirectBuffers).to(factory::setUseDirectBuffers);
map.from(properties::isEagerFilterInit).to((x) -> setEagerFilterInit(factory, x));
map.from(properties::getMaxHttpPostSize).as(DataSize::toBytes).when(this::isPositive)
.to(options.server(UndertowOptions.MAX_ENTITY_SIZE));
map.from(properties::getMaxParameters).to(options.server(UndertowOptions.MAX_PARAMETERS));
map.from(properties::getMaxHeaders).to(options.server(UndertowOptions.MAX_HEADERS));
map.from(properties::getMaxCookies).to(options.server(UndertowOptions.MAX_COOKIES));
map.from(properties::isAllowEncodedSlash).to(options.server(UndertowOptions.ALLOW_ENCODED_SLASH));
map.from(properties::isDecodeUrl).to(options.server(UndertowOptions.DECODE_URL));
map.from(properties::getUrlCharset).as(Charset::name).to(options.server(UndertowOptions.URL_CHARSET));
map.from(properties::isAlwaysSetKeepAlive).to(options.server(UndertowOptions.ALWAYS_SET_KEEP_ALIVE));
map.from(properties.getOptions()::getServer).to(options.forEach(options::server));
map.from(properties.getOptions()::getSocket).to(options.forEach(options::socket));
}
propertyMapper.from(undertowProperties::getMaxHttpPostSize).as(DataSize::toBytes).when(this::isPositive).to(
(maxHttpPostSize) -> customizeServerOption(factory, UndertowOptions.MAX_ENTITY_SIZE, maxHttpPostSize));
propertyMapper.from(properties::getConnectionTimeout).to((connectionTimeout) -> customizeServerOption(factory,
UndertowOptions.NO_REQUEST_TIMEOUT, (int) connectionTimeout.toMillis()));
propertyMapper.from(undertowProperties::getMaxParameters)
.to((maxParameters) -> customizeServerOption(factory, UndertowOptions.MAX_PARAMETERS, maxParameters));
propertyMapper.from(undertowProperties::getMaxHeaders)
.to((maxHeaders) -> customizeServerOption(factory, UndertowOptions.MAX_HEADERS, maxHeaders));
propertyMapper.from(undertowProperties::getMaxCookies)
.to((maxCookies) -> customizeServerOption(factory, UndertowOptions.MAX_COOKIES, maxCookies));
propertyMapper.from(undertowProperties::isAllowEncodedSlash)
.to((allowEncodedSlash) -> customizeServerOption(factory, UndertowOptions.ALLOW_ENCODED_SLASH,
allowEncodedSlash));
propertyMapper.from(undertowProperties::isDecodeUrl)
.to((isDecodeUrl) -> customizeServerOption(factory, UndertowOptions.DECODE_URL, isDecodeUrl));
propertyMapper.from(undertowProperties::getUrlCharset)
.to((urlCharset) -> customizeServerOption(factory, UndertowOptions.URL_CHARSET, urlCharset.name()));
propertyMapper.from(undertowProperties::isAlwaysSetKeepAlive)
.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()));
private void setEagerFilterInit(ConfigurableUndertowWebServerFactory factory, Boolean eagerFilterInit) {
factory.addDeploymentInfoCustomizers((deploymentInfo) -> deploymentInfo.setEagerFilterInit(eagerFilterInit));
}
private boolean isPositive(Number value) {
return value.longValue() > 0;
}
private <T> void customizeServerOption(ConfigurableUndertowWebServerFactory factory, Option<T> option, T 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 void mapAccessLogProperties(ConfigurableUndertowWebServerFactory factory) {
Accesslog properties = this.serverProperties.getUndertow().getAccesslog();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::isEnabled).to(factory::setAccessLogEnabled);
map.from(properties::getDir).to(factory::setAccessLogDirectory);
map.from(properties::getPattern).to(factory::setAccessLogPattern);
map.from(properties::getPrefix).to(factory::setAccessLogPrefix);
map.from(properties::getSuffix).to(factory::setAccessLogSuffix);
map.from(properties::isRotate).to(factory::setAccessLogRotate);
}
private boolean getOrDeduceUseForwardHeaders() {
@@ -142,31 +133,63 @@ public class UndertowWebServerFactoryCustomizer
return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
@SuppressWarnings("unchecked")
private <T> void setCustomOption(ConfigurableUndertowWebServerFactory factory, String key, String value,
String type) {
Field[] fields = UndertowOptions.class.getDeclaredFields();
for (Field field : fields) {
if (getCanonicalName(field.getName()).equals(getCanonicalName(key))) {
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;
}
}
}
/**
* {@link ConfigurableUndertowWebServerFactory} wrapper that makes it easier to apply
* {@link UndertowOptions}.
*/
private static class FactoryOptions {
private static final Map<String, Option<?>> NAME_LOOKUP;
static {
Map<String, Option<?>> lookup = new HashMap<>();
ReflectionUtils.doWithLocalFields(UndertowOptions.class, (field) -> {
int modifiers = field.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)
&& Option.class.isAssignableFrom(field.getType())) {
try {
Option<?> option = (Option<?>) field.get(null);
lookup.put(getCanonicalName(field.getName()), option);
}
catch (IllegalAccessException ex) {
}
}
});
NAME_LOOKUP = Collections.unmodifiableMap(lookup);
}
private final ConfigurableUndertowWebServerFactory factory;
FactoryOptions(ConfigurableUndertowWebServerFactory factory) {
this.factory = factory;
}
<T> Consumer<T> server(Option<T> option) {
return (value) -> this.factory.addBuilderCustomizers((builder) -> builder.setServerOption(option, value));
}
<T> Consumer<T> socket(Option<T> option) {
return (value) -> this.factory.addBuilderCustomizers((builder) -> builder.setSocketOption(option, value));
}
@SuppressWarnings("unchecked")
<T> Consumer<Map<String, String>> forEach(Function<Option<T>, Consumer<T>> function) {
return (map) -> {
map.forEach((key, value) -> {
Option<T> option = (Option<T>) NAME_LOOKUP.get(getCanonicalName(key));
Assert.state(option != null, "Unable to find '" + key + "' in UndertowOptions");
T parsed = option.parseValue(value, getClass().getClassLoader());
function.apply(option).accept(parsed);
});
};
}
private static String getCanonicalName(String name) {
StringBuilder canonicalName = new StringBuilder(name.length());
name.chars().filter(Character::isLetterOrDigit).map(Character::toLowerCase)
.forEach((c) -> canonicalName.append((char) c));
return canonicalName.toString();
}
private String getCanonicalName(String key) {
StringBuilder canonicalName = new StringBuilder(key.length());
key.chars().map((c) -> (char) c).filter(Character::isLetterOrDigit).map(Character::toLowerCase)
.forEach((c) -> canonicalName.append((char) c));
return canonicalName.toString();
}
}

View File

@@ -51,6 +51,7 @@ import static org.mockito.Mockito.verify;
* @author Phillip Webb
* @author Artsiom Yudovin
* @author Rafiullah Hamedy
* @author HaiTao Zhang
*/
class UndertowWebServerFactoryCustomizerTests {

View File

@@ -87,9 +87,9 @@ final class LenientStringToEnumConverterFactory implements ConverterFactory<Stri
private T findEnum(String source) {
Map<String, T> candidates = new LinkedHashMap<String, T>();
for (T candidate : (Set<T>) EnumSet.allOf(this.enumType)) {
candidates.put(getLettersAndDigits(candidate.name()), candidate);
candidates.put(getCanonicalName(candidate.name()), candidate);
}
String name = getLettersAndDigits(source);
String name = getCanonicalName(source);
T result = candidates.get(name);
if (result != null) {
return result;
@@ -103,7 +103,7 @@ final class LenientStringToEnumConverterFactory implements ConverterFactory<Stri
throw new IllegalArgumentException("No enum constant " + this.enumType.getCanonicalName() + "." + source);
}
private String getLettersAndDigits(String name) {
private String getCanonicalName(String name) {
StringBuilder canonicalName = new StringBuilder(name.length());
name.chars().filter(Character::isLetterOrDigit).map(Character::toLowerCase)
.forEach((c) -> canonicalName.append((char) c));

View File

@@ -55,6 +55,7 @@ import org.springframework.util.StringUtils;
* {@link ReactiveWebServerFactory} that can be used to create a {@link TomcatWebServer}.
*
* @author Brian Clozel
* @author HaiTao Zhang
* @since 2.0.0
*/
public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory

View File

@@ -56,6 +56,7 @@ import static org.mockito.Mockito.verify;
*
* @author Brian Clozel
* @author Madhura Bhave
* @author HaiTao Zhang
*/
class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactoryTests {