문제
드디어 파일 업로드 모듈을 성공적으로 붙여서 테스트를 마치고 운영 서버에 반영을 해 보았다.
파일이 업로드도 잘되고, 업로드 한 파일을 다운로드도 잘 하고. 문제가 전혀 없는 듯 보였는데.. 하필(?) 샘플 코드로 받았던 jsp파일을 한글 파일명 테스트 겸사 해본다고 한글파일.jsp
로 파일명을 바꿔서 업로드를 하니 업로드에는 문제가 없었는데 다운로드 할 때 404 에러가 발생했다.
정말 다 끝났다고 생각 했고, 같은 파일의 업로드가 개발 환경에서는 아무 문제가 없었기 때문에 굉장히 골치가 아파지던 상황.
원인
정말 신기한건 다른 한글 파일명에도 문제가 없었고, 심지어 확장자를 조금 변경해 한글파일.jyp
로 바꿔도 아무 문제 없이 작동했다. 또 어떤 다른 확장자가 들어갔을 때 같은 에러가 터질지 몰라 감도 안오던 상황에 책임님께 이 상황을 말씀 드렸더니
파일명이 .jsp 라서 WAS(톰캣) 에서 먼저 가져다가 처리하는거 아니야?
단번에 문제를 캐치하셨다.
jsp 파일이 열리지 않고 다운로드가 되던 오류
그 말을 듣자마자 문득 하나의 경험이 바로 떠올랐다. 지금과는 정 반대의 경우지만, 2주 전 쯤에 김영한님의 스프링 MVC
강의를 들으며 JSP 에 관련된 내용을 실습 하던 중 문제가 발생했었다.
localhost:8080/~.jsp 로 요청하면 해당 뷰를 불러 와야 하는데, 자꾸 페이지가 열리지는 않고 해당 jsp 파일을 브라우저가 다운로드 하는 것 이었다.
한참을 뒤져 이유를 찾아냈는데. build.gradle 파일에서
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
위와 같이 jsp 관련 모듈을 추가 해 주는 과정에서, embed 를 emded 라고 오타를 낸 것이었다. 그래서 jsp 파일을 내장 톰캣이 처리 할 수 없으니 그냥 해당 .jsp 파일을 다운로드 요청이라고 판단하고 보내버렸던 것. 에러가 나지도 않고 오타도 교묘하게 낸 바람에 꽤나 골치아팠던 기억이 난다.
지금은 정 반대의 경우로 .jsp
파일의 다운로드 처리를 해야 하는데 뭔가 톰캣이 먼저 앗 jsp? 이거 내껀가?
하며 건들었다가 아니네~
하며 뱉는 바람에 인코딩이 오염된 모양이다.
해결
.jsp
파일의 다운로드
.jsp
로 끝나는 요청을 보내지 않는게 지금 문제의 핵심이다. 지금까지는 파일 다운로드 받는 요청을 /file/{itemId}/{fileId}/{filename}
으로 보냈었지만, 파일 확장자가 경로에 들어가지 않도록 해 주어야 한다.
@GetMapping(value = "/file/{itemId}/{fileId}/", params = "filename")
public ResponseEntity<?> download(
@PathVariable String itemId,
@PathVariable String fileId,
String filename) throws IOException {
}
응답할 때에 헤더에 Content_disposition 까지 attachment;filename=파일명 까지 걸어 줘서 보내면 완벽하다.
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+filename)
이렇게 요청 URL을 변경 해서 컨트롤러에서 받고 해당 파일을 반환 하도록 변경 해 보니 이제 jsp 파일이라도 문제 없이 다운로드 처리를 하는게 확인 되었다.
하지만..
이제는 한글 파일명이 아에 보이지 않는다. 물론 파일명이 영어일때는 전혀 문제가 전혀 없었다.
한글 파일명 문제
한글 이름을 가진 파일의 다운로드를 처리하는건 항상 까다로운 문제다. 브라우저나 인코딩 등 사용자의 여러가지 환경도 고려해야 하기 때문에 여간 골치아픈게 아니다.
파일명 문제를 해결하기 가장 쉬운 방법이 링크에 원래의 파일 명을 넣는 것이고, 지금까지 그렇게 해 왔지만 .jsp
파일명에서 그 방법이 무너져버렸다.
rfc5987
파일명 인코딩에 대한 문제는 지속적으로 제기되어 왔고, 2010년 제안된 rfc5987 명세서를 거의 모든 브라우저가 지원하기 시작하면서 대부분 해결된 것 으로 보인다.
블럭 해둔 예시를 활용해서 해결을 해 보도록 하겠다.
아래의 테스트 결과에 따르면 대부분의 브라우저에서 문제가 없다고 한다.
http://test.greenbytes.de/tech/tc2231/#attwithfn2231utf8comp
그렇게 완성된 컨트롤러 코드는 아래와 같다. URLEncoder를 이용해 파일명을 UTF-8로 인코딩 해 주고
Content-Disposition: attachment; filename*=UTF-8''foo-a%cc%88.html
방식으로 헤더를 붙여준다.
@GetMapping(value = "/file/{itemId}/{fileId}/", params = "filename")
public ResponseEntity<?> download(
@PathVariable String itemId,
@PathVariable String fileId,
String filename) throws IOException {
ResponseEntity<?> responseEntity = getFile(itemId, fileId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF8''" + URLEncoder.encode(filename, "UTF-8"))
.body(responseEntity.getBody());
}
결과
jsp
확장자의 파일이어도, 파일명이 한글로 되어 있어도 모두 문제없이 다운로드를 구현 하도록 해결이 되었다.
다만 마지막으로, UTF-8로 인코딩 할 때 공백문자와 + 모두 %20
되어 버리는 고질적인 문제가 있으니
스프링이 제공하는 UriUtils를 이용하면 그것 마저도 깔끔하게 해결 된다.
@GetMapping(value = "/file/{itemId}/{fileId}/", params = "filename")
public ResponseEntity<?> download(
@PathVariable String itemId,
@PathVariable String fileId,
String filename) throws IOException {
ResponseEntity<?> responseEntity = getFile(itemId, fileId);
String encode = UriUtils.encode(filename, StandardCharsets.UTF_8.toString());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF8''" + encode)
.body(responseEntity.getBody());
}
진짜 끝.
'Development > Daily Error' 카테고리의 다른 글
일간에러 2022-01-04 iRODS:-24000 (0) | 2022.01.06 |
---|---|
일간에러 2021-12-29 (0) | 2021.12.29 |
일간에러 2021-12-21 파일 업로드시 파일명 깨짐 (0) | 2021.12.21 |
일간에러 2021-12-20 Corrupt form data: premature ending (0) | 2021.12.20 |
일간에러 2021-12-01 createPopper is not a function (0) | 2021.12.02 |