Development/Daily Error

일간에러 2021-12-22 파일 다운로드시 한글 파일명 처리

📝 작성 : 2021.12.23  ⏱ 수정 : 
반응형

문제

드디어 파일 업로드 모듈을 성공적으로 붙여서 테스트를 마치고 운영 서버에 반영을 해 보았다.

파일이 업로드도 잘되고, 업로드 한 파일을 다운로드도 잘 하고. 문제가 전혀 없는 듯 보였는데.. 하필(?) 샘플 코드로 받았던 jsp파일을 한글 파일명 테스트 겸사 해본다고 한글파일.jsp로 파일명을 바꿔서 업로드를 하니 업로드에는 문제가 없었는데 다운로드 할 때 404 에러가 발생했다.

image-20211222144130575

정말 다 끝났다고 생각 했고, 같은 파일의 업로드가 개발 환경에서는 아무 문제가 없었기 때문에 굉장히 골치가 아파지던 상황.

원인

정말 신기한건 다른 한글 파일명에도 문제가 없었고, 심지어 확장자를 조금 변경해 한글파일.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 파일이라도 문제 없이 다운로드 처리를 하는게 확인 되었다.

하지만..

image-20211222150043953

이제는 한글 파일명이 아에 보이지 않는다. 물론 파일명이 영어일때는 전혀 문제가 전혀 없었다.

한글 파일명 문제

한글 이름을 가진 파일의 다운로드를 처리하는건 항상 까다로운 문제다. 브라우저나 인코딩 등 사용자의 여러가지 환경도 고려해야 하기 때문에 여간 골치아픈게 아니다.

파일명 문제를 해결하기 가장 쉬운 방법이 링크에 원래의 파일 명을 넣는 것이고, 지금까지 그렇게 해 왔지만 .jsp 파일명에서 그 방법이 무너져버렸다.

rfc5987

https://datatracker.ietf.org/doc/html/rfc5987

image-20211222151209033

파일명 인코딩에 대한 문제는 지속적으로 제기되어 왔고, 2010년 제안된 rfc5987 명세서를 거의 모든 브라우저가 지원하기 시작하면서 대부분 해결된 것 으로 보인다.

image-20211222151626854

블럭 해둔 예시를 활용해서 해결을 해 보도록 하겠다.

아래의 테스트 결과에 따르면 대부분의 브라우저에서 문제가 없다고 한다.

image-20211222152141176

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());
}

결과

image-20211222155042857

jsp 확장자의 파일이어도, 파일명이 한글로 되어 있어도 모두 문제없이 다운로드를 구현 하도록 해결이 되었다.

다만 마지막으로, UTF-8로 인코딩 할 때 공백문자와 + 모두 %20 되어 버리는 고질적인 문제가 있으니

image-20211222160355637

스프링이 제공하는 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());
}

image-20211222160708282

진짜 끝.

반응형