HTML에서 폼을 이용한 전송 방식은 2가지가 있다.
1. application/x-www-form-urlencoded
우리가 일반적으로 가장 자주 사용하는 기본적인 방법인데 각 필드의 값은 parameter=value¶meter2=value2의 형태로 넘어가는데,
쿼리스트링을 이용한 파라미터 전송방식과 형태가 유사한 것을 알 수 있다.
업로드에 앞서 우리가 업로드한 데이터가 어떤식으로 넘어가는지 또 어떤식으로 확인하고 활용할 수 있는지 먼저 알아보자.
@Slf4j
@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";
}
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>상품 등록 폼</h2>
</div>
<h4 class="mb-3">상품 입력</h4>
<form th:action method="post" enctype="multipart/form-data">
<ul>
<li>상품명 <input type="text" name="itemName"></li>
<li>파일<input type="file" name="file" ></li>
</ul>
<input type="submit"/>
</form>
</div> <!-- /container -->
</body>
</html>
logging.level.org.apache.coyote.http11=debug
이제 테스트를 해보자
요청 헤더를 확인해보면 Content-Type은 multipart/formdata로 설정되어 있으며, 각 part를 나누는 구분자(boundary)도 확인할 수 있다.
이제 콘솔을 확인해보자
바운더리로 각 part가 구분되어 있으며, 파일 데이터의 경우 바이너리 데이터라 이상한 문자로 로그가 남아있는 것을 확인할 수 있다.
로그를 살펴보면 실제로 Collection타입의 parts변수에 2개의 Part객체가 담겨 있는 것을 확인할 수 있다.
#파일 하나의 최대 크기, 기본1mb
spring.servlet.multipart.max-file-size=1MB
#전체 파일의 총합, 기본 10mb
spring.servlet.multipart.max-request-size=10MB
#기본 true
spring.servlet.multipart.enabled=false
나중에 나올 MultipartFile을 사용하는 것이 더 편하기 때문에 참고만 하자
1. 멀티파트 처리 설정이 켜져 있다면
2. 스프링의 DispatcherServlet은 MultipartResolver를 실행한다.
3. 멀티파트 리졸버는 요청 타입이 multipart인 경우 서블릿 컨테이너가 전달하는 HttpServletRequest를 MultipartHttpServletRequest로 변환후 이를 반환한다.
- MultipartHttpServletRequest는 HttpServletRequest의 자식 인터페이스로 멀티파트 관련 추가 기능을 제공한다.
4. 멀티파트 리졸버는 MultipartHttpServletRequest의 구현체인 StandardMultipartHttpServletRequest를 반환하고, 이를 이용해 멀티파트와 관련된 기능을 사용할 수 있게 된다.
이제 실제 파일을 업로드해보자
#파일 저장 경로
file.dir=D:/SpringStudy/springmvc2/upload/src/main/resources/uploadfiles/
@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {
//파일 경로
@Value("${file.dir}")
private String fileDir;
@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);
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();
// 바이너리 데이터를 문자로 바꿀 경우에는 CharSet을 설정해주자!
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);
}
inputStream.close();
}
return "upload-form";
}
}
이제 테스트를 해보면
설정한 경로에 파일이 저장되어 있는 것을 확인할 수 있다!!
이상으로 서블릿에서 제공하는 part를 통해 일일이 업로드를 구현하는 것보다 편하게 업로드를 구현할 수 있었지만 for문이나 if문을 통해 파일 part만 구분해서 업로드를 구현하기 위한 추가적인 코드가 필요했다. 이번에는 스프링에서 제공하는 기능을 통해 업로드를 구현하는 방법을 알아보자!
@Slf4j
@Controller
@RequestMapping("/spring")
public class SpringUploadController {
@Value("${file.dir}")
private String fileDir;
@GetMapping("/upload")
public String newFile() {
return "upload-form";
}
@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";
}
}