Skip to content

Commit

Permalink
Automatically clean up multipart temp files
Browse files Browse the repository at this point in the history
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
  • Loading branch information
poutsma committed Apr 6, 2022
1 parent f529201 commit 192f2be
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

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;
Expand All @@ -32,6 +34,7 @@
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;
Expand Down Expand Up @@ -249,6 +252,7 @@ public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response)
return getDelegate().handle(exchange)
.doOnSuccess(aVoid -> logResponse(exchange))
.onErrorResume(ex -> handleUnresolvedError(exchange, ex))
.then(cleanupMultipart(exchange))
.then(Mono.defer(response::setComplete));
}

Expand Down Expand Up @@ -323,4 +327,23 @@ private boolean isDisconnectedClientError(Throwable ex) {
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();
});
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ public static BodyExtractor<Mono<MultiValueMap<String, String>>, ReactiveHttpInp

/**
* 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
Expand All @@ -151,6 +154,9 @@ public static BodyExtractor<Mono<MultiValueMap<String, Part>>, ServerHttpRequest

/**
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

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;
Expand Down Expand Up @@ -168,7 +169,9 @@ public Mono<ServerResponse> multipartData(ServerRequest request) {
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);
Expand All @@ -183,7 +186,9 @@ public Mono<ServerResponse> parts(ServerRequest request) {
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);
Expand Down

0 comments on commit 192f2be

Please sign in to comment.