multipart/form-data
타입은 이름에서 알 수 있듯 form-data
의 여러 파트(multipart
)를 전송 한다.basic.html
...
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="text" name="desc">
<input type="submit"/>
</form>
...
multipart/form-data
를 바디에 담아 보낼 때 정해진 규칙이 있다.multipart/form-data
타입으로 보낸다.boundary
값은 여러 타입의 데이터들을 구분 짓는 구분자 역할을 한다.boundary
값을 기준으로 타입이 뭔지, 그 안에 값은 뭔지 담아서 전송하기 때문에 서버에서 받아서 처리 할 때 타입에 맞도록 디코딩 할 수 있다.boundary
값 뒤에 --
값은 종료를 알리는 값이다.implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
multipart/form-data
를 처리하기 위해StandardServletMultipartResolver
가 동작한다.StandardServletMultipartResolver
가 MultipartFile
객체에 담아 준다.BasicController
@Controller
@RequiredArgsConstructor
public class BasicController {
private final BasicService basicService;
// view
@GetMapping("/basic")
public String basic() {
return "basic";
}
// 파일 업로드
@ResponseBody
@PostMapping("/basic")
public String saveFile(@RequestParam("file") MultipartFile file,
@RequestParam("desc") String description) {
basicService.saveFile(file);
return "업로드 성공!! - 파일 이름: " + file.getOriginalFilename() + ", 파일 설명: " + description;
}
}
BasicService
@Service
public class BasicService {
public void saveFile(MultipartFile file) {
if (!file.isEmpty()) {
// 파일을 저장할 path
// project root path 밑에 video 디렉토리에 저장
Path filepath = Paths.get("video", file.getOriginalFilename());
// 해당 path 에 파일의 스트림 데이터를 저장
try (OutputStream os = Files.newOutputStream(filepath)) {
os.write(file.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>basic file upload</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="text" name="desc">
<input type="submit"/>
</form>
<div id="result"></div>
</body>
<script>
const formElement = document.querySelector('form');
const resultElement = document.querySelector('div');
formElement.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(formElement);
const response = await fetch('/basic', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.text();
resultElement.textContent = result;
} else {
throw new Error(`${response.status} ${response.statusText}`);
}
});
</script>
</html>
application.yml
spring:
servlet:
multipart:
max-file-size: -1
max-request-size: -1
OutOfMemory
에러가 발생했다.@Test
void heap_memory() {
long gb = 1024L * 1024L * 1024L;
long max = Runtime.getRuntime().maxMemory();
System.out.println((double) max / gb);
}
MultipartFile
객체에 파일 데이터를 담는데 해당 파일 데이터를 메모리에 저장하기 때문에 저장할 파일을 4GB인데 힙메모리 공간이 0.5GB 밖에 되지 않기 때문에 OOM이 발생했다.다음 글에서는 이 문제를 해결해 보자.