파일 업로드 시 net::ERR_CONNECTION_RESET 오류 발생 원인 및 해결 방법
파일 업로드 시 net::ERR_CONNECTION_RESET 오류 발생 원인 및 해결 방법
스프링 프로젝트에서 ajax를 통해 파일 업로드를 하는 기능 동작 중, 용량이 큰 파일을 업로드하는 경우 클라이언트 쪽에서 위 이미지와 같이 '(failed) net::ERR_CONNECTION_RESET' 오류가 발생하였고, 서버 쪽에서는 해당 오류에 대한 로그조차 남지 않는 문제가 발생했는데요.
(로그 레벨 설정이 debug level로 설정되어 있을 경우 오류 정보가 출력되었습니다.)
해당 포스팅에서는 이와 같이 업로드 과정에서 net::ERR_CONNECTION_RESET 오류가 발생하는 원인과 해결 방법에 대해서 정리해 보았습니다.
오류 원인 파악
용량이 큰 파일을 업로드하는 경우에만 해당 문제가 발생했기 때문에 당연히 용량 문제라고 판단되었지만, 문제의 해결을 위해서는 정확한 원인 파악이 필요했는데요.
먼저 로그가 debug level로 설정되어 있을 때만 오류 정보가 출력되는 이유는 DispatcherServlet의 processHandlerException() 메서드에서 확인할 수 있었으며, 'logger.isDebugEnabled()'의 경우에만 발생한 exception에 대한 자세한 내용을 디버그 레벨로 출력하도록 구현되어 있었습니다.
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 20971520 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (2106293889) exceeds the configured maximum (20971520)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.java:142) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1099) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:932) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
... (생략)
Caused by: org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (2106293889) exceeds the configured maximum (20971520)
at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:1007) ~[commons-fileupload-1.5.jar:1.5]
at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:334) ~[commons-fileupload-1.5.jar:1.5]
at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:358) ~[commons-fileupload-1.5.jar:1.5]
at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:113) ~[commons-fileupload-1.5.jar:1.5]
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:158) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
... 35 more
(오류 로그)
이어서 출력된 로그의 주요 내용을 살펴보면 다음과 같은데요.
가장 핵심은 CommonsMultipartResolver 클래스의 parseRequest() 메서드에서 살펴볼 수 있습니다.
((ServletFileUpload) fileUpload).parseRequest(request) 과정에서 FileUploadBase.SizeLimitExceededException이 발생하게 되고, 발생한 exception을 new MaxUploadSizeExceededException으로 throw 하게 되는 것입니다.
SizeLimitExceededException이 발생한 원인은 parseRequest() 메서드의 내부적으로 동작되는 FileItemIteratorImpl 인스턴스의 초기화(init) 과정에서 requestSize가 sizeMax 보다 크면서 발생되며, 여기서 sizeMax는 아래 multipartResolver bean 등록 시 설정된 maxUploadSize의 값이 담겨있는 것을 확인하였습니다.
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="20971520" /> <!-- 20MB -->
<beans:property name="maxInMemorySize" value="1048576" /> <!-- 1MB -->
<beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>
(servlet-context.xml 파일의 multipartResolver bean 등록 부분)
따라서 해당 오류가 발생한 원인은 단순히 파일의 용량이 커서가 아니라 요청 자체의 사이즈가 초과되었기 때문에 이러한 문제가 발생한 것인데요.
여기서 하나 더 중요한 부분은 SizeLimitExceededException 발생 시 담긴 메시지입니다.
'the request was rejected because its size (requestSize) exceeds the configured maximum (sizeMax)'
즉, 클라이언트의 요청이 거부된 것인데요.
다시 ERR_CONNECTION_RESET 오류가 발생한 jquery.min.js 파일 중, 예외를 catch 하는 부분에서 exception을 확인해 보면 이미지와 같이 NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'url'이라는 내용을 확인할 수 있습니다.
***
문제의 원인을 정리하자면, 파일 용량으로 인해 요청 자체의 크기가 MultipartResolver 쪽에서 담을 수 있는 크기를 초과하였고, 클라이언트의 요청 자체를 거부하게 됩니다.
때문에 클라이언트에서는 요청에 대한 오류 응답은 커녕 애초에 요청 자체에 대한 전송을 실패한 것입니다.
해결 방안
MultipartResolver에 설정된 maxUploadSize를 변경하는 방식의 경우, 값의 제한을 없애는 게 아닌 이상 어떠한 값도 그보다 더 큰 사이즈의 요청이 들어왔을 때 동일한 문제가 발생하게 되는데요.
그렇다고 요청의 크기 제한을 없애는 경우, 악의적인 공격에 의해 서버의 리소스가 잡아먹힐 수 있다는 더 큰 문제가 생기게 됩니다.
document.getElementById('fileuploadForm').addEventListener('submit', function(event) {
event.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('file');
var uploadFile = fileInput.files[0];
const maxFileSize = 20971520;
// File.size를 통해 해당 파일의 바이트(byte) 크기를 확인할 수 있습니다.
if (uploadFile.size > maxFileSize) {
alert("업로드 할 수 있는 파일 용량을 초과하였습니다.");
return;
}
formData.append('file', uploadFile);
$.ajax({
url: "http://localhost:8080/fileupload",
type: "POST",
encytpe: 'multipart/form-data',
data: formData,
async: false,
cache: false,
contentType: false,
processData: false,
success: function(response) {
},
error: function(xhr, status, error) {
// status = 0 에 대한 처리
if (xhr.status === 0) {
alert('네트워크 연결이 끊어졌습니다. 다시 시도해주세요.');
}
}
});
});
(javascript 처리 방안)
때문에 ajax 요청 전, javascript 단에서 업로드되는 파일에 대한 크기를 체크하여 업로드 요청을 하는 것이 가장 기본적이며 간단한 처리 방법이 될 것 같습니다.
그리고 ERR_CONNECTION_RESET이 발생하는 경우 xhr.status 값이 '0'으로 넘어오기 때문에 ajax 요청 후 error 쪽에서 해당 경우에 대한 처리를 추가할 수 있습니다.
***
multipartResolver에 대한 parseRequest 과정에서 클라이언트의 요청 자체가 거부되는 문제이기 때문에 백엔드 쪽에서 처리할 수 있는 방안이 있는지 의문인데, 현재는 생각나는 방법이 없습니다.
이 부분은 추후에 백엔드 쪽 처리 방안을 찾게 되면 추가하도록 하겠으며, 좋은 처리 방법이나 의견이 있으시다면 댓글 주시면 참고하여 확인해 보도록 하겠습니다. 감사합니다.