MultipartProperties의 max-file-size, max-request-size

하루히즘·2021년 4월 23일
4

Spring Framework

목록 보기
1/15

서론

Spring Boot 기반으로 프로젝트를 작성하고 있는데 특정 크기를 넘어가는 파일을 전송하면 위처럼 500 에러가 발생하는 것을 확인했다.

org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException:
The field uploadedFiles exceeds its maximum permitted size of 20971520 bytes.

콘솔을 확인한 결과 FileSizeLimitExceededException이란 예외가 발생했는데 이름과 설명만 봐도 알 수 있듯이 파일 크기가 제한을 초과했을 때 발생하는 예외다. 지금은 설정을 바꿔놓았기 때문에 20971520 bytes, 약 20MB가 제한으로 걸려 있으나 스프링 기본 설정에서는 1MB로 설정되어 있다.

본론

웹 서비스에서 단순 텍스트 뿐 아니라 파일도 업로드해야하는 경우 1MB 제한은 너무 작기 때문에 대부분의 경우 설정을 조정해야 한다. 그런데 관련 문서를 찾아보면 max-file-size와 max-request-size란 속성값이 있는데 이 둘은 무슨 차이일까?

max-file-size specifies the maximum size permitted for uploaded files. The default is 1MB
max-request-size specifies the maximum size allowed for multipart/form-data requests. The default is 10MB.

문서를 살펴보면 위처럼 전자는 전송되는 개별 파일의 크기를, 후자는 서버측으로 전송된 요청(request) 자체의 크기를 제한한다는 것을 알 수 있다. 파일 업로드 측면에서 본다면 전자는 한 파일이 가질 수 있는 크기를, 후자는 모든 파일의 크기를 합한 값을 제한한다고 할 수 있다.

즉 max-file-size를 초과하지 않더라도 서버측으로 전송되는 모든 리소스의 크기의 합이 max-request-size를 초과한다면 요청은 차단된다. 그러나 max-file-size를 초과한다면 max-request-size를 초과하든 초과하지 않든 요청은 차단된다. 하지만 초과 여부에 따라 차단 방식이 조금 달라진다.

Spring Boot를 이용하고 있기 때문에 application properties 파일을 이용하여 다음처럼 간단하게 변경할 수 있다.

spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=25MB

이 경우 각 파일은 20MB를 초과할 수 없으며 서버로 전송되는 모든 데이터가 25MB를 초과할 수 없다는 것을 의미한다.

max-file-size

위의 Whitelabel Error Page에서 확인할 수 있듯이 크기 제한을 초과하는 파일을 업로드하면 FileSizeLimitExceededException가 발생한다. 지금은 크기 제한을 20MB로 올렸기 때문에 확인삼아 약 4MB 정도의 파일을 전송해본 결과 다음처럼 잘 업로드되는 것을 볼 수 있었다.
왼쪽 아래는 서버 로그에서 파일을 읽어서 클라우드로 업로드했으며 오른쪽 사진에서 실제로 클라우드로 업로드된 해당 파일을 볼 수 있다.

물론 20MB를 넘는 파일을 전송할 경우 역시 FileSizeLimitExceededException이 발생하게 된다.

max-request-size

그럼 max-request-size를 초과해보면 어떨까? 이를 테스트해보려면 max-file-size를 초과하지 않으면서도 max-request-size를 초과할 수 있도록 적당히 20MB를 넘지 않는 파일을 두세 개 정도 골라서 전송하는게 좋을 것이다.
음악 파일을 3개 정도 골라서 30MB를 전송시켰다. 그 결과 요청의 최대 크기(25MB)를 초과했기 때문에 이전처럼 거부된 것을 볼 수 있었는데 이번에는 좀 다르게 아예 연결 자체가 리셋된 것을 볼 수 있었다.

org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException:
the request was rejected because its size (34700233) exceeds the configured maximum (26214400)

특히 이번에는 발생한 예외도 달라졌는데 파일 크기 제한을 초과한 경우 FileSizeLimitExceededException이 발생했지만 지금처럼 요청 크기 제한을 초과한 경우 SizeLimitExceededException이 발생한 것을 볼 수 있었다. 비슷한 이름의 다른 예외와 헷갈릴 수 있지만 이 예외는 톰캣에서 발생한다.

예외처리

그럼 이런 예외를 어떻게 처리할 수 있을까? 대부분 ExceptionHandler를 구현하여 ControllerAdvice로 적용하는 방법을 권장하고 있다.

@ControllerAdvice
public class FileUploadExceptionAdvice {
     
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public String handleMaxSizeException(...) { ... }
}

출처

파일 크기 제한을 초과하는 경우 발생하는 MaxUploadSizeExceededExceptionMultipartException에 포함되기 때문에 다음처럼 좀 더 포괄적인 ExceptionHandler를 구현할 수도 있다.

@ExceptionHandler(MultipartException.class)
@ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
public String handleMultipartExceptionPayloadTooLarge(...){ ... }

출처

하지만 이 경우 MVC 환경에서 사용자 환경에 맞는 피드백이 어렵기 때문에 파일 크기 제한 자체는 조금 더 올려두고 실제로 MultipartFile을 처리하는 컨트롤러나 서비스에서 파일의 크기를 검증 후 그 결과를 뷰 템플릿에 반영하는 방식으로 구현할 수도 있을 것이다.

결론

Spring Boot에서는 대부분의 설정이 자동으로 적용되기 때문에 이처럼 프로퍼티를 통한 단순 수정만으로 유연하게 변경이 가능하다. 게시판 애플리케이션처럼 파일만 업로드하는 게 아니라 게시글의 제목, 내용 등 텍스트 데이터도 전송되는 것을 고려한다면 max-request-size는 max-file-size보다 좀 더 커야 할 것이다.

게다가 max-request-size를 초과하는 경우 아예 서버측에서 연결을 리셋시켜버리기 때문에 사전에 클라이언트에게 용량 상한에 대한 적절한 고지가 필요할 것이다.

profile
YUKI.N > READY?

4개의 댓글

comment-user-thumbnail
2021년 5월 5일

좋은 내용 감사합니다.

MaxUploadSizeExceededException, SizeLimitExceededException 등의 예외를 처리할 때

근데 왜 Controller 내에 ExceptionHandler를 구현하면 작동하지 않고,
같은 ExceptionHAndler를 ControllerAdvice 에서 구현해야만 작동하는지
위의 예외가 캐취되고 처리되는 매커니즘을 알려주실 수 있을까요?

위 예외가 발생되는 레벨이 달라서 전역으로 처리해야 그런가보다 추론은 하고 있는데,
정확히는 왜 그런지 궁금하네요.
같은 ExceptionHandler 를 Controller 에 둘 때는 작동하지 않고,
ControllerAdvice 에 둘 때에만 적용되더라구요.

1개의 답글