マルチパートコンテンツ

マルチパートデータで説明したように、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 オペレーターの述語として適切になり、イベントをすべてのパーツから、それぞれが単一のパーツに属するウィンドウに分割します。
3Flux::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 オペレーターの述語として適切になり、イベントをすべてのパーツから、それぞれが単一のパーツに属するウィンドウに分割します。
3Flux::switchOnFirst オペレーターを使用すると、フォームフィールドまたはファイルのアップロードを処理しているかどうかを確認できます。
4 フォームフィールドの処理。
5 ファイルのアップロードを処理します。
6 メモリリークを回避するために、本文の内容は完全に消費、中継、解放する必要があります。

受信したパーツイベントは、WebClient を使用して別のサービスに中継することもできます。マルチパートデータを参照してください。