Boot: 3.2.2 Framework: 6.1.3

PartEventHttpMessageReader#maxParts, configured via spring.webflux.multipart.max-parts, is validated incorrectly. Sending a multipart request with as many parts as should be allowed by that property is rejected with Too many parts. This means that if I want to allow at most 5 parts, I'll have to set spring.webflux.multipart.max-parts=6.

Controller:

@RestController
class TestController {

    @PostMapping
    public Mono<Void> upload(@RequestBody Flux<PartEvent> events) {
        return events
                .doOnError(Throwable::printStackTrace)
                .then();
    }

}

This test creates as many file parts as configured via spring.webflux.multipart.max-parts and sends them to the controller method above, expecting success. The request is however rejected and the following exception is thrown: org.springframework.core.codec.DecodingException: Too many parts (6/5 allowed)

@WebFluxTest(value = TestController.class, properties = "spring.webflux.multipart.max-parts=5")
@ImportAutoConfiguration(ReactiveMultipartAutoConfiguration.class)
class ReproducerTests {

    @Autowired
    private WebTestClient webTestClient;

    @Autowired
    private ReactiveMultipartProperties props;

    @Test
    void reproducer() {
        Flux<FilePartEvent> events = Flux.empty();
        for (int i = 0; i < props.getMaxParts(); i++) {
            byte[] bytes = new byte[1024];
            ThreadLocalRandom.current().nextBytes(bytes);
            events = events.concatWith(FilePartEvent.create(
                    "file-data",
                    "file-" + i + ".bin",
                    MediaType.APPLICATION_OCTET_STREAM,
                    Flux.just(DefaultDataBufferFactory.sharedInstance.wrap(bytes))
            ));
        }

        webTestClient.post()
                .body(events, PartEvent.class)
                .exchange()
                .expectStatus().isOk();
    }

}

Reproducer (extract and run ./mvnw test): demo.zip

Comment From: rstoyanchev

I can reproduce the behavior with the demo. From what I can see the calculation in PartEventHttpMessageReader is correct, but there is one extra emission from concatMap here in the beginning.

The Javadoc for windowUntil says "This variant can emit an empty window if the sequence starts with a separator" and since the sequence starts with a headers token, I think that is the reason for the extra emission.

Maybe the counting should be moved a few lines down to here inside the switchOnFirst where we know we are handling the headers for the next part?

What do you think @poutsma?

Comment From: rstoyanchev

Thanks for the report by the way @kzander91.

Comment From: poutsma

Maybe the counting should be moved a few lines down to here inside the switchOnFirst where we know we are handling the headers for the next part?

What do you think @poutsma?

Sounds good, so that's what I did in c570f3b2da2f17dbffdcdbed2e0e99e685d8b47f.