Move servlet http encoding auto-configuration into spring-boot-servlet

This commit is contained in:
Andy Wilkinson
2025-06-05 11:25:42 +01:00
committed by Phillip Webb
parent d0e7766d72
commit 8efbebb9b3
12 changed files with 9 additions and 49 deletions

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in Servlet web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
@AutoConfiguration
@EnableConfigurationProperties(ServletEncodingProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnBooleanProperty(name = "spring.servlet.encoding.enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter(ServletEncodingProperties properties) {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(properties.getCharset().name());
filter.setForceRequestEncoding(properties.shouldForce(ServletEncodingProperties.HttpMessageType.REQUEST));
filter.setForceResponseEncoding(properties.shouldForce(ServletEncodingProperties.HttpMessageType.RESPONSE));
return filter;
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.autoconfigure;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* {@link ConfigurationProperties @ConfigurationProperties} for Servlet encoding.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@ConfigurationProperties("spring.servlet.encoding")
public class ServletEncodingProperties {
/**
* Default HTTP encoding for Servlet applications.
*/
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* Charset of HTTP requests and responses. Added to the "Content-Type" header if not
* set explicitly.
*/
private Charset charset = DEFAULT_CHARSET;
/**
* Whether to force the encoding to the configured charset on HTTP requests and
* responses.
*/
private Boolean force;
/**
* Whether to force the encoding to the configured charset on HTTP requests. Defaults
* to true when "force" has not been specified.
*/
private Boolean forceRequest;
/**
* Whether to force the encoding to the configured charset on HTTP responses.
*/
private Boolean forceResponse;
public Charset getCharset() {
return this.charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return Boolean.TRUE.equals(this.force);
}
public void setForce(boolean force) {
this.force = force;
}
public boolean isForceRequest() {
return Boolean.TRUE.equals(this.forceRequest);
}
public void setForceRequest(boolean forceRequest) {
this.forceRequest = forceRequest;
}
public boolean isForceResponse() {
return Boolean.TRUE.equals(this.forceResponse);
}
public void setForceResponse(boolean forceResponse) {
this.forceResponse = forceResponse;
}
public boolean shouldForce(HttpMessageType type) {
Boolean force = (type != HttpMessageType.REQUEST) ? this.forceResponse : this.forceRequest;
if (force == null) {
force = this.force;
}
if (force == null) {
force = (type == HttpMessageType.REQUEST);
}
return force;
}
/**
* Type of HTTP message to consider for encoding configuration.
*/
public enum HttpMessageType {
/**
* HTTP request message.
*/
REQUEST,
/**
* HTTP response message.
*/
RESPONSE
}
}

View File

@@ -0,0 +1,79 @@
{
"groups": [],
"properties": [
{
"name": "spring.http.encoding.charset",
"type": "java.nio.charset.Charset",
"description": "Charset of HTTP requests and responses. Added to the Content-Type header if not set explicitly.",
"deprecation": {
"replacement": "server.servlet.encoding.charset",
"level": "error"
}
},
{
"name": "spring.http.encoding.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable http encoding support.",
"defaultValue": true,
"deprecation": {
"replacement": "server.servlet.encoding.enabled",
"level": "error"
}
},
{
"name": "spring.http.encoding.force",
"type": "java.lang.Boolean",
"description": "Whether to force the encoding to the configured charset on HTTP requests and responses.",
"defaultValue": false,
"deprecation": {
"replacement": "server.servlet.encoding.force",
"level": "error"
}
},
{
"name": "spring.http.encoding.force-request",
"type": "java.lang.Boolean",
"description": "Whether to force the encoding to the configured charset on HTTP requests. Defaults to true when force has not been specified.",
"defaultValue": true,
"deprecation": {
"replacement": "server.servlet.encoding.force-request",
"level": "error"
}
},
{
"name": "spring.http.encoding.force-response",
"type": "java.lang.Boolean",
"description": "Whether to force the encoding to the configured charset on HTTP responses.",
"defaultValue": false,
"deprecation": {
"replacement": "server.servlet.encoding.force-response",
"level": "error"
}
},
{
"name": "spring.http.encoding.mapping",
"type": "java.util.Map<java.util.Locale,java.nio.charset.Charset>",
"description": "Locale in which to encode mapping.",
"deprecation": {
"replacement": "server.servlet.encoding.mapping",
"level": "error"
}
},
{
"name": "spring.http.log-request-details",
"type": "java.lang.Boolean",
"description": "Whether logging of (potentially sensitive) request details at DEBUG and TRACE level is allowed.",
"defaultValue": false,
"deprecation": {
"replacement": "spring.mvc.log-request-details",
"level": "error"
}
},
{
"name": "spring.servlet.encoding.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable Servlet HTTP encoding support.",
"defaultValue": true
}
]
}

View File

@@ -1,4 +1,5 @@
org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration
org.springframework.boot.servlet.actuate.autoconfigure.exchanges.ServletHttpExchangesAutoConfiguration
org.springframework.boot.servlet.actuate.autoconfigure.mappings.ServletMappingsAutoConfiguration
org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration
org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfiguration
org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.servlet.autoconfigure;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.Filter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link HttpEncodingAutoConfiguration}
*
* @author Stephane Nicoll
*/
class HttpEncodingAutoConfigurationTests {
private AnnotationConfigServletWebApplicationContext context;
@AfterEach
void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
void defaultConfiguration() {
load(EmptyConfiguration.class);
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "UTF-8", true, false);
}
@Test
void disableConfiguration() {
load(EmptyConfiguration.class, "spring.servlet.encoding.enabled:false");
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean(CharacterEncodingFilter.class));
}
@Test
void customConfiguration() {
load(EmptyConfiguration.class, "spring.servlet.encoding.charset:ISO-8859-15",
"spring.servlet.encoding.force:false");
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "ISO-8859-15", false, false);
}
@Test
void customFilterConfiguration() {
load(FilterConfiguration.class, "spring.servlet.encoding.charset:ISO-8859-15",
"spring.servlet.encoding.force:false");
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "US-ASCII", false, false);
}
@Test
void forceRequest() {
load(EmptyConfiguration.class, "spring.servlet.encoding.force-request:false");
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "UTF-8", false, false);
}
@Test
void forceResponse() {
load(EmptyConfiguration.class, "spring.servlet.encoding.force-response:true");
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "UTF-8", true, true);
}
@Test
void forceRequestOverridesForce() {
load(EmptyConfiguration.class, "spring.servlet.encoding.force:true",
"spring.servlet.encoding.force-request:false");
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "UTF-8", false, true);
}
@Test
void forceResponseOverridesForce() {
load(EmptyConfiguration.class, "spring.servlet.encoding.force:true",
"spring.servlet.encoding.force-response:false");
CharacterEncodingFilter filter = this.context.getBean(CharacterEncodingFilter.class);
assertCharacterEncodingFilter(filter, "UTF-8", true, false);
}
@Test
void filterIsOrderedHighest() {
load(OrderedConfiguration.class);
List<Filter> beans = new ArrayList<>(this.context.getBeansOfType(Filter.class).values());
AnnotationAwareOrderComparator.sort(beans);
assertThat(beans.get(0)).isInstanceOf(CharacterEncodingFilter.class);
assertThat(beans.get(1)).isInstanceOf(HiddenHttpMethodFilter.class);
}
private void assertCharacterEncodingFilter(CharacterEncodingFilter actual, String encoding,
boolean forceRequestEncoding, boolean forceResponseEncoding) {
assertThat(actual.getEncoding()).isEqualTo(encoding);
assertThat(actual.isForceRequestEncoding()).isEqualTo(forceRequestEncoding);
assertThat(actual.isForceResponseEncoding()).isEqualTo(forceResponseEncoding);
}
private void load(Class<?> config, String... environment) {
this.context = doLoad(new Class<?>[] { config }, environment);
}
private AnnotationConfigServletWebApplicationContext doLoad(Class<?>[] configs, String... environment) {
AnnotationConfigServletWebApplicationContext applicationContext = new AnnotationConfigServletWebApplicationContext();
TestPropertyValues.of(environment).applyTo(applicationContext);
applicationContext.register(configs);
applicationContext.register(HttpEncodingAutoConfiguration.class);
applicationContext.setServletContext(new MockServletContext());
applicationContext.refresh();
return applicationContext;
}
@Configuration(proxyBeanMethods = false)
static class EmptyConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class FilterConfiguration {
@Bean
CharacterEncodingFilter myCharacterEncodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("US-ASCII");
filter.setForceEncoding(false);
return filter;
}
}
@Configuration(proxyBeanMethods = false)
static class OrderedConfiguration {
@Bean
OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
}
}