728x90
첨부파일 다운로드 API 개발 중에 아래와 같은 오류가 발생했다.
오류 상황
포스트맨 요청
콘솔창
콘솔창을 보니, HttpMessageNotWritableException 이 발생했다.
2024-01-30 11:17:31.261 ERROR 50824 --- [nio-3000-exec-3] k.c.i.p.c.c.ControllerAdviceHandler : org.springframework.http.converter.HttpMessageNotWritableException
2024-01-30 11:17:31.261 ERROR 50824 --- [nio-3000-exec-3] k.c.i.p.c.c.ControllerAdviceHandler : No converter for [class kr.co.iabacus.parkGolf.common.dto.ResponseBaseDto] with preset Content-Type 'application/octet-stream;charset=UTF-8'
2024-01-30 11:17:31.263 WARN 50824 --- [nio-3000-exec-3] .m.m.a.ExceptionHandlerExceptionResolver : Failure in @ExceptionHandler kr.co.iabacus.parkGolf.common.config.ControllerAdviceHandler#baseException(Exception, HttpServletRequest, HttpServletResponse)
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class kr.co.iabacus.parkGolf.common.dto.ResponseBaseDto] with preset Content-Type 'application/octet-stream;charset=UTF-8'
CommunityCtrl 클래스
여기서 파일 다운로드 url을 호출을 한다.
// 9. 파일 다운로드
@GetMapping(value = "/downFiles")
public @ResponseBody ResponseBaseDto<FileDownResDto> downImgs(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value="communityIdx",required=true) Integer communityIdx,
@RequestParam(value="fileIdx",required=true) Integer fileIdx) {
FileDownResDto resDto = communitySvc.fileDown(request, response, communityIdx, fileIdx);
return new ResponseBaseDto<>(resDto);
}
CommunitySvcImpl 클래스
@Override
public FileDownResDto fileDown(HttpServletRequest request, HttpServletResponse response, Integer communityIdx, Integer fileIdx) {
List<ApndFile> fileList = admincommunityDao.selectFile(communityIdx);
for(ApndFile apndFile : fileList)
if(apndFile.getApndFileId().equals(fileIdx))
return commonSvc.downImgs(request, response, apndFile.getApndFileId()); // FileDownResDto 반환
return null;
}
CommonSvcImpl 클래스
@Override
public FileDownResDto downImgs(HttpServletRequest request, HttpServletResponse response, Integer apndFileId) {
ApndFile file = commonDao.selectApndFile(apndFileId);
String path = file.getApndFilePath() + File.separator;
String fileNm = file.getApndFileNm();
String realFileNm = file.getOrgFileNm();
// 파일 다운로드
FileUtil.fileDownload(request, response, path, fileNm, realFileNm);
String binary = fileUtil.fileByteString(path, fileNm, realFileNm); // 파일의 경로를 받아 해당 파일을 Base64 문자열로 변환
String extension = "";
int i = fileNm.lastIndexOf('.');
if (i > 0) {
extension = fileNm.substring(i + 1);
}
return new FileDownResDto(extension, binary);
}
FileUtil.java 클래스
문제가 되는 상황은 이 부분이었다.
// 5. 파일 다운로드
public static void fileDownload(HttpServletRequest request, HttpServletResponse response, String filePath, String fileNm, String realFileNm) {
String realFilePath = "";
realFilePath = filePath + fileNm; // 미리 저장해 둔 FILE_BASE_PATH 변수와 filePath와 fileNm을 조합하여 실제 파일의 전체 경로(realFilePath)를 생성
log.debug("파일 경로 : " + realFilePath);
// File file = new File(realFilePath);
File file = new File("C:\\Users\\USER\\Desktop\\parkGolf\\"+ realFilePath);
if (file.exists()) {
log.debug("파일 존재");
String header = request.getHeader("User-Agent");
String docName = "";
OutputStream os = null;
InputStream is = null;
// 다운로드할 파일의 정보와 함께 HTTP 응답의 헤더를 설정
try {
if (header.indexOf("MSIE") > -1) {
docName = URLEncoder.encode(realFileNm, "UTF-8").replaceAll("\\+", "%20");
} else if (header.indexOf("Trident") > -1) {
docName = URLEncoder.encode(realFileNm, "UTF-8").replaceAll("\\+", "%20");
} else if (header.indexOf("Chrome") > -1) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < realFileNm.length(); i++) {
char c = realFileNm.charAt(i);
if (c > '~') {
sb.append(URLEncoder.encode("" + c, "UTF-8"));
} else {
sb.append(c);
}
}
docName = sb.toString();
} else if (header.indexOf("Opera") > -1) {
docName = "\"" + new String(realFileNm.getBytes("UTF-8"), "8859_1") + "\"";
} else if (header.indexOf("Safari") > -1) {
docName = "\"" + new String(realFileNm.getBytes("UTF-8"), "8859_1") + "\"";
docName = URLDecoder.decode(docName);
} else {
docName = "\"" + new String(realFileNm.getBytes("UTF-8"), "8859_1") + "\"";
docName = URLDecoder.decode(docName); // 여기서 깨짐 !!! todo 해결하기
}
/** Set Response Header */
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-Type", "application/octet-stream; charset=utf-8");
response.setContentLength((int) file.length());
response.setHeader("Content-Disposition", "attachment;fileName=" + "\"" + docName + "\"" + ";"); // 브라우저에게 해당 컨텐츠를 어떻게 처리할지 설정
response.setHeader("Content-Disposition", "attachment;filename=" + docName + ";");
System.out.println("docName ===================================================== " + docName); // "ê°ì¥.jpg" 이런식으로 깨진다.
response.setHeader("Content-Transfer-Encoding", "binary"); // 전송 인코딩 방식 설정
response.setCharacterEncoding("UTF-8");
os = response.getOutputStream();
is = null;
is = new FileInputStream(file); // 파일을 읽어 FileInputStream을 이용하여 바이트 스트림을 얻는다.
FileCopyUtils.copy(is, os); // 얻은 바이트 스트림을 FileCopyUtils.copy()를 사용하여 응답 스트림(OutputStream)으로 복사
os.flush(); // 응답 스트림을 플러시하여 파일 전송!
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new BizException("fileNotFound");
} catch (IOException e) {
e.printStackTrace();
throw new BizException("fileDownloadFail");
} finally {
if (os != null) {
try {
os.close(); // os 닫아주기
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close(); // is 닫아주기
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
// log.debug("파일 없음");
throw new BizException("fileNotFound");
}
}
디버깅을 해보니, 이 부분에서 docName이 깨지는 것을 확인했다.
docName = URLDecoder.decode(docName);
해결 시도
해결 시도 1 . pom.xml 에 jackson-core 의존성 추가
@RestController라서 메시지컨버터가 자동으로 변환해줄텐데, 자꾸 메시지컨버터가 없다고 나오니, 아래와 같이 pom.xml 에 의존성을 추가해주었다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.2</version>
</dependency>
실패.. 안된다.