Skip to content

Commit ca1a666

Browse files
committed
Polish "Add support for configuring remaining Undertow server options"
See gh-16278
1 parent 186b1fa commit ca1a666

File tree

3 files changed

+121
-115
lines changed

3 files changed

+121
-115
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.util.Map;
3030
import java.util.TimeZone;
3131

32+
import io.undertow.UndertowOptions;
33+
3234
import org.springframework.boot.context.properties.ConfigurationProperties;
3335
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
3436
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@@ -1117,47 +1119,47 @@ public static class Undertow {
11171119
private boolean eagerFilterInit = true;
11181120

11191121
/**
1120-
* The maximum number of query or path parameters that are allowed. This limit
1121-
* exists to prevent hash collision based DOS attacks.
1122+
* Maximum number of query or path parameters that are allowed. This limit exists
1123+
* to prevent hash collision based DOS attacks.
11221124
*/
1123-
private Integer maxParameters;
1125+
private int maxParameters = UndertowOptions.DEFAULT_MAX_PARAMETERS;
11241126

11251127
/**
1126-
* The maximum number of headers that are allowed. This limit exists to prevent
1127-
* hash collision based DOS attacks.
1128+
* Maximum number of headers that are allowed. This limit exists to prevent hash
1129+
* collision based DOS attacks.
11281130
*/
1129-
private Integer maxHeaders;
1131+
private int maxHeaders = UndertowOptions.DEFAULT_MAX_HEADERS;
11301132

11311133
/**
1132-
* The maximum number of cookies that are allowed. This limit exists to prevent
1133-
* hash collision based DOS attacks.
1134+
* Maximum number of cookies that are allowed. This limit exists to prevent hash
1135+
* collision based DOS attacks.
11341136
*/
1135-
private Integer maxCookies;
1137+
private int maxCookies = 200;
11361138

11371139
/**
1138-
* Set this to true if you want the server to decode percent encoded slash
1139-
* characters. This is probably a bad idea, as it can have security implications,
1140-
* due to different servers interpreting the slash differently. Only enable this
1141-
* if you have a legacy application that requires it.
1140+
* Whether the server should decode percent encoded slash characters. Enabling
1141+
* encoded slashes can have security implications due to different servers
1142+
* interpreting the slash differently. Only enable this if you have a legacy
1143+
* application that requires it.
11421144
*/
1143-
private Boolean allowEncodedSlash;
1145+
private boolean allowEncodedSlash = false;
11441146

11451147
/**
1146-
* If the URL should be decoded. If this is not set to true then percent encoded
1147-
* characters in the URL will be left as is.
1148+
* Whether the URL should be decoded. When disabled, percent-encoded characters in
1149+
* the URL will be left as-is.
11481150
*/
1149-
private Boolean decodeUrl;
1151+
private boolean decodeUrl = true;
11501152

11511153
/**
1152-
* The charset to decode the URL to.
1154+
* Charset used to decode URLs.
11531155
*/
1154-
private String urlCharset;
1156+
private Charset urlCharset = StandardCharsets.UTF_8;
11551157

11561158
/**
1157-
* If the 'Connection: keep-alive' header should be added to all responses, even
1158-
* if not required by spec.
1159+
* Whether the 'Connection: keep-alive' header should be added to all responses,
1160+
* even if not required by the HTTP specification.
11591161
*/
1160-
private Boolean alwaysSetKeepAlive;
1162+
private boolean alwaysSetKeepAlive = true;
11611163

11621164
private final Accesslog accesslog = new Accesslog();
11631165

@@ -1209,19 +1211,19 @@ public void setEagerFilterInit(boolean eagerFilterInit) {
12091211
this.eagerFilterInit = eagerFilterInit;
12101212
}
12111213

1212-
public Integer getMaxParameters() {
1214+
public int getMaxParameters() {
12131215
return this.maxParameters;
12141216
}
12151217

12161218
public void setMaxParameters(Integer maxParameters) {
12171219
this.maxParameters = maxParameters;
12181220
}
12191221

1220-
public Integer getMaxHeaders() {
1222+
public int getMaxHeaders() {
12211223
return this.maxHeaders;
12221224
}
12231225

1224-
public void setMaxHeaders(Integer maxHeaders) {
1226+
public void setMaxHeaders(int maxHeaders) {
12251227
this.maxHeaders = maxHeaders;
12261228
}
12271229

@@ -1233,35 +1235,35 @@ public void setMaxCookies(Integer maxCookies) {
12331235
this.maxCookies = maxCookies;
12341236
}
12351237

1236-
public Boolean isAllowEncodedSlash() {
1238+
public boolean isAllowEncodedSlash() {
12371239
return this.allowEncodedSlash;
12381240
}
12391241

1240-
public void setAllowEncodedSlash(Boolean allowEncodedSlash) {
1242+
public void setAllowEncodedSlash(boolean allowEncodedSlash) {
12411243
this.allowEncodedSlash = allowEncodedSlash;
12421244
}
12431245

1244-
public Boolean isDecodeUrl() {
1246+
public boolean isDecodeUrl() {
12451247
return this.decodeUrl;
12461248
}
12471249

12481250
public void setDecodeUrl(Boolean decodeUrl) {
12491251
this.decodeUrl = decodeUrl;
12501252
}
12511253

1252-
public String getUrlCharset() {
1254+
public Charset getUrlCharset() {
12531255
return this.urlCharset;
12541256
}
12551257

1256-
public void setUrlCharset(String urlCharset) {
1258+
public void setUrlCharset(Charset urlCharset) {
12571259
this.urlCharset = urlCharset;
12581260
}
12591261

1260-
public Boolean isAlwaysSetKeepAlive() {
1262+
public boolean isAlwaysSetKeepAlive() {
12611263
return this.alwaysSetKeepAlive;
12621264
}
12631265

1264-
public void setAlwaysSetKeepAlive(Boolean alwaysSetKeepAlive) {
1266+
public void setAlwaysSetKeepAlive(boolean alwaysSetKeepAlive) {
12651267
this.alwaysSetKeepAlive = alwaysSetKeepAlive;
12661268
}
12671269

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizer.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,45 +89,45 @@ public void customize(ConfigurableUndertowWebServerFactory factory) {
8989

9090
propertyMapper.from(properties::getMaxHttpHeaderSize).whenNonNull()
9191
.asInt(DataSize::toBytes).when(this::isPositive)
92-
.to((maxHttpHeaderSize) -> customizeProperties(factory,
92+
.to((maxHttpHeaderSize) -> customizeServerOption(factory,
9393
UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize));
9494

95-
propertyMapper.from(undertowProperties::getMaxHttpPostSize)
96-
.asInt(DataSize::toBytes).when(this::isPositive)
97-
.to((maxHttpPostSize) -> customizeProperties(factory,
98-
UndertowOptions.MAX_HEADER_SIZE, maxHttpPostSize));
95+
propertyMapper.from(undertowProperties::getMaxHttpPostSize).as(DataSize::toBytes)
96+
.when(this::isPositive)
97+
.to((maxHttpPostSize) -> customizeServerOption(factory,
98+
UndertowOptions.MAX_ENTITY_SIZE, maxHttpPostSize));
9999

100100
propertyMapper.from(properties::getConnectionTimeout)
101-
.to((connectionTimeout) -> customizeProperties(factory,
101+
.to((connectionTimeout) -> customizeServerOption(factory,
102102
UndertowOptions.NO_REQUEST_TIMEOUT,
103103
(int) connectionTimeout.toMillis()));
104104

105105
propertyMapper.from(undertowProperties::getMaxParameters)
106-
.to((maxParameters) -> customizeProperties(factory,
106+
.to((maxParameters) -> customizeServerOption(factory,
107107
UndertowOptions.MAX_PARAMETERS, maxParameters));
108108

109109
propertyMapper.from(undertowProperties::getMaxHeaders)
110-
.to((maxHeaders) -> customizeProperties(factory,
110+
.to((maxHeaders) -> customizeServerOption(factory,
111111
UndertowOptions.MAX_HEADERS, maxHeaders));
112112

113113
propertyMapper.from(undertowProperties::getMaxCookies)
114-
.to((maxCookies) -> customizeProperties(factory,
114+
.to((maxCookies) -> customizeServerOption(factory,
115115
UndertowOptions.MAX_COOKIES, maxCookies));
116116

117117
propertyMapper.from(undertowProperties::isAllowEncodedSlash)
118-
.to((allowEncodedSlash) -> customizeProperties(factory,
118+
.to((allowEncodedSlash) -> customizeServerOption(factory,
119119
UndertowOptions.ALLOW_ENCODED_SLASH, allowEncodedSlash));
120120

121121
propertyMapper.from(undertowProperties::isDecodeUrl)
122-
.to((isDecodeUrl) -> customizeProperties(factory,
122+
.to((isDecodeUrl) -> customizeServerOption(factory,
123123
UndertowOptions.DECODE_URL, isDecodeUrl));
124124

125125
propertyMapper.from(undertowProperties::getUrlCharset)
126-
.to((urlCharset) -> customizeProperties(factory,
127-
UndertowOptions.URL_CHARSET, urlCharset));
126+
.to((urlCharset) -> customizeServerOption(factory,
127+
UndertowOptions.URL_CHARSET, urlCharset.name()));
128128

129129
propertyMapper.from(undertowProperties::isAlwaysSetKeepAlive)
130-
.to((alwaysSetKeepAlive) -> customizeProperties(factory,
130+
.to((alwaysSetKeepAlive) -> customizeServerOption(factory,
131131
UndertowOptions.ALWAYS_SET_KEEP_ALIVE, alwaysSetKeepAlive));
132132

133133
factory.addDeploymentInfoCustomizers((deploymentInfo) -> deploymentInfo
@@ -138,10 +138,10 @@ private boolean isPositive(Number value) {
138138
return value.longValue() > 0;
139139
}
140140

141-
private <T> void customizeProperties(ConfigurableUndertowWebServerFactory factory,
142-
Option<T> propType, T prop) {
141+
private <T> void customizeServerOption(ConfigurableUndertowWebServerFactory factory,
142+
Option<T> option, T value) {
143143
factory.addBuilderCustomizers(
144-
(builder) -> builder.setServerOption(propType, prop));
144+
(builder) -> builder.setServerOption(option, value));
145145
}
146146

147147
private boolean getOrDeduceUseForwardHeaders() {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/embedded/UndertowWebServerFactoryCustomizerTests.java

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
package org.springframework.boot.autoconfigure.web.embedded;
1818

1919
import java.io.File;
20+
import java.nio.charset.StandardCharsets;
2021
import java.util.Arrays;
2122

2223
import io.undertow.Undertow;
2324
import io.undertow.Undertow.Builder;
2425
import io.undertow.UndertowOptions;
2526
import org.junit.Before;
2627
import org.junit.Test;
28+
import org.xnio.Option;
2729
import org.xnio.OptionMap;
2830

2931
import org.springframework.boot.autoconfigure.web.ServerProperties;
@@ -88,37 +90,75 @@ public void customizeUndertowAccessLog() {
8890
}
8991

9092
@Test
91-
public void customizeUndertowConnectionCommonSettings() {
92-
bind("server.undertow.maxParameters=50", "server.undertow.maxHeaders=60",
93-
"server.undertow.maxCookies=70", "server.undertow.allowEncodedSlash=true",
94-
"server.undertow.decodeUrl=true", "server.undertow.urlCharset=UTF-8",
95-
"server.undertow.alwaysSetKeepAlive=true");
96-
Builder builder = Undertow.builder();
97-
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
98-
this.customizer.customize(factory);
99-
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
100-
"serverOptions")).getMap();
101-
assertThat(map.get(UndertowOptions.MAX_PARAMETERS)).isEqualTo(50);
102-
assertThat(map.get(UndertowOptions.MAX_HEADERS)).isEqualTo(60);
103-
assertThat(map.get(UndertowOptions.MAX_COOKIES)).isEqualTo(70);
104-
assertThat(map.get(UndertowOptions.ALLOW_ENCODED_SLASH)).isTrue();
105-
assertThat(map.get(UndertowOptions.DECODE_URL)).isTrue();
106-
assertThat(map.get(UndertowOptions.URL_CHARSET)).isEqualTo("UTF-8");
107-
assertThat(map.get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isTrue();
93+
public void customMaxHttpHeaderSize() {
94+
bind("server.max-http-header-size=2048");
95+
assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isEqualTo(2048);
10896
}
10997

11098
@Test
111-
public void customizeUndertowCommonConnectionCommonBoolSettings() {
112-
bind("server.undertow.allowEncodedSlash=false", "server.undertow.decodeUrl=false",
113-
"server.undertow.alwaysSetKeepAlive=false");
114-
Builder builder = Undertow.builder();
115-
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
116-
this.customizer.customize(factory);
117-
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
118-
"serverOptions")).getMap();
119-
assertThat(map.get(UndertowOptions.ALLOW_ENCODED_SLASH)).isFalse();
120-
assertThat(map.get(UndertowOptions.DECODE_URL)).isFalse();
121-
assertThat(map.get(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse();
99+
public void customMaxHttpHeaderSizeIgnoredIfNegative() {
100+
bind("server.max-http-header-size=-1");
101+
assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull();
102+
}
103+
104+
public void customMaxHttpHeaderSizeIgnoredIfZero() {
105+
bind("server.max-http-header-size=0");
106+
assertThat(boundServerOption(UndertowOptions.MAX_HEADER_SIZE)).isNull();
107+
}
108+
109+
@Test
110+
public void customMaxHttpPostSize() {
111+
bind("server.undertow.max-http-post-size=256");
112+
assertThat(boundServerOption(UndertowOptions.MAX_ENTITY_SIZE)).isEqualTo(256);
113+
}
114+
115+
@Test
116+
public void customConnectionTimeout() {
117+
bind("server.connectionTimeout=100");
118+
assertThat(boundServerOption(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(100);
119+
}
120+
121+
@Test
122+
public void customMaxParameters() {
123+
bind("server.undertow.max-parameters=4");
124+
assertThat(boundServerOption(UndertowOptions.MAX_PARAMETERS)).isEqualTo(4);
125+
}
126+
127+
@Test
128+
public void customMaxHeaders() {
129+
bind("server.undertow.max-headers=4");
130+
assertThat(boundServerOption(UndertowOptions.MAX_HEADERS)).isEqualTo(4);
131+
}
132+
133+
@Test
134+
public void customMaxCookies() {
135+
bind("server.undertow.max-cookies=4");
136+
assertThat(boundServerOption(UndertowOptions.MAX_COOKIES)).isEqualTo(4);
137+
}
138+
139+
@Test
140+
public void allowEncodedSlashes() {
141+
bind("server.undertow.allow-encoded-slash=true");
142+
assertThat(boundServerOption(UndertowOptions.ALLOW_ENCODED_SLASH)).isTrue();
143+
}
144+
145+
@Test
146+
public void disableUrlDecoding() {
147+
bind("server.undertow.decode-url=false");
148+
assertThat(boundServerOption(UndertowOptions.DECODE_URL)).isFalse();
149+
}
150+
151+
@Test
152+
public void customUrlCharset() {
153+
bind("server.undertow.url-charset=UTF-16");
154+
assertThat(boundServerOption(UndertowOptions.URL_CHARSET))
155+
.isEqualTo(StandardCharsets.UTF_16.name());
156+
}
157+
158+
@Test
159+
public void disableAlwaysSetKeepAlive() {
160+
bind("server.undertow.always-set-keep-alive=false");
161+
assertThat(boundServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE)).isFalse();
122162
}
123163

124164
@Test
@@ -147,49 +187,13 @@ public void setUseForwardHeaders() {
147187
verify(factory).setUseForwardHeaders(true);
148188
}
149189

150-
@Test
151-
public void customizeMaxHttpHeaderSize() {
152-
bind("server.max-http-header-size=2048");
153-
Builder builder = Undertow.builder();
154-
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
155-
this.customizer.customize(factory);
156-
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
157-
"serverOptions")).getMap();
158-
assertThat(map.get(UndertowOptions.MAX_HEADER_SIZE).intValue()).isEqualTo(2048);
159-
}
160-
161-
@Test
162-
public void customMaxHttpHeaderSizeIgnoredIfNegative() {
163-
bind("server.max-http-header-size=-1");
164-
Builder builder = Undertow.builder();
165-
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
166-
this.customizer.customize(factory);
167-
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
168-
"serverOptions")).getMap();
169-
assertThat(map.contains(UndertowOptions.MAX_HEADER_SIZE)).isFalse();
170-
}
171-
172-
@Test
173-
public void customMaxHttpHeaderSizeIgnoredIfZero() {
174-
bind("server.max-http-header-size=0");
175-
Builder builder = Undertow.builder();
176-
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
177-
this.customizer.customize(factory);
178-
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
179-
"serverOptions")).getMap();
180-
assertThat(map.contains(UndertowOptions.MAX_HEADER_SIZE)).isFalse();
181-
}
182-
183-
@Test
184-
public void customConnectionTimeout() {
185-
bind("server.connection-timeout=100");
190+
private <T> T boundServerOption(Option<T> option) {
186191
Builder builder = Undertow.builder();
187192
ConfigurableUndertowWebServerFactory factory = mockFactory(builder);
188193
this.customizer.customize(factory);
189194
OptionMap map = ((OptionMap.Builder) ReflectionTestUtils.getField(builder,
190195
"serverOptions")).getMap();
191-
assertThat(map.contains(UndertowOptions.NO_REQUEST_TIMEOUT)).isTrue();
192-
assertThat(map.get(UndertowOptions.NO_REQUEST_TIMEOUT)).isEqualTo(100);
196+
return map.get(option);
193197
}
194198

195199
private ConfigurableUndertowWebServerFactory mockFactory(Builder builder) {

0 commit comments

Comments
 (0)