mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Fix Kotlin example in Multipart Content
Closes gh-36094 Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
This commit is contained in:
committed by
Sébastien Deleuze
parent
192da6229c
commit
9abe4b46ca
+5
-85
@@ -103,8 +103,8 @@ Kotlin::
|
|||||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
@PostMapping("/")
|
@PostMapping("/")
|
||||||
fun handle(@RequestPart("meta-data") Part metadata, // <1>
|
fun handle(@RequestPart("meta-data") metadata: Part, // <1>
|
||||||
@RequestPart("file-data") FilePart file): String { // <2>
|
@RequestPart("file-data") file: FilePart): String { // <2>
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@@ -169,7 +169,7 @@ Kotlin::
|
|||||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
@PostMapping("/")
|
@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::
|
|||||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
@PostMapping("/")
|
@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,
|
|||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
[tabs]
|
include-code::./PartEventController[tag=snippet,indent=0]
|
||||||
======
|
|
||||||
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.
|
|
||||||
======
|
|
||||||
|
|
||||||
Received part events can also be relayed to another service by using the `WebClient`.
|
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].
|
See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data].
|
||||||
|
|||||||
+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
@@ -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[]
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user