반응형
12-24 00:25
Today
Total
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
관리 메뉴

개발하는 고라니

[Spring] 서버 -> 클라이언트 파일 다운로드 본문

Framework/Spring

[Spring] 서버 -> 클라이언트 파일 다운로드

조용한고라니 2021. 2. 27. 17:03
반응형

위와 같은 웹 페이지가 있을 때, 첨부 파일의 이름을 클릭하면 파일이 다운로드 되도록 하는 법을 알아본다.

 

우선 첨부파일의 정보를 출력하는 웹 페이지의 HTML일부를 보면 다음과 같다.

        <div class="uploadResult">
            <ul>
                <li th:each="uploadDTO : ${dto.uploadList}">
                    <div class="uploadDiv" th:data-url="${uploadDTO.getImageURL()}">
                        <a th:href="@{/download(fileName=${uploadDTO.getImageURL()})}">[[${uploadDTO.fileName}]]</a>
                    </div>
                    
                    <img th:if="${uploadDTO.image}" th:src="|/display?fileName=${uploadDTO.getThumbnailURL()}">
                    <img th:if="${!uploadDTO.image}" th:src="|/display?fileName=${uploadDTO.getAttachURL()}">

                </li>
            </ul>
        </div>

"th:"와 같은 문자열이 있는데, 이는 thymeleaf라는 Template Engine을 사용하여 그렇다. 어쨋든 

<a th:href="@{/download(fileName=${uploadDTO.getImageURL()})}">[[${uploadDTO.fileName}]]</a>
//-------------------------------------------------------
<a href="/download?fileName=파일이름">파일이름</a>

'파일이름'을 클릭하면 '/download?fileName=파일이름'이라는 URL에 'GET'요청을 하게된다. 이 때 파일이 다운로드 되도록 하는 것이다.

 

View 단은 이정도면 되었고, Back단을 살펴보자.

Back-End

  1. 서버에서 MIME 타입을 다운로드 타입 "application/octet-stream"으로 지정한다.
  2. ResponseEntity<T>의 'T'는 byte[], Resource등 사용할 수 있다.
  3. 다운로드시 저장되는 이름은 Response Header의 'Content-Disposition'에 명시를 해야한다.
  4. 각 브라우저마다 '한글' 파일명에 대해 인코딩 처리를 한다.
//produces, MediaType.APPLICATION_OCTET_STREAM_VALUE : Header의 Content-Type 지정
//header.add("Content-Disposition", "attachment; filename=" + onlyFileName)
//: Header의 "Content-Disposition"에 저장될 파일명을 명시한다.
//result = new ResponseEntity<>(file, header, HttpStatus.OK)
//: 클라이언트에게 파일 데이터와 헤더 정보를 함께 보낸다.

    @GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<Resource> download(String fileName){
        ResponseEntity<Resource> result = null;

        try {
            String originFileName = URLDecoder.decode(fileName, "UTF-8");

            Resource file = new FileSystemResource("C:\\upload" + File.separator + originFileName);

            if(!file.exists()) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            
            String onlyFileName = originFileName.substring(originFileName.lastIndexOf("_") + 1);
            HttpHeaders header = new HttpHeaders();
            header.add("Content-Disposition", "attachment; filename=" + onlyFileName);

            result = new ResponseEntity<>(file, header, HttpStatus.OK);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

위의 코드는 (4)에 해당하는 한글 파일명 인코딩 처리를 하지 않은 것이다. 이 때 파일명이 한글인 것을 다운받으면 어떻게 되는지 알아보자.

 

* Internet Explore

* Chrome

* Edge

* firefox

총 4개의 브라우저에서 시도했을 때 모두 한글명으로 나오지 않았다. 즉 각 브라우저에서 자신들만의 방법으로 처리하여 저장되는 것을 확인할 수 있었다.

 

이제 (4) 브라우저별 한글이름을 인코딩해보자.

    @GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<Resource> download(String fileName, @RequestHeader("User-Agent") String agent){
        ResponseEntity<Resource> result = null;

        try {
            String originFileName = URLDecoder.decode(fileName, "UTF-8");

            Resource file = new FileSystemResource("C:\\upload" + File.separator + originFileName);

            if(!file.exists()) return new ResponseEntity<>(HttpStatus.NOT_FOUND);

            //브라우저별 한글파일 명 처리
            String onlyFileName = originFileName.substring(originFileName.lastIndexOf("_") + 1);

            if(agent.contains("Trident"))//Internet Explore
                onlyFileName = URLEncoder.encode(onlyFileName, "UTF-8").replaceAll("\\+", " ");
                
            else if(agent.contains("Edge")) //Micro Edge
                onlyFileName = URLEncoder.encode(onlyFileName, "UTF-8");
                
            else //Chrome
                onlyFileName = new String(onlyFileName.getBytes("UTF-8"), "ISO-8859-1");
            //브라우저별 한글파일 명 처리

            HttpHeaders header = new HttpHeaders();
            header.add("Content-Disposition", "attachment; filename=" + onlyFileName);

            result = new ResponseEntity<>(file, header, HttpStatus.OK);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

download 메소드에서 인자로 클라이언트의 브라우저가 무엇인지 정보를 받는 @RequestHeader("User-Agent") String agent를 받았다. agent의 값에 따라 인코딩 방법을 달리하면 된다.


InputStream & OutputStream 이용하여 파일 다운로드

    @GetMapping(value = "/download")
    public void download(String fileName, HttpServletResponse response, HttpServletRequest request){

        try {
            String originFileName = URLDecoder.decode(fileName, "UTF-8");
            String onlyFileName = originFileName.substring(originFileName.lastIndexOf("_") + 1);

            File file = new File("C:\\upload", originFileName);

            if(file.exists()) {
                String agent = request.getHeader("User-Agent");

                //브라우저별 한글파일 명 처리
                if(agent.contains("Trident"))//Internet Explore
                    onlyFileName = URLEncoder.encode(onlyFileName, "UTF-8").replaceAll("\\+", " ");
                    
                else if(agent.contains("Edge")) //Micro Edge
                    onlyFileName = URLEncoder.encode(onlyFileName, "UTF-8");
                    
                else //Chrome
                    onlyFileName = new String(onlyFileName.getBytes("UTF-8"), "ISO-8859-1");
                //브라우저별 한글파일 명 처리

                response.setHeader("Content-Type", "application/octet-stream");
                response.setHeader("Content-Disposition", "attachment; filename=" + onlyFileName);

                InputStream is = new FileInputStream(file);
                OutputStream os = response.getOutputStream();

                int length;
                byte[] buffer = new byte[1024];

                while( (length = is.read(buffer)) != -1){
                    os.write(buffer, 0, length);
                }

                os.flush();
                os.close();
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

이 때, Chrome에서 실행 시 agent는 다음과 같은 값을 가진다.

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome.....

 

반응형

'Framework > Spring' 카테고리의 다른 글

[Spring] Transactional  (0) 2021.06.11
[Spring] DispatcherServlet  (0) 2021.06.04
[Spring] 업로드한 파일의 Content-Type  (0) 2021.02.25
[Spring] 파일 업로드  (0) 2021.02.25
[Spring] URL Encode : 공백을 '+'이 아닌 '%20'  (0) 2020.12.17
Comments