본문 바로가기
Spring

[Spring] MultipartFile 바인딩

by 감자b 2024. 12. 27.

일반적으로 HTML 폼 전송 방식에는 2가지 종류가 있다.

  1. application/x-www-form-urlencoded
    • 요청 HTTP 헤더에 Content-Type: application/x-www-form-urlencoded을 추가하여 전송하는 방식으로 HTML 폼 데이터를 서버로 전송하는 기본적인 방법이다.
    • 이는 폼에 입력한 항목을 쿼리파라미터 형식과 같이(username=hbb&age=20) &로 구분하고 이를 HTTP 바디에 문자로 전송하는 방식이다.
  2. multipart/form-data
    • 만약 위에서 이름, 나이 외에 첨부파일을 전송해야 한다면 어떻게 해야할까?
    • 첨부 파일의 경우 바이너리로 전송해야 하므로 문자와 바이너리를 동시에 전송을 해야하는 상황이 발생한다.
    • 따라서 HTTP는 multipart/form-data 라는 전송 방식을 제공
    • 해당 방식을 사용하려면 Form 태그에 enctype="multipart/form-data" 를 지정해야 하고 지정 시 다른 종류의 여러 파일과 폼을 함께 전송 가능하다.
    • (<form action=”/save” method=”post” enctype=”multipart/form-data”>)

 

포스트맨을 이용해서 name,age,텍스트파일,이미지를 전송하였다.

# header 부분

----------------------------xxx
Content-Disposition: form-data; name="name"

hbb
----------------------------xxx
Content-Disposition: form-data; name="age"

20
----------------------------xxx
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

hello

----------------------------xxx
Content-Disposition: form-data; name="image"; filename="xxx.png"
Content-Type: image/png

PNG

logging.level.org.apache.coyote.http11=trace 를 application.properties에 적용 시 폼의 입력 결과로 생성된 HTTP 메시지를 로그로 출력 가능하다.

생성된 HTTP 메시지를 보면 각각의 전송 항목이 구분되어 있고,Content-Disposition 이라는 항목별 헤더 내부에 정보가 있다.

폼의 일반 데이터는 문자가 전송되고, 파일의 경우 파일 이름, Content-Type이 추가되고 바이너리 데이터가 전송되는 것을 확인할 수 있다.

즉 multipart/form-data는 각각의 항목을 구분, 한 번에 전송한다.


멀티파트 옵션

spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB

큰 파일을 업로드하는 것을 제한하기 위해 다음과 같은 옵션을 사용할 수 있다.

max-file-size : 파일 하나의 최대 사이즈로 기본 1MB로 지정한다는 의미이다.

max-request-size : 멀티파트 요청 하나에 여러 파일 업로드가 가능한데, 그 전체 합을 뜻한다.

위 옵션으로 지정한 사이즈를 초과하는 경우 SizeLimitExceededException 발생한다.

spring.servlet.multipart.enabled=false

멀티파트는 일반적인 폼 요청에 비해 매우 복잡하므로 위와 같이 멀티파트 처리를 하지 않도록 제한할 수도 있으며

기본 설정은 true이다.


스프링에서 파일 업로드

스프링은 MultipartFile 인터페이스로 멀티파트 파일을 편리하게 사용할 수 있도록 한다.

@Slf4j
@RestController
@RequestMapping("/file")
public class FileUploadController {

    @PostMapping("/save")
    public String save(@ModelAttribute MemberDTO memberDTO, @RequestParam MultipartFile file, @RequestParam MultipartFile image) {
        log.info("memberDTO = {}", memberDTO);
        log.info("multipartFile file = {}", file);
        log.info("multipartFile image = {}", image);
        // 저장 로직
        return "ok";
    }
}

파라미터 타입으로 MultipartFile을 한 뒤 업로드하는 폼의 name에 맞춰 @RequestParam을 적용하면 된다. @ModelAttribute 사용 시 MultipartFile이 객체 내부에 있어도 바인딩이 가능하다.

@Setter
@ToString
public class MemberDTO {
    private String name;
    private Integer age;
    private MultipartFile file;
    private MultipartFile image;
}

