Loading...
Spring Framework Reference Documentation 7.0.2의 Multipart Content의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Multipart Data에서 설명한 것처럼, ServerWebExchange는 multipart
콘텐츠에 대한 액세스를 제공합니다. controller에서 (예를 들어 browser로부터 오는) 파일 업로드 폼을
처리하는 가장 좋은 방법은 다음 예제에서 보듯이 command 객체에 대한 데이터 바인딩을 통해서입니다:
1class MyForm { 2 3 private String name; 4 5 private FilePart file; 6 7 // ... 8 9} 10 11@Controller 12public class FileUploadController { 13 14 @PostMapping("/form") 15 public String handleFormUpload(MyForm form, BindingResult errors) { 16 // ... 17 } 18 19}
1class MyForm( 2 val name: String, 3 val file: FilePart 4) 5 6@Controller 7class FileUploadController { 8 9 @PostMapping("/form") 10 fun handleFormUpload(form: MyForm, errors: BindingResult): String { 11 // ... 12 } 13 14}
RESTful 서비스 시나리오에서 non-browser 클라이언트로부터 multipart 요청을 제출할 수도 있습니다. 다음 예제는 JSON과 함께 파일을 사용하는 방법을 보여줍니다:
1POST /someUrl 2Content-Type: multipart/mixed 3 4--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp 5Content-Disposition: form-data; name="meta-data" 6Content-Type: application/json; charset=UTF-8 7Content-Transfer-Encoding: 8bit 8 9{ 10 "name": "value" 11} 12--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp 13Content-Disposition: form-data; name="file-data"; filename="file.properties" 14Content-Type: text/xml 15Content-Transfer-Encoding: 8bit 16... File Data ...
다음 예제에서 보듯이 @RequestPart를 사용하여 개별 part에 액세스할 수 있습니다:
1@PostMapping("/") 2public String handle(@RequestPart("meta-data") Part metadata, // (1) 3 @RequestPart("file-data") FilePart file) { // (2) 4 // ... 5}
| 1 | 메타데이터를 얻기 위해 @RequestPart를 사용합니다. |
| 2 | 파일을 얻기 위해 @RequestPart를 사용합니다. |
1@PostMapping("/") 2fun handle( 3 @RequestPart("meta-data") metadata: Part, // (1) 4 @RequestPart("file-data") file: FilePart // (2) 5): String { 6 // ... 7}
| 1 | 메타데이터를 얻기 위해 @RequestPart를 사용합니다. |
| 2 | 파일을 얻기 위해 @RequestPart를 사용합니다. |
raw part 콘텐츠를 (예를 들어 JSON으로 — @RequestBody와 유사하게) 역직렬화하려면,
다음 예제에서 보듯이 Part 대신 구체적인 대상 객체를 선언할 수 있습니다:
1@PostMapping("/") 2public String handle(@RequestPart("meta-data") MetaData metadata) { // (1) 3 // ... 4}
| 1 | 메타데이터를 얻기 위해 @RequestPart를 사용합니다. |
1@PostMapping("/") 2fun handle(@RequestPart("meta-data") metadata: MetaData): String { // (1) 3 // ... 4}
| 1 | 메타데이터를 얻기 위해 @RequestPart를 사용합니다. |
@RequestPart는 jakarta.validation.Valid 또는 Spring의
@Validated 어노테이션과 함께 사용할 수 있으며, 이는 Standard Bean Validation이 적용되도록 합니다.
검증 오류는 400 (BAD_REQUEST) 응답을 발생시키는 WebExchangeBindException으로 이어집니다.
이 예외에는 오류 세부 정보가 담긴 BindingResult가 포함되며, 비동기 래퍼와 함께 인자를 선언한 다음
오류 관련 오퍼레이터를 사용하여 controller 메서드에서 처리할 수도 있습니다:
1@PostMapping("/") 2public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) { 3 // use one of the onError* operators... 4}
1@PostMapping("/") 2fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String { 3 // ... 4}
다른 매개변수에 @Constraint 어노테이션이 있어서 메서드 검증이 적용되는 경우,
대신 HandlerMethodValidationException이 발생합니다. Validation 섹션을 참고하세요.
모든 multipart 데이터에 MultiValueMap으로 액세스하려면, 다음 예제에서 보듯이 @RequestBody를
사용할 수 있습니다:
1@PostMapping("/") 2public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { // (1) 3 // ... 4}
| 1 | @RequestBody를 사용합니다. |
1@PostMapping("/") 2fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { // (1) 3 // ... 4}
| 1 | @RequestBody를 사용합니다. |
PartEventmultipart 데이터에 순차적으로, 스트리밍 방식으로 액세스하려면, Flux<PartEvent> (또는 Kotlin에서는
Flow<PartEvent>)와 함께 @RequestBody를 사용할 수 있습니다.
multipart HTTP 메시지의 각 part는 최소한 하나의 PartEvent를 생성하며, 여기에는 헤더와 part의 콘텐츠가 담긴 버퍼가 모두 포함됩니다.
FormPartEvent를 생성합니다.FilePartEvent 객체를 생성합니다.
파일이 여러 버퍼로 분할될 만큼 충분히 큰 경우, 첫 번째 FilePartEvent 뒤에 후속 이벤트가 이어집니다.예를 들어:
1@PostMapping("/") 2public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { // (1) 3 allPartsEvents.windowUntil(PartEvent::isLast) // (2) 4 .concatMap(p -> p.switchOnFirst((signal, partEvents) -> { // (3) 5 if (signal.hasValue()) { 6 PartEvent event = signal.get(); 7 if (event instanceof FormPartEvent formEvent) { // (4) 8 String value = formEvent.value(); 9 // handle form field 10 } 11 else if (event instanceof FilePartEvent fileEvent) { // (5) 12 String filename = fileEvent.filename(); 13 Flux<DataBuffer> contents = partEvents.map(PartEvent::content); // (6) 14 // handle file upload 15 } 16 else { 17 return Mono.error(new RuntimeException("Unexpected event: " + event)); 18 } 19 } 20 else { 21 return partEvents; // either complete or error signal 22 } 23 })); 24}
| 1 | @RequestBody를 사용합니다. |
| 2 | 특정 part에 대한 마지막 PartEvent는 isLast()가 true로 설정되며,<br>이후에 다음 part에 속하는 추가 이벤트가 이어질 수 있습니다.<br>이는 isLast 프로퍼티를 Flux::windowUntil 오퍼레이터의 프레디케이트로 사용하여,<br>모든 part의 이벤트를 각각 하나의 part에 속하는 윈도우로 분할하는 데 적합하게 만듭니다. |
| 3 | Flux::switchOnFirst 오퍼레이터를 사용하면 폼 필드를 처리하는지<br>파일 업로드를 처리하는지 확인할 수 있습니다. |
| 4 | 폼 필드를 처리합니다. |
| 5 | 파일 업로드를 처리합니다. |
| 6 | 본문 콘텐츠는 메모리 누수를 피하기 위해 완전히 소비, 릴레이 또는 해제되어야 합니다. |
1@PostMapping("/") 2fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = // (1) 3 allPartsEvents.windowUntil(PartEvent::isLast) // (2) 4 .concatMap { 5 it.switchOnFirst { signal, partEvents -> // (3) 6 if (signal.hasValue()) { 7 val event = signal.get() 8 if (event is FormPartEvent) { // (4) 9 val value: String = event.value() 10 // handle form field 11 } 12 else if (event is FilePartEvent) { // (5) 13 val filename: String = event.filename() 14 val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content) // (6) 15 // handle file upload 16 } 17 else { 18 return@switchOnFirst Mono.error(RuntimeException("Unexpected event: $event")) 19 } 20 } 21 else { 22 return@switchOnFirst partEvents // either complete or error signal 23 } 24 } 25 }
| 1 | @RequestBody를 사용합니다. |
| 2 | 특정 part에 대한 마지막 PartEvent는 isLast()가 true로 설정되며,<br>이후에 다음 part에 속하는 추가 이벤트가 이어질 수 있습니다.<br>이는 isLast 프로퍼티를 Flux::windowUntil 오퍼레이터의 프레디케이트로 사용하여,<br>모든 part의 이벤트를 각각 하나의 part에 속하는 윈도우로 분할하는 데 적합하게 만듭니다. |
| 3 | Flux::switchOnFirst 오퍼레이터를 사용하면 폼 필드를 처리하는지<br>파일 업로드를 처리하는지 확인할 수 있습니다. |
| 4 | 폼 필드를 처리합니다. |
| 5 | 파일 업로드를 처리합니다. |
| 6 | 본문 콘텐츠는 메모리 누수를 피하기 위해 완전히 소비, 릴레이 또는 해제되어야 합니다. |
수신된 part 이벤트는 WebClient를 사용하여 다른 서비스로 릴레이할 수도 있습니다.
Multipart Data를 참고하세요.
@RequestAttribute
@RequestBody