多部分內容
-
Java
-
Kotlin
class MyForm {
private String name;
private FilePart file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
class MyForm(
val name: String,
val file: FilePart)
@Controller
class FileUploadController {
@PostMapping("/form")
fun handleFormUpload(form: MyForm, errors: BindingResult): String {
// ...
}
}
您還可以在 RESTful 服務場景中從非瀏覽器客戶端提交多部分請求。以下示例使用檔案和 JSON
POST /someUrl
Content-Type: multipart/mixed
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...
您可以使用 @RequestPart 訪問各個部分,如下例所示
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1)
@RequestPart("file-data") FilePart file) { (2)
// ...
}
| 1 | 使用 @RequestPart 獲取元資料。 |
| 2 | 使用 @RequestPart 獲取檔案。 |
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, (1)
@RequestPart("file-data") FilePart file): String { (2)
// ...
}
| 1 | 使用 @RequestPart 獲取元資料。 |
| 2 | 使用 @RequestPart 獲取檔案。 |
要反序列化原始部分內容(例如,到 JSON——類似於 @RequestBody),您可以宣告一個具體的目標Object,而不是 Part,如下例所示
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { (1)
// ...
}
| 1 | 使用 @RequestPart 獲取元資料。 |
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String { (1)
// ...
}
| 1 | 使用 @RequestPart 獲取元資料。 |
您可以將 @RequestPart 與 jakarta.validation.Valid 或 Spring 的 @Validated 註解結合使用,這將導致應用標準 Bean 驗證。驗證錯誤會導致 WebExchangeBindException,從而導致 400 (BAD_REQUEST) 響應。該異常包含帶有錯誤詳細資訊的 BindingResult,也可以透過宣告帶有非同步包裝器的引數然後在控制器方法中使用與錯誤相關的運算子來處理
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// use one of the onError* operators...
}
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
// ...
}
如果由於其他引數具有 @Constraint 註解而應用了方法驗證,則會引發 HandlerMethodValidationException。請參閱驗證部分。
要將所有多部分資料作為 MultiValueMap 訪問,您可以使用 @RequestBody,如下例所示
-
Java
-
Kotlin
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { (1)
// ...
}
| 1 | 使用 @RequestBody。 |
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { (1)
// ...
}
| 1 | 使用 @RequestBody。 |
PartEvent
為了以流式方式順序訪問多部分資料,您可以將 @RequestBody 與 Flux<PartEvent>(或 Kotlin 中的 Flow<PartEvent>)一起使用。多部分 HTTP 訊息中的每個部分將至少生成一個 PartEvent,其中包含標題和包含部分內容的緩衝區。
-
表單欄位將生成一個單一的
FormPartEvent,其中包含欄位的值。 -
檔案上傳將生成一個或多個
FilePartEvent物件,其中包含上傳時使用的檔名。如果檔案足夠大,需要分成多個緩衝區,則第一個FilePartEvent之後將跟隨著後續事件。
例如:
-
Java
-
Kotlin
@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 | 使用 @RequestBody。 |
| 2 | 特定部分的最終 PartEvent 將把 isLast() 設定為 true,之後可能會有屬於後續部分的附加事件。這使得 isLast 屬性適合作為 Flux::windowUntil 運算子的謂詞,用於將所有部分的事件拆分為每個部分單獨的視窗。 |
| 3 | Flux::switchOnFirst 運算子允許您檢視您正在處理的是表單欄位還是檔案上傳。 |
| 4 | 處理表單欄位。 |
| 5 | 處理檔案上傳。 |
| 6 | 必須完全消耗、轉發或釋放主體內容,以避免記憶體洩漏。 |
@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 | 使用 @RequestBody。 |
| 2 | 特定部分的最終 PartEvent 將把 isLast() 設定為 true,之後可能會有屬於後續部分的附加事件。這使得 isLast 屬性適合作為 Flux::windowUntil 運算子的謂詞,用於將所有部分的事件拆分為每個部分單獨的視窗。 |
| 3 | Flux::switchOnFirst 運算子允許您檢視您正在處理的是表單欄位還是檔案上傳。 |
| 4 | 處理表單欄位。 |
| 5 | 處理檔案上傳。 |
| 6 | 必須完全消耗、轉發或釋放主體內容,以避免記憶體洩漏。 |
收到的部分事件也可以透過 WebClient 轉發到另一個服務。請參閱多部分資料。