우리가 평소 HTML Form을 통해 데이터를 주고 받을 때는 폼 전송 방식을 application/x-www-form-urlencoded 을 사용한다.
이때는 입력 받은 문자열에 대한 처리인데, 만약 파일을 전송할 때는 파일의 바이너리 코드를 전송하고 받을 필요가 있다. 이때는 폼 전송 방식을 multipart/form-data를 사용하면 된다.

이떄 Multipart/form-data는 전송 항목을 각각 구분하여 항목별로 헤더에 추가해준다. 이때 일반 데이터는 각 항목별로 문자가 전송되고, 파일의 경우 파일 이름과 Content-Type이 추가되어 바이너리 데이터가 전송된다.
또한 바이너리 데이터를 전송할 때는 업로드 할 파일의 사이즈 제한이 존재하는데 다음과 같은 옵션을 주어 사이즈를 설정할 수 있다.
spring.servlet.multipart.max-file-size=1MB // 하나의 파일 최대 사이즈 : 1MB (Default)
spring.servlet.multipart.max-request-size=10MB // 전체 파일 최대 사이즈 : 10MB (Default)
서블릿에서는 기본적인 폼 요청보다 복잡한 멀티파트 관련 처리를 온 오프하는 옵션이 제공되는데 다음과 같이 설정할 수 있다
spring.servlet.multipart.enabled = true (기본 true)
이렇게 설정하여 멀티파트 형식을 통해 데이터를 전송 받게 되면 데이터를 하나하나 각각 부분(Part)으로 나누어 전송하게 되는데 parts에는 나누어진 데이터가 각각 담긴다.
Collection<Part> parts = request.getParts();
for (Part part : parts) {
log.info("name={}", part.getName());
}
서블릿의 Part는 멀티파트 형식을 편리하게 읽을 수 있는 메서드를 제공한다.
part.getSubmittedFileName() : 클라이언트가 전달한 파일명
Collection<Part> parts = request.getParts();
for (Part part : parts) {
log.info("submittedFileName={}", part.getSummitedFileName())
}
part.getInputStream() : Part의 전송 데이터 읽기
InputStream inputStream = part.getInputStream();
String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
part.write() : Part를 통해 전송된 데이터 저장
if (StringUtils.hasText(part.getSubmittedFileName())) {
String fullPath = fileDir + part.getSubmittedFileName();
part.write(fullPath)
}
파일 경로의 경우 application.properties에 넣고 쓰면 편리하게 사용할 수 있다.
//application.properties
file.dir=/Users/sanghoon/Desktop/file/
// controller
@Value("${file.dir}")
private String fileDir;
Servlet의 Part를 사용하면 multipart/form-data 방식을 조금 더 편리하게 다룰 수는 있지만 HttpServletRequest를 사용해야하고, 추가로 파일 부분만 구분하려면 여러가지 코드를 더 넣어줘야 한다. 하지만 스프링은 MultipartFile이라는 인터페이스를 사용하면 멀티파트 파일을 매우 편리하게 지원한다.
file.getOriginalFilename()
public String saveFile(@RequestParam String itemName,
@RequestParam MultipartFile file, HttpServletRequest request) throws IOException {
if(!file.isEmpty()) {
String full Path = fileDir + file.getOriginalFilename();
}
}
file.transferTo()
file.transferTo(new File(fullPath));
고객이 업로든한 파일명을 그대로 서버에 저장할 경우 다른 고객이 동일한 이름으로 저장한 경우 덮어씌워질 가능성이 있다. 따라서 서버 내부에 저장하는 이름은 UUID같은 겹치지 않는 값을 통해 저장해야 한다.
uploadFileName : 고객이 업로드한 파일명
public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()) {
return null;
}
String originalFilename = multipartFile.getOriginalFilename();
}
storeFileName : 서버 내부에서 관리하는 파일명
public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()) {
return null;
}
String originalFilename = multipartFile.getOriginalFilename();
String storeFileName = createStoreFileName(originalFilename);
multipartFile.transferTo(new File(getFullPath(storeFileName)));
return new UploadFile(originalFilename, storeFileName);
}
private String extractExt(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
}
private String createStoreFileName(String originalFilename) {
String ext = extractExt(originalFilename);
String uuid = UUID.randomUUID().toString();
return uuid + "." + ext;
}
List\ imageFiles
@Data
public class ItemForm {
private Long itemId;
private String itemName;
private List<MultipartFile> imageFiles;
private MultipartFile attachFile;
}
타임리프에서 다중 이미지를 등록하는 방법은 multiple="multiple" 옵션을 주면 된다.
<ul>
<li>상품명 <input type="text" name="itemName"></li>
<li>첨부파일<input type="file" name="attachFile" ></li>
<li>이미지 파일들<input type="file" multiple="multiple" name="imageFiles" ></li>
</ul>
@GetMapping("/images/{filename}")
public Resource downloadImage(@PathVariable String filename) throwsMalformedURLException {
return new UrlResource("file:" + fileStore.getFullPath(filename));
}
// html
<img th:each="imageFile : ${item.imageFiles}" th:src="|/images/$
{imageFile.getStoreFileName()}|" width="300" height="300"/>
UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
String encodedUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);
String contentDisposition = "attachment; filename=\"" + encodedUploadFileName + "\"";
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.body(resource);