본 포스트는 김영한 님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의를 토대로 작성하였습니다.
파일을 HTML 폼 전송 방식으로 하는 방법은 크게 2가지가 있다.
하나씩 알아보자.
html 폼 데이터를 서버로 전송하는 가장 기본적인 방식이다.
form 태그에 별도의 enctype 옵션이 없으면 브라우저는 해당 요청의 Content-Type을
application/x-www-form-urlencoded로 설정한다.
그러나 파일을 전송하기 위해선 바이너리 데이터를 전송해야 한다. 그리고 문자를 전송하는 이 방식으로는 바이너리 데이터를 전송하기 어렵다. 또한 파일 전송 외에 이름, 나이 등 다른 데이터들도 함께 보내는 경우 내용을 구분하기 위한 방법이 필요하다.
form 태그에서 사용하려면 enctype="multipart/form-data"
를 지정해야 한다.
위 그림처럼 요청을 multipart로 보낼 시 하이픈과 랜덤한 문자열을 기준으로 데이터들이 나누어 전송하게 된다.
그럼 이제 Spring에서 어떻게 multipart 요청을 받아들이고 처리할 수 있는지 알아보자.
먼저 DispatcherServlet에서 들어온 요청이 multipart 요청인지 확인한다.
이후 MultipartResolver를 호출하여 해당 request를 MultipartHttpServletRequest로 변환하여 반환한다.
이 MultipartHttpServletRequest는 Multipart 요청을 처리하기 위한 좀 더 다양한 기능을 제공한다.
그 다음은 매핑된 핸들러 찾기, 핸들러 어댑터 호출 등 기존의 요청 처리 방식과 동일하다.
컨트롤러 단에서는 어떻게 요청을 처리하는지 알아보자.
//controller
@RequestMapping("/servlet/v1")
public class ServletUploadControllerV1 {
@GetMapping("/upload")
public String newFile() {
return "upload-form";
}
@PostMapping("/upload")
public String saveFileV1(HttpServletRequest request) throws
ServletException, IOException {
log.info("request={}", request);
String itemName = request.getParameter("itemName");
log.info("itemName={}", itemName);
Collection<Part> parts = request.getParts();
log.info("parts={}", parts);
return "upload-form";
}
}
가장 기본적인 HttpServletRequest를 사용하는 방법이다.
request.getParts()
를 호출하면 Part라는 객체를 담은 컬렉션이 반환되고 이 Part들 안에 나뉜 데이터들이 들어오게 된다.
@PostMapping("/upload")
public String saveFileV1(HttpServletRequest request) throws
ServletException, IOException {
log.info("request={}", request);
String itemName = request.getParameter("itemName");
log.info("itemName={}", itemName);
Collection<Part> parts = request.getParts();
log.info("parts={}", parts);
for (Part part : parts) {
log.info("==== PART ====");
log.info("name={}", part.getName());
Collection<String> headerNames = part.getHeaderNames();
for (String headerName : headerNames) {
log.info("header {}: {}", headerName,
part.getHeader(headerName));
}
//편의 메서드
//content-disposition; filename log.info("submittedFileName={}", part.getSubmittedFileName()); log.info("size={}", part.getSize()); //part body size
//데이터 읽기
InputStream inputStream = part.getInputStream();
String body = StreamUtils.copyToString(inputStream,
StandardCharsets.UTF_8);
log.info("body={}", body);
//파일에 저장하기
if (StringUtils.hasText(part.getSubmittedFileName())) {
String fullPath = fileDir + part.getSubmittedFileName();
log.info("파일 저장 fullPath={}", fullPath); part.write(fullPath);
}
}
Part의 값을 가져오는 방법은 다음과 같다.
간단히 보면 다음과 같은 기능들을 사용할 수 있다.
part.write()의 경우 인자로 파일 저장 디렉토리를 넘겨주면 된다.
@PostMapping("/upload")
public String saveFile(@RequestParam String itemName,
@RequestParam MultipartFile file, HttpServletRequest
request) throws IOException {
log.info("request={}", request);
log.info("itemName={}", itemName);
log.info("multipartFile={}", file);
if (!file.isEmpty()) {
String fullPath = fileDir + file.getOriginalFilename();
log.info("파일 저장 fullPath={}", fullPath);
file.transferTo(new File(fullPath));
}
return "upload-form";
}
스프링의 MultiPartFile을 사용하면 상당히 코드가 짧아진다. 먼저 인자에 MultipartFile이라는 객체로 파일을 받아올 수 있으며 이를 이용하면 된다.