Add config property for webflux codec maxInMemorySize

This commit creates a new configuration property
`spring.codec.max-in-memory-size` which configures the maximum
amount of data to be buffered in memory by codecs (both client and
server).

This property has no default value - it will let Spring Framework handle
the default behavior, currently enforcing a 256KB for provided codecs.

Fixes gh-18828
pull/18832/head
Brian Clozel 5 years ago
parent 2d0a235c52
commit b7f59eb7cb

@ -0,0 +1,46 @@
/*
* Copyright 2012-2019 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.autoconfigure.codec;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
/**
* {@link ConfigurationProperties properties} for reactive codecs.
*
* @author Brian Clozel
* @since 2.2.1
*/
@ConfigurationProperties(prefix = "spring.codec")
public class CodecProperties {
/**
* Limit on the number of bytes that can be buffered whenever the input stream needs
* to be aggregated. By default this is not set, in which case individual codec
* defaults apply. Most codecs are limited to 256K by default.
*/
private DataSize maxInMemorySize;
public DataSize getMaxInMemorySize() {
return this.maxInMemorySize;
}
public void setMaxInMemorySize(DataSize maxInMemorySize) {
this.maxInMemorySize = maxInMemorySize;
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 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.
*/
/**
* Auto-configuration for reactive codecs.
*/
package org.springframework.boot.autoconfigure.codec;

@ -20,11 +20,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.codec.CodecProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpProperties; import org.springframework.boot.autoconfigure.http.HttpProperties;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -33,6 +35,7 @@ import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
import org.springframework.util.unit.DataSize;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
/** /**
@ -46,6 +49,7 @@ import org.springframework.web.reactive.function.client.WebClient;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ CodecConfigurer.class, WebClient.class }) @ConditionalOnClass({ CodecConfigurer.class, WebClient.class })
@AutoConfigureAfter(JacksonAutoConfiguration.class) @AutoConfigureAfter(JacksonAutoConfiguration.class)
@EnableConfigurationProperties({ HttpProperties.class, CodecProperties.class })
public class CodecsAutoConfiguration { public class CodecsAutoConfiguration {
private static final MimeType[] EMPTY_MIME_TYPES = {}; private static final MimeType[] EMPTY_MIME_TYPES = {};
@ -68,14 +72,18 @@ public class CodecsAutoConfiguration {
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HttpProperties.class) static class DefaultCodecsConfiguration {
static class LoggingCodecConfiguration {
@Bean @Bean
@Order(0) @Order(0)
CodecCustomizer loggingCodecCustomizer(HttpProperties properties) { CodecCustomizer defaultCodecCustomizer(HttpProperties httpProperties, CodecProperties codecProperties) {
return (configurer) -> configurer.defaultCodecs() return (configurer) -> {
.enableLoggingRequestDetails(properties.isLogRequestDetails()); PropertyMapper map = PropertyMapper.get();
CodecConfigurer.DefaultCodecs defaultCodecs = configurer.defaultCodecs();
defaultCodecs.enableLoggingRequestDetails(httpProperties.isLogRequestDetails());
map.from(codecProperties.getMaxInMemorySize()).whenNonNull().asInt(DataSize::toBytes)
.to(defaultCodecs::maxInMemorySize);
};
} }
} }

@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.codec.CodecProperties;
import org.springframework.boot.autoconfigure.http.HttpProperties; import org.springframework.boot.autoconfigure.http.HttpProperties;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.boot.web.codec.CodecCustomizer;
@ -67,11 +68,11 @@ class CodecsAutoConfigurationTests {
} }
@Test @Test
void loggingRequestDetailsBeanShouldHaveOrderZero() { void defaultCodecCustomizerBeanShouldHaveOrderZero() {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {
Method customizerMethod = ReflectionUtils.findMethod( Method customizerMethod = ReflectionUtils.findMethod(
CodecsAutoConfiguration.LoggingCodecConfiguration.class, "loggingCodecCustomizer", CodecsAutoConfiguration.DefaultCodecsConfiguration.class, "defaultCodecCustomizer",
HttpProperties.class); HttpProperties.class, CodecProperties.class);
Integer order = new TestAnnotationAwareOrderComparator().findOrder(customizerMethod); Integer order = new TestAnnotationAwareOrderComparator().findOrder(customizerMethod);
assertThat(order).isEqualTo(0); assertThat(order).isEqualTo(0);
}); });
@ -98,6 +99,16 @@ class CodecsAutoConfigurationTests {
}); });
} }
@Test
void maxInMemorySizeEnforcedInDefaultCodecs() {
this.contextRunner.withPropertyValues("spring.codec.max-in-memory-size=1MB").run((context) -> {
CodecCustomizer customizer = context.getBean(CodecCustomizer.class);
CodecConfigurer configurer = new DefaultClientCodecConfigurer();
customizer.customize(configurer);
assertThat(configurer.defaultCodecs()).hasFieldOrPropertyWithValue("maxInMemorySize", 1048576);
});
}
static class TestAnnotationAwareOrderComparator extends AnnotationAwareOrderComparator { static class TestAnnotationAwareOrderComparator extends AnnotationAwareOrderComparator {
@Override @Override

@ -2538,8 +2538,9 @@ If you want to take complete control of Spring WebFlux, you can add your own `@C
Spring WebFlux uses the `HttpMessageReader` and `HttpMessageWriter` interfaces to convert HTTP requests and responses. Spring WebFlux uses the `HttpMessageReader` and `HttpMessageWriter` interfaces to convert HTTP requests and responses.
They are configured with `CodecConfigurer` to have sensible defaults by looking at the libraries available in your classpath. They are configured with `CodecConfigurer` to have sensible defaults by looking at the libraries available in your classpath.
Spring Boot applies further customization by using `CodecCustomizer` instances. Spring Boot provides dedicated configuration properties for codecs, `+spring.codec.*+`.
For example, `spring.jackson.*` configuration keys are applied to the Jackson codec. It also applies further customization by using `CodecCustomizer` instances.
For example, `+spring.jackson.*+` configuration keys are applied to the Jackson codec.
If you need to add or customize codecs, you can create a custom `CodecCustomizer` component, as shown in the following example: If you need to add or customize codecs, you can create a custom `CodecCustomizer` component, as shown in the following example:
@ -2554,7 +2555,7 @@ If you need to add or customize codecs, you can create a custom `CodecCustomizer
public CodecCustomizer myCodecCustomizer() { public CodecCustomizer myCodecCustomizer() {
return codecConfigurer -> { return codecConfigurer -> {
// ... // ...
} };
} }
} }

@ -24,7 +24,7 @@ def generateConfigMetadataDocumentation() {
builder builder
.addSection("core") .addSection("core")
.withKeyPrefixes("debug", "trace", "logging", "spring.aop", "spring.application", .withKeyPrefixes("debug", "trace", "logging", "spring.aop", "spring.application",
"spring.autoconfigure", "spring.banner", "spring.beaninfo", "spring.config", "spring.autoconfigure", "spring.banner", "spring.beaninfo", "spring.codec", "spring.config",
"spring.info", "spring.jmx", "spring.main", "spring.messages", "spring.pid", "spring.info", "spring.jmx", "spring.main", "spring.messages", "spring.pid",
"spring.profiles", "spring.quartz", "spring.reactor", "spring.task", "spring.profiles", "spring.quartz", "spring.reactor", "spring.task",
"spring.mandatory-file-encoding", "info", "spring.output.ansi.enabled") "spring.mandatory-file-encoding", "info", "spring.output.ansi.enabled")

Loading…
Cancel
Save