@@ -619,11 +619,11 @@ https://github.com/synchronoss/nio-multipart[Synchronoss NIO Multipart] library.
619
619
Both are configured through the `ServerCodecConfigurer` bean
620
620
(see the <<webflux-web-handler-api, Web Handler API>>).
621
621
622
- To parse multipart data in streaming fashion, you can use the `Flux<Part >` returned from an
623
- `HttpMessageReader<Part> ` instead. For example, in an annotated controller, use of
624
- `@RequestPart` implies `Map`-like access to individual parts by name and, hence, requires
625
- parsing multipart data in full. By contrast, you can use `@RequestBody` to decode the
626
- content to `Flux<Part>` without collecting to a `MultiValueMap`.
622
+ To parse multipart data in streaming fashion, you can use the `Flux<PartEvent >` returned from the
623
+ `PartEventHttpMessageReader ` instead of using `@RequestPart`, as that implies `Map`-like access
624
+ to individual parts by name and, hence, requires parsing multipart data in full.
625
+ By contrast, you can use `@RequestBody` to decode the content to `Flux<PartEvent>` without
626
+ collecting to a `MultiValueMap`.
627
627
628
628
629
629
[[webflux-forwarded-headers]]
@@ -2825,29 +2825,98 @@ as the following example shows:
2825
2825
----
2826
2826
<1> Using `@RequestBody`.
2827
2827
2828
+ ===== `PartEvent`
2828
2829
2829
- To access multipart data sequentially, in streaming fashion, you can use `@RequestBody` with
2830
- `Flux<Part>` (or `Flow<Part>` in Kotlin) instead, as the following example shows:
2830
+ To access multipart data sequentially, in a streaming fashion, you can use `@RequestBody` with
2831
+ `Flux<PartEvent>` (or `Flow<PartEvent>` in Kotlin).
2832
+ Each part in a multipart HTTP message will produce at
2833
+ least one `PartEvent` containing both headers and a buffer with the contents of the part.
2834
+
2835
+ - Form fields will produce a *single* `FormPartEvent`, containing the value of the field.
2836
+ - File uploads will produce *one or more* `FilePartEvent` objects, containing the filename used
2837
+ when uploading. If the file is large enough to be split across multiple buffers, the first
2838
+ `FilePartEvent` will be followed by subsequent events.
2839
+
2840
+
2841
+ For example:
2831
2842
2832
2843
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
2833
2844
.Java
2834
2845
----
2835
- @PostMapping("/")
2836
- public String handle(@RequestBody Flux<Part> parts) { <1>
2837
- // ...
2838
- }
2846
+ @PostMapping("/")
2847
+ public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { <1>
2848
+ allPartsEvents.windowUntil(PartEvent::isLast) <2>
2849
+ .concatMap(p -> p.switchOnFirst((signal, partEvents) -> { <3>
2850
+ if (signal.hasValue()) {
2851
+ PartEvent event = signal.get();
2852
+ if (event instanceof FormPartEvent formEvent) { <4>
2853
+ String value = formEvent.value();
2854
+ // handle form field
2855
+ }
2856
+ else if (event instanceof FilePartEvent fileEvent) { <5>
2857
+ String filename = fileEvent.filename();
2858
+ Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
2859
+ // handle file upload
2860
+ }
2861
+ else {
2862
+ return Mono.error(new RuntimeException("Unexpected event: " + event));
2863
+ }
2864
+ }
2865
+ else {
2866
+ return partEvents; // either complete or error signal
2867
+ }
2868
+ }));
2869
+ }
2839
2870
----
2840
2871
<1> Using `@RequestBody`.
2872
+ <2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
2873
+ followed by additional events belonging to subsequent parts.
2874
+ This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
2875
+ split events from all parts into windows that each belong to a single part.
2876
+ <3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
2877
+ file upload.
2878
+ <4> Handling the form field.
2879
+ <5> Handling the file upload.
2841
2880
2842
2881
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
2843
2882
.Kotlin
2844
2883
----
2845
2884
@PostMapping("/")
2846
- fun handle(@RequestBody parts: Flow<Part>): String { // <1>
2847
- // ...
2848
- }
2885
+ fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { // <1>
2886
+ allPartsEvents.windowUntil(PartEvent::isLast) <2>
2887
+ .concatMap {
2888
+ it.switchOnFirst { signal, partEvents -> <3>
2889
+ if (signal.hasValue()) {
2890
+ val event = signal.get()
2891
+ if (event is FormPartEvent) { <4>
2892
+ val value: String = event.value();
2893
+ // handle form field
2894
+ } else if (event is FilePartEvent) { <5>
2895
+ val filename: String = event.filename();
2896
+ val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content);
2897
+ // handle file upload
2898
+ } else {
2899
+ return Mono.error(RuntimeException("Unexpected event: " + event));
2900
+ }
2901
+ } else {
2902
+ return partEvents; // either complete or error signal
2903
+ }
2904
+ }
2905
+ }
2906
+ }
2849
2907
----
2850
2908
<1> Using `@RequestBody`.
2909
+ <2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
2910
+ followed by additional events belonging to subsequent parts.
2911
+ This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
2912
+ split events from all parts into windows that each belong to a single part.
2913
+ <3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
2914
+ file upload.
2915
+ <4> Handling the form field.
2916
+ <5> Handling the file upload.
2917
+
2918
+ Received part events can also be relayed to another service by using the `WebClient`.
2919
+ See <<webflux-client-body-multipart>>.
2851
2920
2852
2921
2853
2922
[[webflux-ann-requestbody]]
0 commit comments