회사에서 사용자가 첨부한 파일을 다운로드 하는 기능을 만들어야했습니다.
이전에는 base64로 내려줘서 프론트에서 디코딩해서 다운로드하게끔 했었지만
이번에는 respons의 getOutpuStream을 이용하여 바이트배열을 출력하도록하여 이것을 프론트에서 핸들링하도록 하였습니다.
그런데 처음으로 제대로 된 파일 다운로드 기능을 구현하려다보니
시간이 꽤나 많이 걸렸네요ㅠㅠ
[Content-Disposition]이라는 헤더에 값을 처음 지정해주기도 했고
이를 프론트에서 추출하는 것도 처음이어서 그랬던 것 같습니다.
프론트 코드를 보기전에
백엔드에서 어떻게 내려보내주는지를 먼저 볼까요?
보편적으로 Content-Disposition 헤더를 파일 다운로드 관련으로 설정해야한다면 "attachment; filename*=UTF-8''" fileName"
의 양식으로 설정합니다.
// 한글 등의 문자 깨짐 방지를 위해 파일명을 인코딩
String encodedFileName = URLEncoder.encode(decodeFileName, "UTF-8");
// 공백이 "+"가 되는것을 방지 하기 위해 replace
String replaceEncodedFileName = encodedFileName.replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + replaceEncodedFileName);
이 Content-Disposition이 어떤 형식으로 설정되어서 내려오는지를 먼저 안다면 프론트에서 보다 쉽게 파일명을 추출하기 수월할 것입니다.
전 이걸 몰라서 한참 헤맸네요..
// $get은 nuxt3의 $fetch를 plugin에서 커스텀한 것입니다.
// request url 뒤로 차례대로 params, responseType 입니다.
const downloadResponse = await $get<Blob>('/attchments' , {fileName : 'mini.pdf'} , 'blob');
if(downloadResponse !== 200 || !downloadResponse._data) {
// 에러 발생시 catch로 throw하여 에러 메세지 처리를 해야하기에
// 백엔드에서 내려보내준 응답 본문을 그대로 throw한다.
// 이 응답에는 message,status,code 등이 포함되어있음
// 이를 ctach 블록에서 JSON.parse를 이용하여 JSON 본문으로 파싱하여 사용하면됨
throw downloadResponse._data
}
// !! Content-Disposition 헤더에서 값을 추출한다.
const contentDisposition = getTestReportResponse.headers.get('Content-Disposition');
// filename*= 을 기준으로 문자열을 분리해 배열로 만들고, 이를 구조분해할당하여 1번 인덱스의 값만 가져온다.
const [, encodedFileNamePart] = contentDisposition.split('filename*=');
// UTF-8 문자를 제거한다.
const replaceUtf8 = encodedFileNamePart.replace(/UTF-8/g, '');
// 작음따옴표를 제거한다.
const replaceSingleQuote = replaceUtf8.replace(/''/g, '');
// 인코딩 된 fileName을 디코딩한다.
const decodedFileName = decodeURIComponent(replaceSingleQuote);
// 파일 다운로드
const downloadUrl = window.URL.createObjectURL(new Blob([downloadResponse._data]));
const a = document.createElement('a');
a.href = downloadUrl;
a.download = decodedFileName;
a.click();
a.remove();
생각보다 시간이 많이걸렸지만 파일 다운로드에 관해 많은걸 배울 수 있었습니다.
이 Content-Disposition 헤더에서 파일명을 추출하는 방법은 다양합니다. 찾아보니 정말 다양한 방식으로 추출하더라구요
그 중 match를 사용하는 방법도 보았는데 좋은 방법인 것 같아요.
하지만 젤 좋은 코드는 짧고 가독성 좋으면서 누구든 금방 알 수 있는 코드라고 생각하여(사실 내가 쉽게 할려고) 가장 간단한 방법으로 추출해냈습니다.
추가로 파일 다운로드 중 에러가 발생했을때 어떤식으로 catch에서 핸들링하는지도 몰라 헤맸는데, 이는 다음 포스팅에 적어볼까합니다.
감사합니다.
즐코!