when i use router function,it works fine RouterFunction:
@Bean
public RouterFunction<ServerResponse> configUpload(UploadHandler uploadHandler) {
return RouterFunctions.route(RequestPredicates.POST("/api/v1/config/upload").and(RequestPredicates.contentType(MediaType.MULTIPART_FORM_DATA)), uploadHandler::uploadConfig);
}
UploadHandler:
public Mono<ServerResponse> uploadConfig(ServerRequest request){
return request.body(BodyExtractors.toMultipartData()).flatMap(parts->{
Map<String, Part> stringPartMap = parts.toSingleValueMap();
stringPartMap.forEach((partName,value)->{
if(value instanceof FilePart){
// Handle file
FilePart file = (FilePart) stringPartMap.get(partName);
DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("File name: {}", file.filename());
}
if(value instanceof FormFieldPart){
// Handle form field
FormFieldPart type = (FormFieldPart) stringPartMap.get(partName);
DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("type value: {}", type.value());
}
});
return Mono.empty();
}).then(ServerResponse.ok().bodyValue(Result.success(new MultiFileUploadResponse())));
}
But when i use RestController with RequestBody it give me a parse error: Could not find first boundary
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.server.ServerWebInputException: 400 BAD_REQUEST "Failed to read HTTP message"; nested exception is org.springframework.core.codec.DecodingException: Could not find first boundary Caused by: org.springframework.web.server.ServerWebInputException: 400 BAD_REQUEST "Failed to read HTTP message"; nested exception is org.springframework.core.codec.DecodingException: Could not find first boundary at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.handleReadError(AbstractMessageReaderArgumentResolver.java:235) ~[spring-webflux-5.3.27.jar:5.3.27] at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.lambda$readBody$0(AbstractMessageReaderArgumentResolver.java:185) ~[spring-webflux-5.3.27.jar:5.3.27] at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$BaseSink.error(FluxCreate.java:474) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:802) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$BufferAsyncSink.error(FluxCreate.java:747) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$SerializedFluxSink.drainLoop(FluxCreate.java:237) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$SerializedFluxSink.drain(FluxCreate.java:213) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$SerializedFluxSink.error(FluxCreate.java:189) [reactor-core-3.4.29.jar:3.4.29] at org.springframework.http.codec.multipart.PartGenerator.hookOnError(PartGenerator.java:176) [spring-web-5.3.27.jar:5.3.27] at reactor.core.publisher.BaseSubscriber.onError(BaseSubscriber.java:180) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$BaseSink.error(FluxCreate.java:474) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:802) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$BufferAsyncSink.error(FluxCreate.java:747) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$SerializedFluxSink.drainLoop(FluxCreate.java:237) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$SerializedFluxSink.drain(FluxCreate.java:213) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxCreate$SerializedFluxSink.error(FluxCreate.java:189) [reactor-core-3.4.29.jar:3.4.29] at org.springframework.http.codec.multipart.MultipartParser.emitError(MultipartParser.java:179) [spring-web-5.3.27.jar:5.3.27] at org.springframework.http.codec.multipart.MultipartParser$PreambleState.onComplete(MultipartParser.java:321) [spring-web-5.3.27.jar:5.3.27] at org.springframework.http.codec.multipart.MultipartParser.hookOnComplete(MultipartParser.java:124) [spring-web-5.3.27.jar:5.3.27] at reactor.core.publisher.BaseSubscriber.onComplete(BaseSubscriber.java:197) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) [reactor-core-3.4.29.jar:3.4.29] at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) [reactor-core-3.4.29.jar:3.4.29] at reactor.netty.channel.FluxReceive.disposeAndUnsubscribeReceiver(FluxReceive.java:499) [reactor-netty-core-1.0.32.jar:1.0.32] at reactor.netty.channel.FluxReceive.lambda$new$0(FluxReceive.java:86) [reactor-netty-core-1.0.32.jar:1.0.32] at reactor.netty.channel.FluxReceive.cancelReceiver(FluxReceive.java:199) [reactor-netty-core-1.0.32.jar:1.0.32] at reactor.netty.channel.FluxReceive.doCancel(FluxReceive.java:205) [reactor-netty-core-1.0.32.jar:1.0.32] at reactor.netty.channel.FluxReceive.dispose(FluxReceive.java:118) [reactor-netty-core-1.0.32.jar:1.0.32] at reactor.netty.channel.ChannelOperations.discard(ChannelOperations.java:366) ~[reactor-netty-core-1.0.32.jar:1.0.32] at reactor.netty.http.server.HttpServerOperations.cleanHandlerTerminate(HttpServerOperations.java:764) ~[reactor-netty-http-1.0.32.jar:1.0.32] at reactor.netty.http.server.HttpTrafficHandler.operationComplete(HttpTrafficHandler.java:464) ~[reactor-netty-http-1.0.32.jar:1.0.32] at reactor.netty.http.server.HttpTrafficHandler.operationComplete(HttpTrafficHandler.java:65) ~[reactor-netty-http-1.0.32.jar:1.0.32] at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:590) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:557) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:492) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:636) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:625) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:105) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.PromiseCombiner.tryPromise(PromiseCombiner.java:170) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.PromiseCombiner.access$600(PromiseCombiner.java:35) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.PromiseCombiner$1.operationComplete0(PromiseCombiner.java:62) ~[netty-common-4.1.92.Final.jar:4.1.92.Final] at io.netty.util.concurrent.PromiseCombiner$1.operationComplete(PromiseCombiner.java:44) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
Controller code is:
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Result<MultiFileUploadResponse>> upload(@RequestBody Mono<MultiValueMap<String, Part>> multiValueMapMono) {
multiValueMapMono.subscribe(stringPartMap -> {
stringPartMap.forEach((partName, value) -> {
if (value instanceof FilePart) {
// Handle file
FilePart file = (FilePart) stringPartMap.get(partName);
DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("File name: {}", file.filename());
}
if (value instanceof FormFieldPart) {
// Handle form field
FormFieldPart type = (FormFieldPart) stringPartMap.get(partName);
DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("type value: {}", type.value());
}
});
});
return Mono.just(Result.success(new MultiFileUploadResponse()));
}
@PostMapping(value = "/upload2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Result<MultiFileUploadResponse>> upload2(@RequestBody Flux<Part> input) {
input.subscribe(value -> {
if (value instanceof FilePart) {
// Handle file
FilePart file = (FilePart) value;
DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("File name: {}", file.filename());
}
if (value instanceof FormFieldPart) {
// Handle form field
FormFieldPart type = (FormFieldPart) value;
DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("type value: {}", type.value());
}
});
return Mono.just(Result.success(new MultiFileUploadResponse()));
}
Both Mono<MultiValueMap<String, Part>> or Flux<Part> get error
Spring webflux version:
org.springframework.boot spring-boot-starter-webflux 2.7.12
I have read the spring reference and confirm it support https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/web-reactive.html#webflux-multipart-forms