스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - (57) 스프링 부트 - 서블릿과 파일 업로드
인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.
서블릿과 파일 업로드 1
먼저 서블릿을 통한 파일 업로드를 코드와 함께 알아보자.

hello.upload.controller.ServletUploadControllerV1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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";
}
}
request.getParts() : multipart/form-data 방식에서 각각 나누어진 부분을 받아서 확인할 수 있다.

다음과 같이 나눠져서 들어오기 때문에 parts로 나눠서 알아본다.
templates/upload-form.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!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>
다음과 같이 enctype="multipart/form-data", <input type="file" name="file" >을 사용해서 파일을 넘긴다.
이제 테스트를 해보자.
테스트를 진행하기 전에 로그를 확인하기 위해서 다음과 같이 설정하자.
application.properties
1
2
3
4
5
# 스프링 부트 3.2 이하
logging.level.org.apache.coyote.http11=debug
# 스프링 부트 3.2 이상
logging.level.org.apache.coyote.http11=trace


바이너리 데이터까지 출력 돼서 로그가 엄청 이상한데, 아무튼 위 사진에서 —- 로 파트가 쪼개져서 온 걸 볼 수 있고,
아래에선 parts 가 2개 출력 되는 걸 볼 수 있다.
멀티파트 사용 옵션
업로드 사이즈 제한
1
2
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
큰 파일을 무제한 업로드하게 둘 수는 없으므로 업로드 사이즈를 제한할 수 있다.
사이즈를 넘으면 예외(SizeLimitExceededException)가 발생한다.
max-file-size: 파일 하나의 최대 사이즈, 기본 1MBmax-request-size: 멀티파트 요청 하나에 여러 파일을 업로드 할 수 있는데, 그 전체합 이다. 기본 10MB
spring.servlet.multipart.enabled 끄기
application.properties
1
spring.servlet.multipart.enabled=false
결과 로그
1
2
3
request=org.apache.catalina.connector.RequestFacade@xxx
itemName=null
parts=[]
멀티파트는 일반적인 폼 요청인 application/x-www-form-urlencoded 보다 훨씬 복잡하다.
spring.servlet.multipart.enabled=false하면 서블릿 컨테이너는 멀티파트와 관련된 처리를 하지 않는다.
그래서 결과 로그를 보면 request.getParameter("itemName"), request.getParts()의 결과가 비어 있 다.
spring.servlet.multipart.enabled 켜기
default 가 켜기이다.
1
spring.servlet.multipart.enabled=true #(default = true)
1
2
request=org.springframework.web.multipart.support.StandardMultipartHttpServletRequest itemName=Spring
parts=[ApplicationPart1, ApplicationPart2]
request.getParameter("itemName") 의 결과도 잘 출력 되고, request.getParts()에도 요청한 두 가지 멀티파트의 부분 데이터가 포함된 것을 확인할 수 있다. 이 옵션을 켜면 복잡한 멀티파트 요청을 처리해서 사용할 수 있게 제공한다.
끈 로그 킨 로그를 비교해 보면
HttpServletRequest객체가 RequestFacade → StandardMultipartHttpServletRequest로 변한 것을 확인할 수 있다.
참고
spring.servlet.multipart.enabled옵션을 켜면 스프링의 DispatcherServlet 에서 멀티파트 리졸버(MultipartResolver)를 실행한다.
멀티파트 리졸버는 멀티파트 요청인 경우 서블릿 컨테이너가 전달하는 일반적인HttpServletRequest를MulipartHttpServletRequest로 변환해서 반환한다.MulipartHttpServletRequest는HttpServletRequest의 자식 인터페이스이고, 멀티파트와 관련된 추가 기능을 제공한다.
스프링이 제공하는 기본 멀티파트 리졸버는MultipartHttpServletRequest인터페이스를 구현한StandardMultipartHttpServletRequest를 반환한다.
이제 컨트롤러에서HttpServletRequest대신에MultipartHttpServletRequest를 주입 받을 수 있는데, 이것을 사용하면 멀티파트와 관련된 여러가지 처리를 편리하게 할 수 있다. 그런데 추후 배울MultipartFile라는 것을 사용하는 것이 더 편하기 때문에MultipartHttpServletRequest를 잘 사용하지는 않는다.
서블릿과 파일 업로드 2
이제 파일이 서버에 오긴 왔으니까 실제 경로에 저장해 보자.
먼저 파일을 업로드를 하려면 실제 파일이 저장되는 경로가 필요하다.
만들 경로에 실제 폴더를 만들어 두자.
그리고 경로를 application.properties에 등록하자.
application.properties
1
file.dir=D:\\test\\study\\file\\
윈도우는 \\으로 폴더를 구분해야 한다.
리눅스나 맥은 그냥 /data/storage/ 이런 식으로 /로 구분 하면 된다.
주의
- 꼭 해당 경로에 실제 폴더를 만들어 두자.
application.properties에 설정할 때 마지막/슬래시(윈도우:\\)가 포함된 것에 주의하자.
hello.upload.controller.ServletUploadControllerV2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@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();
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);
}
}
return "upload-form";
}
}
1
2
@Value("${file.dir}")
private String fileDir;
application.properties에서 설정한 경로를 주입한다.
멀티파트 형식은 전송 데이터를 하나하나 각각 parts로 나누어 전송한다. parts에는 이렇게 나누어진 데이터가 각각 담긴다.
서블릿이 제공하는 Part는 멀티파트 형식을 편리하게 읽을 수 있는 다양한 메서드를 지원한다.
Part 주요 메서드
part.getSubmittedFileName(): 클라이언트가 전달한 파일 명part.getInputStream(): Part의 전송 데이터를 읽을 수 있다.part.write(...): Part를 통해 전송된 데이터를 저장할 수 있다.
이제 실행해 보자.
결과
다음 내용을 전송했다.
itemName: 상품Afile: 스크린샷.png


다음과 같이 로그도 잘 나오고, 파일도 잘 저장된 걸 볼 수 있다.
파일 사이즈 라던지, getSubmittedFileName으로 알아낸 파일 명도 잘 나온다.
참고
큰 용량의 파일 업로드 테스트를 할 때는 로그가 너무 많이 남아서 다음 옵션은 끄는게 좋다.logging.level.org.apache.coyote.http11=trace
다음 부분도 파일의 바이너리 데이터를 모두 출력하므로 끄는 것이 좋다.log.info("body={}", body);
서블릿이 제공하는 Part는 편하기는 하지만, HttpServletRequest를 사용해야 하고,
추가로 파일 부분만 구분하려면 여러가지 코드를 넣어야 한다.
다음에는 스프링이 이 부분을 얼마나 편리하게 제공하는지 확인해보자.
댓글남기기