참고로 setter가 있어야 @ModelAttribute가 파라미터를 객체에 바인딩 해준다.

@Slf4j
@RestController
@RequestMapping("/file")
public class FileUploadController {

    @PostMapping("/save")
    public String save(@ModelAttribute MemberDTO memberDTO) throws ServletException, IOException {
        log.info("memberDTO = {}", memberDTO);
        // 저장 로직
        return "ok";
    }
}

@RequestPart

만약 API로 통신하는 경우에는 어떻게 해야할까?

위 코드에서 @ModelAttribute 대신 @RequestBody로 데이터를 받을 시 다음과 같은 에러가 나온다.

{
	"type": "about:blank",
	"title": "Unsupported Media Type",
	"status": 415,
	"detail": "Content-Type 'multipart/form-data;boundary=-----xxx;charset=UTF-8' is not supported.",
	"instance": "/file/save"
}

이는 데이터가 multipart/form-data 요청으로 오는 데이터를 application/json 이나 xml 형식으로 받아서 매핑하려해서 생기는 것이었다.

그렇다면 JSON 형식의 DTO와 Multipart 파일을 함께 받으려면 어떻게 해야할까?

 

Multipart 파일을 받을 수 있는 또 다른 방법으로 스프링은 @RequestPart 애노테이션을 제공한다.

먼저 스프링 공식문서에는 @RequestPart에 대해 다음과 같이 설명한다.

Note that @RequestParam annotation can also be used to associate the part of a "multipart/form-data" request with a method argument supporting the same method argument types. The main difference is that when the method argument is not a String or raw MultipartFile / Part, @RequestParam relies on type conversion via a registered Converter or PropertyEditor while RequestPart relies on HttpMessageConverters taking into consideration the 'Content-Type' header of the request part. RequestParam is likely to be used with name-value form fields while RequestPart is likely to be used with parts containing more complex content e.g. JSON, XML).

두 애노테이션 모두 MultipartFile을 받을 때 사용이 가능한데 다음과 같은 차이가 있다.

  • @RequestParam
    • 메서드 인수가 String, MultipartFile, Part가 아닌 경우, Spring은 ConversionService나 PropertyEditor를 사용하여 요청 파라미터를 적절한 타입으로 변환
    • 주로 이름-값 양식 필드와 함께 사용
  • @RequestPart
    • 메서드 인수가 String, MultipartFile/Part가 아니라면 요청 헤더의 Content-Type에 맞는 HttpMessageConverter로 변환
    • 요청이 multipart/form-data 형식이라면 MultipartResolver가 동작하여 파일 데이터를 MultipartFile로 변환
    • 주로 JSON, XML 등 더 복잡한 내용을 포함하는 부분과 함께 사용

따라서 JSON과 MultipartFile을 함께 받으려면 form-data로 전송된 JSON과 파일을 @RequestPart로 받아야 한다.

@Slf4j
@RestController
@RequestMapping("/file")
public class FileUploadController {

    @PostMapping("/save")
    public String save(@RequestPart MemberDTO memberDTO, @RequestPart MultipartFile file, @RequestPart MultipartFile image) {
        log.info("memberDTO = {}", memberDTO);
        log.info("file = {}", file);
        log.info("image = {}", image);
        return "ok";
    }
}

참고

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 | 김영한 - 인프런

김영한 | 웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습

www.inflearn.com

 

 

RequestPart (Spring Framework 6.2.1 API)

Annotation that can be used to associate the part of a "multipart/form-data" request with a method argument. Supported method argument types include MultipartFile in conjunction with Spring's MultipartResolver abstraction, jakarta.servlet.http.Part in conj

docs.spring.io

 

'Spring' 카테고리의 다른 글

[Spring] @Transactional  (0) 2024.12.27
[Spring] JDBC, 커넥션 풀, 트랜잭션 추상화  (0) 2024.12.27
[Spring] 스프링의 예외 처리  (0) 2024.12.27
[Spring] 필터와 인터셉터  (0) 2024.12.27
[Spring] Bean Validation  (0) 2024.12.27