빙응의 공부 블로그
[Spring]스프링 MVC 2편 - 파일 업로드 본문
📝파일업로드
일반적으로 사용하는 HTML Form을 통한 파입 업로드를 이해하려면 먼저 폼을 전송하는 다음 두 가지 방식의 차이를 이해해야 한다.
application/x-www-form-urlencoded 방식은 HTML 폼 데이터를 서버로 전송하는 가장 기본적인 방법이다.
Form 태그에 별도의 enctype 옵션이 없으면 브라우저는 요청 http 메시지 헤더에 다음 내용을 추가한다.
Cotent-Type : application/x-www-form-urlencoded
파일을 업로하드 하려면 파일은 문자가 아니라 바이너리 데이터를 전송해야 한다. 문자를 전송하는 이 방식으로 파일을
전송하기는 어렵다. 그리고 또 한가지 문제가 있는데, 보통 폼을 전송할 때 파일만 전송하는 것이 아니다.
게시판으로 예를 들어보자면
제목, 이름, 내용, 첨부파일
이렇게 파일과 함께 전송해야한다. 이것은 문자와 바이너리를 동시에 전송하는 상황이다.
이 문제를 해결하기 위해 HTTP는 multipart/form-data라는 전송 방식을 제공한다.
해당 방식을 사용하려면 Form 태그에 별도의 enctype="multipart/form-data"를 지정해야 한다.
multipart/form-data 방식은 다른 종류의 여러 파일을 폼의 내용과 함께 전송할 수 있다.
📝스프링 파일
스프링에서 파일 업로드를 하는법을 보자
간단한 예제
@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";
}
<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>
하나하나씩 살펴보자
Collection<Part> parts = request.getParts();
log.info("parts={}", parts);
request.getParts() : multipart/form-data 전송 방식에서 각각 나누어진 부분을 받아서 확인할 수 있다.
<form th:action method="post" enctype="multipart/form-data">
multipart/form-data 방식을 사용하려면 해당처럼 enctype을 지정해줘야 한다.
🚩멀티파트 사용 옵션
업로드 사이즈 제한
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
큰 파일을 무제한 업로드하게 둘 수는 없다 그러므로 제한을 해야한다.
spring.servlet.multipart.enabled
멀티파트는 일반적인 폼 요청인 application/x-www-form-urlencoded 보다 훨씬 복잡하
spring.servlet.multipart.enabled 옵션을 끄면 서블릿 컨테이너는 멀티파트와 관련된 처리를 하지 않는다.
그래서 결과 로그를 보면 request.getParameter("itemName") , request.getParts() 의 결과가 비어있다.
spring.servlet.multipart.enabled=false
결과 로그
request=org.apache.catalina.connector.RequestFacade@xxx
itemName=null
parts=[]
spring.servlet.multipart.enabled=true (기본 true)
이 옵션을 켜면 스프링 부트는 서블릿 컨테이너에게 멀티파트 데이터를 처리하라고 설정한다.
결과 로그
request=org.springframework.web.multipart.support.StandardMultipartHttpServletRe
quest
itemName=Spring
parts=[ApplicationPart1, ApplicationPart2]
참고!
spring.servlet.multipart.enabled 옵션을 켜면 스프링의 DispatcherServlet에서 멀티 파트 리졸버를 실행한다.
멀티파트 리졸버는 멀티파트 요청인 경우 서블릿 컨테이너가 전달하는 일반적인 HttpServletRequest를
MultipartHttpServletRequest로 변환해서 반환한다.
MultipartHttpServletRequest 는 HttpServletRequest 의 자식 인터페이스이고, 멀티파트와 관련된 추가 기능을 제공한다
스프링이 제공하는 기본 멀티파트 리졸버는 MultipartHttpServletRequest 인터페이스를 구현한 StandardMultipartHttpServletRequest 를 반환한다
이제 컨트롤러에서 HttpServletRequest 대신에 MultipartHttpServletRequest 를 주입받을 수 있는데, 이것을 사용하면 멀티파트와 관련된 여러가지 처리를 편리하게 할 수 있다. 그런데 이후 강의에서 설명 할 MultipartFile 이라는 것을 사용하는 것이 더 편하기 때문에 MultipartHttpServletRequest 를 잘 사용하지는 않는다. 더 자세한 내용은 MultipartResolver 를 검색해보자
📝서블릿과 파일 업로드
서블릿이 제공하는 Part에 대해 알고보고 실제 파일을 서버에 업로드 해보자
먼저 파일 업로드를 하려면 실제 파일을 저장되는 경로가 필요하다.
application.properties
file.dir=파일 업로드 경로 설정(예): /Users/kimyounghan/study/file/
PART 사용 예제
@Value("${file.dir}")
private String fileDir;
@GetMapping("/upload")
public String newFile() {
return "upload-form";
}
@PostMapping("/upload")
public String saveFileV2(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());
//PART 각각의 헤더들의 값을 출력
Collection<String> headerNames = part.getHeaderNames();
for (String headerName : headerNames) {
log.info("header {}: {}", headerName, part.getHeader(headerName));
}
//편의 메서드
//content-disposition은 헤더에 여러개가 들어가나 편의 메소드로 편하게 출력가능
//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";
}
멀티파트 형식은 전송 데이터를 각각 부분(Part)으로 나누어 전송한다. parts에는 이렇게 나누어진 각각의 데이터가 담긴다
서블릿이 제공하는 PART는 멀티파트 형식을 편리하게 읽을 수 있는 다양한 메서드를 제공한다.
PART 주요 메서드
- part.getSubmittedFileName() : 클라이언트가 전달한 파일명
- part.getInputStream(): Part의 전송 데이터를 읽을 수 있다.
- part.write(...): Part를 통해 전송된 데이터를 저장할 수 있다.
📝스프링과 파일 업로드
스프링은 MultipartFile 이라는 인터페이스로 멀티파트 파일을 매우 편리하게 지원한다.
@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";
}
코드를 보면 스프링 답게 딱 필요한 부분만 코드로 작성하면 된다.
@RequestParam MultipartFile file
업로드하는 HTML Form의 name에 맞추어 @RequestParam을 적용하면 된다. 추가로 @ModelAttribute에서도
MultipartFile을 동일하게 사용할 수 있다.
MultipartFile 주요 메서드
file.getOriginalFilename() : 업로드 파일 명
file.transferTo(...) : 파일 저장
'Spring > 인프런_개념' 카테고리의 다른 글
[Spring]실전! 스프링 부트와 JPA 활용1 - 설계의 주의점 (0) | 2024.03.11 |
---|---|
[Spring]스프링 MVC 2편 - 스프링 타입 컨버터 (1) | 2024.02.09 |
[Spring]스프링 MVC 2편 - API 오류 처리 (0) | 2024.02.06 |
[Spring]스프링 MVC 2편 - 예외 처리와 오류 페이지 (1) | 2024.02.05 |
[Spring]스프링 MVC 2편 - 로그인 처리(필터, 인터셉터) (0) | 2024.02.04 |