오류 해결

첨부파일 다운로드 개발 중 HttpMessageNotWritableException 오류

Nellie Kim 2024. 1. 30. 11:26
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>

실패.. 안된다.