マルチパートコンテンツ
マルチパートデータで説明したように、ServerWebExchange
はマルチパートコンテンツへのアクセスを提供します。コントローラーでファイルアップロードフォーム (ブラウザーなどから) を処理する最良の方法は、次の例に示すように、コマンドオブジェクトへのデータバインディングを使用することです。
Java
Kotlin
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
class MyForm(
val name: String,
val file: MultipartFile)
@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 を使用してファイルを取得します。 |
次の例に示すように、未加工部分のコンテンツをデシリアライズする(たとえば、@RequestBody
に似た JSON に)ために、Part
の代わりに具体的なターゲット Object
を宣言できます。
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
を少なくとも 1 つ生成します。
フォームフィールドは、フィールドの値を含む単一の
FormPartEvent
を生成します。ファイルのアップロードは、アップロード時に使用されるファイル名を含む 1 つ以上の
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 | メモリリークを回避するために、本文の内容は完全に消費、中継、解放する必要があります。 |