일반적으로 사용하는 html Form을 통한 파일업로드를 이해하기 위해서는 다음 두 가지의 http 미디어 타입의 차이를 이해해야 한다.
application/x-www-form-urlencoded : 폼 데이터를 서버로 전송하기 위한 가장 기본적인 방법 (Content-Type)multipart/form-data: 파일과 문자 데이터를 동시에 전송해야하는 상황 (form 태그에 별도의 enctype="multipart/form-data" 지정해야함.)html에서 폼을 통해 multipart/form-data로 데이터가 넘어온다면 서블릿 의 HttpServletRequest는 request.getParts()로 하여금 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);
return "upload-form";
}
어플리케이션 설정에서 우리는 넘어올 파일의 용량을 제한할 수 있다. 용량을 넘어 파일 데이터가 서버 내로 들어올 경우 SizeLimitExceededException 예외가 발생한다.
spring.servlet.multipart.max-file-size=1MB // 파일 하나의 최대 사이즈 (기본 1MB)
spring.servlet.multipart.max-request-size=10MB // 멀티파트 요청 하나에서 여러 파일에 대한 전체 합 용량 (기본 10MB)
멀티파트는 일반적인 폼 요청에 비해 패킷 구성이 굉장히 복잡하므로 스프링은 multipart자체를 다루지 않는 옵션도 제공한다.(spring.servlet.multipart.enabled) 이 옵션에 false(기본=true)를 주면 서블릿 컨테이너는 멀티파트와 관련된 처리를 절대 하지 않는다.
이 옵션이 true인 동안에는 DispatcherServlet은 멀티파트 리졸버를 항상 실행한다. 멀티파트 리졸버는 요청이 멀티파트일 경우 여러가지 다른 처리를 진행한다(HttpServletRequest -> MultipartHttpServletRequest로 주입 : 멀티파트와 관련된 추가 기능 제공)
// application.properties
file.dir=파일 업로드 경로 설정
어플리케이션 설정에서 file.dir경로를 설정하고 서블릿을 이용하여 우리는 업로드된 파일 데이터를 읽어 해당 경로에 저장할 수 있다.
@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
log.info("SubmittedFileName", part.getSubmittedFileName());
log.info("size={}", part.getSize());
//데이터 읽기
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";
}
request.getParts()로 하여금 멀티파트 데이터들을 받을 수 있다. 하나의 멀티파트 요청에 대해 여러 데이터가 존재할 수 있으므로 Part라는 객체를 Collections에 담아준다.
여기서 파일에 해당하는 Part를 찾기 위해 여러 메서드를 활용할 수 있다.
part.getName() : 멀티파트 요청에서 현재 파트의 이름을 반환한다. 보통 name 속성값을 가져오는 데 사용된다.part.getHeaderNames() : 현재 파트의 모든 헤더 이름을 Collection<String> 형태로 반환한다. 이를 통해 해당 파트에 포함된 모든 헤더의 이름을 확인할 수 있다.part.getHeader(String name) : 주어진 헤더 이름에 해당하는 헤더 값을 반환한다. 예를 들어, part.getHeader("Content-Type")을 호출하면 현재 파트의 Content-Type 헤더 값을 얻을 수 있다.part.getSubmittedFileName() : 클라이언트가 업로드한 파일의 원본 이름을 반환한다. 주로 파일 업로드 시 사용되며, 사용자가 선택한 파일의 이름을 확인할 수 있다.part.getInputStream() : 현재 파트의 내용을 읽을 수 있는 InputStream 객체를 반환한다. 파일 데이터나 텍스트 데이터를 읽어오기 위해 사용되며, 이 스트림을 통해 파일 내용을 처리할 수 있다.스프링으로 하여금 이러한 멀티파트 데이터를 조금 더 편리하게 이용할 수 있다. @RequestParam MultipartFile file으로 하여금 멀티파트에서 파일을 따로 받을 수 있다.
주요 메서드는 다음과 같다.
file.getOriginalFilename() : 업로드 파일 명file.transferTo(...) : 파일 저장