Automatically clean up multipart temp files
This commit ensures that any resources created for multipart handling, obtained via ServerWebExchange.getMultipartData(), are automatically deleted after handling the completing the response. Resource for parts obtained via BodyExtractors::toMultipartData and BodyExtractors::toParts are not cleaned automatically, and should be cleaned via Part::delete. Closes gh-27633
This commit is contained in:
parent
f52920142b
commit
192f2becf6
|
|
@ -18,7 +18,9 @@ package org.springframework.web.server.adapter;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
|
@ -32,6 +34,7 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.codec.LoggingCodecSupport;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
|
|
@ -249,6 +252,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
|||
return getDelegate().handle(exchange)
|
||||
.doOnSuccess(aVoid -> logResponse(exchange))
|
||||
.onErrorResume(ex -> handleUnresolvedError(exchange, ex))
|
||||
.then(cleanupMultipart(exchange))
|
||||
.then(Mono.defer(response::setComplete));
|
||||
}
|
||||
|
||||
|
|
@ -323,4 +327,23 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
|||
return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
private Mono<Void> cleanupMultipart(ServerWebExchange exchange) {
|
||||
return exchange.getMultipartData()
|
||||
.onErrorResume(t -> Mono.empty()) // ignore errors reading multipart data
|
||||
.flatMapIterable(Map::values)
|
||||
.flatMapIterable(Function.identity())
|
||||
.flatMap(this::deletePart)
|
||||
.then();
|
||||
}
|
||||
|
||||
private Mono<Void> deletePart(Part part) {
|
||||
return part.delete().onErrorResume(ex -> {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to perform cleanup of multipart items", ex);
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ public abstract class BodyExtractors {
|
|||
|
||||
/**
|
||||
* Extractor to read multipart data into a {@code MultiValueMap<String, Part>}.
|
||||
* <p><strong>Note:</strong> that resources used for part handling,
|
||||
* like storage for the uploaded files, is not deleted automatically, but
|
||||
* should be done via {@link Part#delete()}.
|
||||
* @return {@code BodyExtractor} for multipart data
|
||||
*/
|
||||
// Parameterized for server-side use
|
||||
|
|
@ -151,6 +154,9 @@ public abstract class BodyExtractors {
|
|||
|
||||
/**
|
||||
* Extractor to read multipart data into {@code Flux<Part>}.
|
||||
* <p><strong>Note:</strong> that resources used for part handling,
|
||||
* like storage for the uploaded files, is not deleted automatically, but
|
||||
* should be done via {@link Part#delete()}.
|
||||
* @return {@code BodyExtractor} for multipart request parts
|
||||
*/
|
||||
// Parameterized for server-side use
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.Map;
|
|||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.test.StepVerifier;
|
||||
|
|
@ -168,7 +169,9 @@ class MultipartIntegrationTests extends AbstractRouterFunctionIntegrationTests {
|
|||
assertThat(parts.size()).isEqualTo(2);
|
||||
assertThat(((FilePart) parts.get("fooPart")).filename()).isEqualTo("foo.txt");
|
||||
assertThat(((FormFieldPart) parts.get("barPart")).value()).isEqualTo("bar");
|
||||
return ServerResponse.ok().build();
|
||||
return Flux.fromIterable(parts.values())
|
||||
.concatMap(Part::delete)
|
||||
.then(ServerResponse.ok().build());
|
||||
}
|
||||
catch(Exception e) {
|
||||
return Mono.error(e);
|
||||
|
|
@ -183,7 +186,9 @@ class MultipartIntegrationTests extends AbstractRouterFunctionIntegrationTests {
|
|||
assertThat(parts.size()).isEqualTo(2);
|
||||
assertThat(((FilePart) parts.get(0)).filename()).isEqualTo("foo.txt");
|
||||
assertThat(((FormFieldPart) parts.get(1)).value()).isEqualTo("bar");
|
||||
return ServerResponse.ok().build();
|
||||
return Flux.fromIterable(parts)
|
||||
.concatMap(Part::delete)
|
||||
.then(ServerResponse.ok().build());
|
||||
}
|
||||
catch(Exception e) {
|
||||
return Mono.error(e);
|
||||
|
|
|
|||
Loading…
Reference in New Issue