Browse Source

Fix Kotlin example in `Multipart Content`

Closes gh-36094
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
pull/36110/head
Tran Ngoc Nhan 4 weeks ago committed by Sébastien Deleuze
parent
commit
9abe4b46ca
  1. 90
      framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc
  2. 70
      framework-docs/src/main/java/org/springframework/docs/web/webflux/controller/annmethods/partevent/PartEventController.java
  3. 69
      framework-docs/src/main/kotlin/org/springframework/docs/web/webflux/controller/annmethods/partevent/PartEventController.kt

90
framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc

@ -103,8 +103,8 @@ Kotlin:: @@ -103,8 +103,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, // <1>
@RequestPart("file-data") FilePart file): String { // <2>
fun handle(@RequestPart("meta-data") metadata: Part, // <1>
@RequestPart("file-data") file: FilePart): String { // <2>
// ...
}
----
@ -169,7 +169,7 @@ Kotlin:: @@ -169,7 +169,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
fun handle(@Valid @RequestPart("meta-data") metadata: Mono<MetaData>): String {
// ...
}
----
@ -202,7 +202,7 @@ Kotlin:: @@ -202,7 +202,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { // <1>
fun handle(@RequestBody parts: Mono<MultiValueMap<String, Part>>): String { // <1>
// ...
}
----
@ -227,87 +227,7 @@ when uploading. If the file is large enough to be split across multiple buffers, @@ -227,87 +227,7 @@ when uploading. If the file is large enough to be split across multiple buffers,
For example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { <1>
allPartsEvents.windowUntil(PartEvent::isLast) <2>
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> { <3>
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) { <4>
String value = formEvent.value();
// handle form field
}
else if (event instanceof FilePartEvent fileEvent) { <5>
String filename = fileEvent.filename();
Flux<DataBuffer> contents = partEvents.map(PartEvent::content); <6>
// handle file upload
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
}));
}
----
<1> Using `@RequestBody`.
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
followed by additional events belonging to subsequent parts.
This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
split events from all parts into windows that each belong to a single part.
<3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
file upload.
<4> Handling the form field.
<5> Handling the file upload.
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { // <1>
allPartsEvents.windowUntil(PartEvent::isLast) <2>
.concatMap {
it.switchOnFirst { signal, partEvents -> <3>
if (signal.hasValue()) {
val event = signal.get()
if (event is FormPartEvent) { <4>
val value: String = event.value();
// handle form field
} else if (event is FilePartEvent) { <5>
val filename: String = event.filename();
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content); <6>
// handle file upload
} else {
return Mono.error(RuntimeException("Unexpected event: " + event));
}
} else {
return partEvents; // either complete or error signal
}
}
}
}
----
<1> Using `@RequestBody`.
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
followed by additional events belonging to subsequent parts.
This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
split events from all parts into windows that each belong to a single part.
<3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
file upload.
<4> Handling the form field.
<5> Handling the file upload.
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
======
include-code::./PartEventController[tag=snippet,indent=0]
Received part events can also be relayed to another service by using the `WebClient`.
See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data].

70
framework-docs/src/main/java/org/springframework/docs/web/webflux/controller/annmethods/partevent/PartEventController.java

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
/*
* Copyright 2026-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webflux.controller.annmethods.partevent;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.multipart.FilePartEvent;
import org.springframework.http.codec.multipart.FormPartEvent;
import org.springframework.http.codec.multipart.PartEvent;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
public class PartEventController {
// tag::snippet[]
@PostMapping("/")
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { // Using @RequestBody.
// The final PartEvent for a particular part will have isLast() set to true, and can be
// followed by additional events belonging to subsequent parts.
// This makes the isLast property suitable as a predicate for the Flux::windowUntil operator, to
// split events from all parts into windows that each belong to a single part.
allPartsEvents.windowUntil(PartEvent::isLast)
// The Flux::switchOnFirst operator allows you to see whether you are handling
// a form field or file upload.
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) {
String value = formEvent.value();
// Handling the form field.
}
else if (event instanceof FilePartEvent fileEvent) {
String filename = fileEvent.filename();
// The body contents must be completely consumed, relayed, or released to avoid memory leaks.
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
// Handling the file upload.
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
return Mono.empty();
}));
}
// end::snippet[]
}

69
framework-docs/src/main/kotlin/org/springframework/docs/web/webflux/controller/annmethods/partevent/PartEventController.kt

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* Copyright 2026-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webflux.controller.annmethods.partevent
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.http.codec.multipart.FilePartEvent
import org.springframework.http.codec.multipart.FormPartEvent
import org.springframework.http.codec.multipart.PartEvent
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@RestController
class PartEventController {
// tag::snippet[]
@PostMapping("/")
fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) { // Using @RequestBody.
// The final PartEvent for a particular part will have isLast() set to true, and can be
// followed by additional events belonging to subsequent parts.
// This makes the isLast property suitable as a predicate for the Flux::windowUntil operator, to
// split events from all parts into windows that each belong to a single part.
allPartsEvents.windowUntil(PartEvent::isLast)
.concatMap {
// The Flux::switchOnFirst operator allows you to see whether you are handling
// a form field or file upload.
it.switchOnFirst { signal, partEvents ->
if (signal.hasValue()) {
val event = signal.get()
if (event is FormPartEvent) {
val value: String = event.value()
// Handling the form field.
} else if (event is FilePartEvent) {
val filename: String = event.filename()
// The body contents must be completely consumed, relayed, or released to avoid memory leaks.
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content)
// Handling the file upload.
} else {
return@switchOnFirst Mono.error(RuntimeException("Unexpected event: $event"))
}
} else {
return@switchOnFirst partEvents // either complete or error signal
}
Mono.empty()
}
}
}
// end::snippet[]
}
Loading…
Cancel
Save