본문 바로가기
공부 기록

[REACT x SPRING BOOT] Axios + multipart/form-data 깔끔하게 받기 (파일 + 게시물 동시에 받기)

by 타태 2022. 3. 14.


2022.02.22 - [실전 공부/AWS&Docker&Linux] - [Docker] Layered jar를 이용한 스프링 부트 Docker build 최적화

[Docker] Layered jar를 이용한 스프링 부트 Docker build 최적화

스프링 부트 메이븐 플러그인 공식 문서 Spring Boot Maven Plugin Documentation While you may start your Spring Boot application very easily from your test (or test suite) itself, it may be desirable..

ktae23.tistory.com



첨부 파일이 있는 게시물 또는 이와 유사한 데이터를 요청에 실어 보낼때 우리는 Multipart/form-data를 사용한다.
이때 파일을 제외한 파라미터가 많지 않다면 @RequestParam으로 함께 받곤 한다.

파일이 없다면 form으로 보내 @ModelAtrributes로 객체를 매핑하거나 JSON으로 보내고 @RequestBody 어노테이션을 사용하면 스프링의 메세지 컨버터가 인자값으로 명시한 객체의 클래스에 접근하여 프로퍼티 접근법을 이용해 JSON 매핑을 지원한다.

보통 검색을 해보면 각각의 값을 form에 하나 하나 append하고 같이 전송할 파일을 추가하여 전송하는 게 일반적인 예제이다.
이럴 경우 form에 추가된 값이 각각의 요청 바디의 파트를 차지하게 된다.
때문에 하나~~하나 @RequestParam으로 요청 파라미터를 꺼내오거나 파라미터를 맵으로 꺼내든 오브젝트 매퍼를 이용한 래퍼클래스를 작성해야 한다.

하지만 멀티파트로 파일과 함께 보낼 경우에는 파일과 함께 오기 때문에 이 방법이 유효하지 않다.
하지만 나는 게시물 객체 + 파일을 함께 받아서 깔끔하게 처리 하고 싶었다.

그래서 사용한 방법이 @RequestPart이다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {

	/**
	 * Alias for {@link #name}.
	 */
	@AliasFor("name")
	String value() default "";

	/**
	 * The name of the part in the {@code "multipart/form-data"} request to bind to.
	 * @since 4.2
	 */
	@AliasFor("value")
	String name() default "";

	/**
	 * Whether the part is required.
	 * <p>Defaults to {@code true}, leading to an exception being thrown
	 * if the part is missing in the request. Switch this to
	 * {@code false} if you prefer a {@code null} value if the part is
	 * not present in the request.
	 */
	boolean required() default true;

}


Multipart/form-dataHTTP 요청을 보낼때 요청 바디를 열어보면 폼에 포함된 각 요청 파트를 바운더리로 구분하여 보내게 되는데 @RequestPart는 이러한 요청 파트 중 일치하는 파트를 가져와 HttpMessageConverter를 통해 파트의 내용을 전달하여 객체 매핑을 지원해주는 어노테이션이다.
서블릿 3.0부터 지원하며 MultipartFile을 바로 추출하는 용도로도 함께 사용할 수 있다.

또한 어노테이션에 적힌 주석에 의하면 JSONXML과 같은 더 복잡한 콘텐츠가 포함된 파트와 함께 사용될 가능성이 높다고 적혀있다.
덕분에 내가 요청을 깔끔하게 해결 할 수 있었다.

특히 중요한 부분은 이 어노테이션이 요청의 Content-Type 헤더를 고려하기 때문에 Multipart/form-data를 보낼때 그냥 보내서는 안된다는 점이다.

Axios POST 요청 파트(단일 객체)

      const frm = new FormData();
     
      const data = {
        "name": name,
        "gender": gender,
        "birthday": birthday
      }
      frm.append("files", fileRef.current.files[0]);
      frm.append("data", new Blob([JSON.stringify(data)], {
        type: "application/json"
    }));

 

form에 객체 생성에 필요한 값들을 JSON 형식으로 구조화한 뒤 JSON.stringify() 함수를 통해 JSON 문자열로 변환하고 이를 Blob(Binary Large OBject)으로 변환하여 form에 추가, 함께 보낼 파일 역시 form에 추가한다.

그리고 요청을 보내면 JSON 객체가 "data"라는 이름으로 하나의 요청 파트를 차지하게 된다.

 

이때 data를 바로 추가하는 것이 아니라 Blob으로 생성하면서 타입을 "application/json"으로 지정하는 것이 포인트이다.

이렇게 보내야만 해당 요청 파트가 json 타입으로 전송 되기 때문에 메세지 컨버터의 객체 매핑이 지원 된다.

 

추가로 아래 예제처럼 배열로 보내면 컨트롤러에서 객체 리스트로 받을 수 있다. 

 

Axios POST 요청 파트(배열 객체)

   const frm = new FormData();
     
      const data = [
        {
        "name": name,
        "gender": gender,
        "birthday": birthday
        },
        {
        "name": name,
        "gender": gender,
        "birthday": birthday
        }
      ]
      frm.append("files", fileRef.current.files[0]);
      frm.append("data", new Blob([JSON.stringify(data)], {
        type: "application/json"
    }));


그렇다면 HTML5의 기능을 생각하여 JSON Array가 아닌 data라는 이름으로 여러개 보내 보내도 객체 리스트로 받아질까?

2021.07.15 - [실전 공부/HTML5&CSS&JS] - [HTML5] checkbox 여러 개를 form으로 전송하기

[HTML5] checkbox 여러 개를 form으로 전송하기

2021.07.04 - [문제 해결 기록] - [GIT] git pull/push takes forever ( git 무한 로딩 에러 해결) [GIT] git pull/push takes forever ( git 무한 로딩 에러 해결) git을 새로 설치한 뒤 pull 또는 push 등 명령..

ktae23.tistory.com


안타깝게도 이 경우에는 매핑 되지 않는다.
메시지 컨버터는 JSON을 객체로 매핑해주기 때문에 List<ApplyFormDto>와 같은 형식으로 매핑을 하려면 배열 형태로 보내야 한다.


** 아래처럼 바운더리를 기준으로 요청 파트가 나뉜다.
객체에 매핑하고자하는 데이터의 타입을 application/json으로 지정하여 메세지 컨버터의 지원을 받는다.

컨트롤러 인자

단일 매핑 : @RequestPart("data") ApplyFormDto applyForm, @RequestPart MultipartFile file
배열 매핑 : @RequestPart("data") List<ApplyFormDto> applyForm, @RequestPart List<MultipartFile> file


반응형

댓글