일반적으로 HTML 폼 전송 방식에는 2가지 종류가 있다.
- application/x-www-form-urlencoded
- 요청 HTTP 헤더에 Content-Type: application/x-www-form-urlencoded을 추가하여 전송하는 방식으로 HTML 폼 데이터를 서버로 전송하는 기본적인 방법이다.
- 이는 폼에 입력한 항목을 쿼리파라미터 형식과 같이(username=hbb&age=20) &로 구분하고 이를 HTTP 바디에 문자로 전송하는 방식이다.
- 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 |