빌드노트
MultipartFile은 있는데 내용은 0바이트다
Spring MVC의 필수 파일 파트가 있어도 내용은 0바이트일 수 있다. MockMvc에서 파트 누락·빈 파일명·이름 있는 빈 파일·내용 있는 파일을 비교하고, required와 isEmpty()로 누락과 빈 내용을 각각 처리하는 조건을 확인한다.
Spring MVC 컨트롤러에 MultipartFile이 들어왔고 파일명도 있는데, getSize()는 0일 수 있다. 파일 파트가 있다는 것과 파일 내용이 있다는 것은 별도로 검사해야 한다. @RequestPart("file")의 필수 설정은 파트 누락을 막는다. 내용까지 필수라면 isEmpty() 검사도 필요하다.
내용이 필수라면 처리 전에 isEmpty()를 검사한다
이미 multipart 요청을 받는 @RestController에서 파일 내용을 처리하기 직전에 다음 조건을 둔다. 필요한 타입은 org.springframework.http.ResponseEntity, org.springframework.web.multipart.MultipartFile과 org.springframework.web.bind.annotation의 PostMapping, RequestPart다.
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestPart("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("EMPTY_FILE");
}
return ResponseEntity.ok("CONTENT");
}
@RequestPart의 required 기본값은 true다. file 파트를 찾지 못하면 인자를 준비하는 단계에서 예외가 나므로 메서드 본문에 들어오지 않는다. 반면 존재하는 0바이트 파일은 본문까지 들어와 isEmpty() 조건에 걸린다. RequestPart 6.2.12 API (새 탭에서 열림)
CONTENT는 이 예제에서 빈 내용 검사를 통과했다는 표시이며, 실제 저장은 수행하지 않는다. 0바이트 파일도 유효한 입력인 서비스라면 이 거절 조건을 넣을 필요가 없다. 먼저 파일의 존재만 필수인지, 내용도 필수인지 정한다.
같은 /upload에 네 입력을 보냈다
Windows의 OpenJDK 17.0.2, Spring Framework와 spring-test 6.2.12에서 별도 MockMvc 실험을 실행했다. 앞의 /upload에 파트명 file로 아래 입력을 보냈고, 같은 mock 파일에서 읽은 isEmpty()와 getSize()도 함께 확인했다.
file파트 없음: 기본 400,MissingServletRequestPartException. 메서드가 호출되지 않아isEmpty()와 크기를 읽을 인자가 없었다.- 파일명
"", 내용 0바이트:isEmpty=true,size=0. 메서드에 들어와 400과EMPTY_FILE을 반환했다. - 파일명
"empty.txt", 내용 0바이트:isEmpty=true,size=0. 이름이 있어도 같은 분기에서 400과EMPTY_FILE을 반환했다. - 파일명
"hello.txt", 내용abc:isEmpty=false,size=3. 메서드에 들어와 200과CONTENT를 반환했다.
누락과 빈 내용은 모두 400일 수 있지만 실패한 위치가 다르다. 파트 누락의 400은 Spring MVC 기본 예외 처리 (새 탭에서 열림)가 만든 결과이고, EMPTY_FILE은 위 메서드가 직접 만든 본문이다. 이번 실험에는 사용자 정의 예외 처리를 넣지 않았다. 자신의 앱에서 응답이 다르면 예외 처리 설정도 확인한다.
기존 업로드를 점검할 때는 파트명을 맞춘 뒤, 이름이 있는 0바이트 파일부터 보내 본다. 위 조건을 적용했다면 EMPTY_FILE로 거절되어야 한다. 그대로 처리된다면 파일명이나 null만 검사하고 있지 않은지 확인한다. 파트를 아예 빼는 입력과 내용 abc를 넣는 입력도 따로 비교한다. 상태 코드만 보지 말고 누락 예외가 발생했는지, 메서드의 빈 내용 분기로 들어갔는지를 구분해야 한다.
MultipartFile API (새 탭에서 열림)의 isEmpty()는 파일을 선택하지 않았거나 선택한 파일에 내용이 없는 경우를 나타낸다. getSize()는 바이트 수이고 빈 파일은 0이다. 따라서 isEmpty()만으로 사용자가 파일을 선택하지 않은 것인지, 실제 0바이트 파일을 선택한 것인지까지 구분할 수는 없다.
getOriginalFilename()은 클라이언트가 제공한 이름이며 빈 문자열이나 null일 수도 있다. 이름이 있다는 이유로 내용이 있거나 안전한 파일이라고 판단하지 않는다. 보조 비교에서는 이름이 ""이고 내용이 abc인 mock 파일도 isEmpty=false, size=3이었다. 이는 mock에 직접 넣은 값의 결과이며, 브라우저의 전송 형태나 실제 서버의 파일 분류를 검증한 결과는 아니다.
누락의 오류 본문까지 직접 정하려면
누락도 컨트롤러 안에서 처리하려면 인자를 @RequestPart(name = "file", required = false) MultipartFile file로 바꾸고, isEmpty()보다 먼저 file == null을 검사한다. 별도 비교에서 누락은 이 분기로 들어와 400과 MISSING_FILE을 반환했고, 두 0바이트 입력은 계속 400과 EMPTY_FILE, 내용 abc는 200과 CONTENT였다. required=false는 누락을 null로 받는 설정이다. 그 상태를 오류로 거절할지 정상적인 미첨부로 처리할지는 서비스의 입력 계약으로 정한다.
이미 @RequestParam("file") MultipartFile file을 쓰고 있어도 같은 구분을 적용할 수 있다. 별도 비교에서 단일 파일 인자의 누락·빈 내용 결과가 같았고, Spring MVC multipart 문서 (새 탭에서 열림)도 @RequestParam으로 받은 파일에서 isEmpty()를 확인한다.
이번 실험이 확인한 범위는 파싱 이후의 인자 처리와 빈 내용 분기다. MockMvc 공식 설명 (새 탭에서 열림)에 명시된 대로 multipart()는 mock 요청을 구성하며 실제 multipart 본문을 파싱하지 않는다. 브라우저의 미선택 전송 형태, Servlet 컨테이너의 파싱, 업로드 크기 제한, 파일 형식과 저장 안전성은 별도로 확인해야 한다.