Remove spring.webflux.multipart.streaming property

As of spring-projects/spring-framework#29293, the streaming mode on the
`DefaultPartHttpMessageReader` is deprecated as hard limitations have
been found with the design and won't be fixed. Instead, developers
should use the `PartEvent` API and the `PartEventHttpMessageReader`
(which is configured by default with the codecs).

This commit removes the `spring.webflux.multipart.streaming` property
and applies all `spring.webflux.multipart.*` properties that are
applicable to `PartEventHttpMessageReader`.

Closes gh-32658
This commit is contained in:
Brian Clozel
2022-10-10 14:59:34 +02:00
parent 4e46f86558
commit d870474fcd
5 changed files with 56 additions and 28 deletions

View File

@@ -31,6 +31,7 @@ import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
import org.springframework.util.unit.DataSize;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@@ -61,11 +62,18 @@ public class ReactiveMultipartAutoConfiguration {
map.from(multipartProperties::getMaxDiskUsagePerPart).asInt(DataSize::toBytes)
.to(defaultPartHttpMessageReader::setMaxDiskUsagePerPart);
map.from(multipartProperties::getMaxParts).to(defaultPartHttpMessageReader::setMaxParts);
map.from(multipartProperties::getStreaming).to(defaultPartHttpMessageReader::setStreaming);
map.from(multipartProperties::getFileStorageDirectory).as(Paths::get)
.to((dir) -> configureFileStorageDirectory(defaultPartHttpMessageReader, dir));
map.from(multipartProperties::getHeadersCharset).to(defaultPartHttpMessageReader::setHeadersCharset);
}
else if (codec instanceof PartEventHttpMessageReader partEventHttpMessageReader) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(multipartProperties::getMaxInMemorySize).asInt(DataSize::toBytes)
.to(partEventHttpMessageReader::setMaxInMemorySize);
map.from(multipartProperties::getMaxHeadersSize).asInt(DataSize::toBytes)
.to(partEventHttpMessageReader::setMaxHeadersSize);
map.from(multipartProperties::getHeadersCharset).to(partEventHttpMessageReader::setHeadersCharset);
}
});
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@@ -21,11 +21,13 @@ import java.nio.charset.StandardCharsets;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
import org.springframework.util.unit.DataSize;
/**
* {@link ConfigurationProperties Configuration properties} for configuring multipart
* support in Spring Webflux. Used to configure the {@link DefaultPartHttpMessageReader}.
* support in Spring Webflux. Used to configure the {@link DefaultPartHttpMessageReader}
* and the {@link PartEventHttpMessageReader}.
*
* @author Chris Bono
* @since 2.6.0
@@ -57,12 +59,6 @@ public class ReactiveMultipartProperties {
*/
private Integer maxParts = -1;
/**
* Whether to stream directly from the parsed input buffer stream without storing in
* memory nor file. Default is non-streaming.
*/
private Boolean streaming = Boolean.FALSE;
/**
* Directory used to store file parts larger than 'maxInMemorySize'. Default is a
* directory named 'spring-multipart' created under the system temporary directory.
@@ -107,14 +103,6 @@ public class ReactiveMultipartProperties {
this.maxParts = maxParts;
}
public Boolean getStreaming() {
return this.streaming;
}
public void setStreaming(Boolean streaming) {
this.streaming = streaming;
}
public String getFileStorageDirectory() {
return this.fileStorageDirectory;
}

View File

@@ -3376,6 +3376,14 @@
"name": "any"
}
]
},
{
"name": "spring.webflux.multipart.streaming",
"type": "java.lang.Boolean",
"deprecation": {
"reason": "Replaced by the PartEventHttpMessageReader and the PartEvent API.",
"level": "error"
}
}
]
}

View File

@@ -26,6 +26,7 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.util.unit.DataSize;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@@ -36,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link ReactiveMultipartAutoConfiguration}.
*
* @author Chris Bono
* @author Brian Clozel
*/
class ReactiveMultipartAutoConfigurationTests {
@@ -56,16 +58,17 @@ class ReactiveMultipartAutoConfigurationTests {
}
@Test
void shouldConfigureMultipartProperties() {
this.contextRunner.withPropertyValues("spring.webflux.multipart.streaming:true",
"spring.webflux.multipart.max-in-memory-size=1GB", "spring.webflux.multipart.max-headers-size=16KB",
"spring.webflux.multipart.max-disk-usage-per-part=100MB", "spring.webflux.multipart.max-parts=7",
"spring.webflux.multipart.headers-charset:UTF_16").run((context) -> {
void shouldConfigureMultipartPropertiesForDefaultReader() {
this.contextRunner
.withPropertyValues("spring.webflux.multipart.max-in-memory-size=1GB",
"spring.webflux.multipart.max-headers-size=16KB",
"spring.webflux.multipart.max-disk-usage-per-part=100MB",
"spring.webflux.multipart.max-parts=7", "spring.webflux.multipart.headers-charset:UTF_16")
.run((context) -> {
CodecCustomizer customizer = context.getBean(CodecCustomizer.class);
DefaultServerCodecConfigurer configurer = new DefaultServerCodecConfigurer();
customizer.customize(configurer);
DefaultPartHttpMessageReader partReader = getPartReader(configurer);
assertThat(partReader).hasFieldOrPropertyWithValue("streaming", true);
DefaultPartHttpMessageReader partReader = getDefaultPartReader(configurer);
assertThat(partReader).hasFieldOrPropertyWithValue("maxParts", 7);
assertThat(partReader).hasFieldOrPropertyWithValue("maxHeadersSize",
Math.toIntExact(DataSize.ofKilobytes(16).toBytes()));
@@ -77,10 +80,33 @@ class ReactiveMultipartAutoConfigurationTests {
});
}
private DefaultPartHttpMessageReader getPartReader(DefaultServerCodecConfigurer codecConfigurer) {
@Test
void shouldConfigureMultipartPropertiesForPartEventReader() {
this.contextRunner.withPropertyValues("spring.webflux.multipart.max-in-memory-size=1GB",
"spring.webflux.multipart.max-headers-size=16KB", "spring.webflux.multipart.headers-charset:UTF_16")
.run((context) -> {
CodecCustomizer customizer = context.getBean(CodecCustomizer.class);
DefaultServerCodecConfigurer configurer = new DefaultServerCodecConfigurer();
customizer.customize(configurer);
PartEventHttpMessageReader partReader = getPartEventReader(configurer);
assertThat(partReader).hasFieldOrPropertyWithValue("maxHeadersSize",
Math.toIntExact(DataSize.ofKilobytes(16).toBytes()));
assertThat(partReader).hasFieldOrPropertyWithValue("headersCharset", StandardCharsets.UTF_16);
assertThat(partReader).hasFieldOrPropertyWithValue("maxInMemorySize",
Math.toIntExact(DataSize.ofGigabytes(1).toBytes()));
});
}
private DefaultPartHttpMessageReader getDefaultPartReader(DefaultServerCodecConfigurer codecConfigurer) {
return codecConfigurer.getReaders().stream().filter(DefaultPartHttpMessageReader.class::isInstance)
.map(DefaultPartHttpMessageReader.class::cast).findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find DefaultPartHttpMessageReader"));
}
private PartEventHttpMessageReader getPartEventReader(DefaultServerCodecConfigurer codecConfigurer) {
return codecConfigurer.getReaders().stream().filter(PartEventHttpMessageReader.class::isInstance)
.map(PartEventHttpMessageReader.class::cast).findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find PartEventHttpMessageReader"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@@ -33,8 +33,6 @@ class ReactiveMultipartPropertiesTests {
void defaultValuesAreConsistent() {
ReactiveMultipartProperties multipartProperties = new ReactiveMultipartProperties();
DefaultPartHttpMessageReader defaultPartHttpMessageReader = new DefaultPartHttpMessageReader();
assertThat(defaultPartHttpMessageReader).hasFieldOrPropertyWithValue("streaming",
multipartProperties.getStreaming());
assertThat(defaultPartHttpMessageReader).hasFieldOrPropertyWithValue("maxInMemorySize",
(int) multipartProperties.getMaxInMemorySize().toBytes());
assertThat(defaultPartHttpMessageReader).hasFieldOrPropertyWithValue("maxHeadersSize",