티스토리 뷰

반응형

자바 소스를 사용하여 하나의 파일을 다운로드 하는 방법은 찾기 쉬웠는데 압축된 파일을 다운로드하는 것은 잘 보이지가 않더라구요. 

이번에는 파일을 압축하여 zip 파일로 다운로드를 하는 로직을 작성해보았습니다. 

 

환경: macOS / 브라우저 : Chrome

 

가장 먼저 파일들이 있다는 가정하에 작성을 해보도록 할게요. 

    public static void fileDownload(List<kr.component.common.domain.File> files) throws Exception {
        String dir = FAKEPATH;
        File file = new File(dir);
        file.mkdir();
        files.forEach(f -> {
            try {
//                System.out.println("-------Download Start------");
                URL Url;
                byte[] buf;
                int byteRead;
                int byteWritten = 0;
                Url = new URL(f.getPath());
                OutputStream outStream = new BufferedOutputStream(new FileOutputStream(dir + f.getName()));
                URLConnection uCon = Url.openConnection();
                InputStream is = uCon.getInputStream();

                buf = new byte[1024];
                while ((byteRead = is.read(buf)) != -1) {
                    outStream.write(buf, 0, byteRead);
                    byteWritten += byteRead;
                }
//                System.out.println("Download Successfully.");
//                System.out.println("File name :일바 " + f.getName());
//                System.out.println("of bytes  : " + byteWritten);
//                System.out.println("-------Download End--------");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

위에 작성된 함수는 여러 개의 파일들을 zip파일로 묶어 FAKEPATH 에 생성해주는 함수입니다. 

FAKEPATH 는 개발자가 지정할 수 있습니다.

 

    private static String getBrowser(HttpServletRequest request) {
        String header = request.getHeader("User-Agent");
        if (header.indexOf("MSIE") > -1 || header.indexOf("Trident") > -1)
            return "MSIE";
        else if (header.indexOf("Chrome") > -1)
            return "Chrome";
        else if (header.indexOf("Opera") > -1)
            return "Opera";
        return "Firefox";
    }

    private static String getDisposition(String filename, String browser)
            throws UnsupportedEncodingException {
        String dispositionPrefix = "attachment;filename=";
        String encodedFilename = null;
        if (browser.equals("MSIE")) {
            encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll(
                    "\\+", "%20");
        } else if (browser.equals("Firefox")) {
            encodedFilename = "\""
                    + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
        } else if (browser.equals("Opera")) {
            encodedFilename = "\""
                    + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
        } else if (browser.equals("Chrome")) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < filename.length(); i++) {
                char c = filename.charAt(i);
                if (c > '~') {
                    sb.append(URLEncoder.encode("" + c, "UTF-8"));
                } else {
                    sb.append(c);
                }
            }
            encodedFilename = sb.toString();
        }
        return dispositionPrefix + encodedFilename;
    }

 

그 다음으로 살펴볼 것은 생성된 파일을 다운로드 하는 로직입니다. 여기서 request의 경우 브라우저의 종류를 체크하기 위해 파라미터로 전달을 받았고, response를 파라미터로 받는 것은 ServletOutputStream 객체를 통해 자동으로 파일을 다운로드 하기 위해 추가하였습니다.

 

마지막으로 파일을 자동으로 다운로드하게 만드는 함수인데, 이 함수에서 위에서 사용하였던 getBrowsergetDisposition함수 두가지를 호출하게 됩니다. 

    final static String FAKEPATH = "./fakepath/";
    final static Charset UTF8 = Charset.forName("UTF-8");
    final static Charset ISO = Charset.forName("ISO-8859-1");

    public static void toZipAndGetUrl(HttpServletRequest request, HttpServletResponse response, String fileName) {
        try{
            String extension = ".zip";
            fileName += extension;
            String fileNameEncoding = new String(fileName.getBytes(UTF8), ISO);
            File createFile = new File("test.zip");
            File fakepath = new File(FAKEPATH.replace("./", ""));
            File[] list = fakepath.listFiles();

            FileOutputStream fout = new FileOutputStream(createFile.getName());
            ZipOutputStream zout = new ZipOutputStream(fout);
            Arrays.stream(list).forEach(src -> {
                try {
                    ZipEntry zipEntry = new ZipEntry(new File(src.getAbsolutePath()).getName());
                    zout.putNextEntry(zipEntry);

                    FileInputStream fin = new FileInputStream(FAKEPATH + src.getName());
                    byte[] buffer = new byte[1024];
                    int length;

                    // input file을 1024바이트로 읽음, zip stream에 읽은 바이트를 씀
                    while((length = fin.read(buffer)) > 0){
                        zout.write(buffer, 0, length);
                        zout.flush();
                    }

                    zout.closeEntry();
                    fin.close();
                    src.delete();
                }
                catch (Exception e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            });
            String filePath = createFile.getAbsolutePath();

            zout.close();
            response.setContentType("application/zip");
            response.setCharacterEncoding("utf-8");

            String browser = getBrowser(request);
            String disposition = getDisposition(fileName, browser);

            response.addHeader("Content-Disposition", disposition);

            FileInputStream fis = new FileInputStream(createFile.getName());
            BufferedInputStream bis = new BufferedInputStream(fis);
            ServletOutputStream so = response.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(so);

            byte[] data = new byte[2048];
            int input = 0;
            while((input=bis.read(data))!=-1){
                bos.write(data,0,input);
                bos.flush();
            }

            if(bos!=null) bos.close();
            if(bis!=null) bis.close();
            if(so!=null) so.close();
            if(fis!=null) fis.close();

            if(fakepath.isDirectory()) {
                fakepath.delete();
            }
            createFile.delete();
//            return filePath;
        } catch(IOException ioe){
            log.error(ExceptionUtils.getStackTrace(ioe));
        }
    }

 

이렇게 함수를 준비해보았습니다. 실제로 Service에서 호출하게 되면 어떤 코드로 이루어질까요? 

 

@Service
public class CommonService {
    public String downloadFileZip(HttpServletRequest request, HttpServletResponse response, Map<String, Object> param) throws Exception {
        List<File> files = new ArrayList<>();
        int paramSetSize = param.keySet().size();

        // 파라미터의 개수가 0이면 전체 검색이므로 거릅니다. 
        if(paramSetSize == 0) {
            files = fileMapper.selectFile(param).stream().map(FileDto.FileQueryResponse::getFile).collect(toList());
        }

        // 검색된 파일의 개수가 0인 경우 다운로드를 하지 않습니다.
        if(files.size() == 0) {
            return "";
        }

        // 검색된 파일의 개수가 20개가 넘는 경우 에러를 발생시킵니다.
        if(files.size() > 20) {
            throw new ArrayStoreException();
        }

        ZipDownloadUtils.fileDownload(files);
        String fileName = param.getOrDefault("filename", "파일 다운로드").toString();
        ZipDownloadUtils.toZipAndGetUrl(request, response, fileName);
        return "";
    }
}

 

위에서 작성한 소스를 다 합치면 아래와 같이 쓸 수 있습니다.

@Slf4j
public class ZipDownloadUtils {

    final static String FAKEPATH = "./fakepath/";
    final static Charset UTF8 = Charset.forName("UTF-8");
    final static Charset ISO = Charset.forName("ISO-8859-1");

    public static void toZipAndGetUrl(HttpServletRequest request, HttpServletResponse response, String fileName) {
        try{
            String extension = ".zip";
            fileName += extension;
            String fileNameEncoding = new String(fileName.getBytes(UTF8), ISO);
            File createFile = new File("test.zip");
            File fakepath = new File(FAKEPATH.replace("./", ""));
            File[] list = fakepath.listFiles();

            FileOutputStream fout = new FileOutputStream(createFile.getName());
            ZipOutputStream zout = new ZipOutputStream(fout);
            Arrays.stream(list).forEach(src -> {
                try {
                    ZipEntry zipEntry = new ZipEntry(new File(src.getAbsolutePath()).getName());
                    zout.putNextEntry(zipEntry);

                    FileInputStream fin = new FileInputStream(FAKEPATH + src.getName());
                    byte[] buffer = new byte[1024];
                    int length;

                    // input file을 1024바이트로 읽음, zip stream에 읽은 바이트를 씀
                    while((length = fin.read(buffer)) > 0){
                        zout.write(buffer, 0, length);
                        zout.flush();
                    }

                    zout.closeEntry();
                    fin.close();
                    src.delete();
                }
                catch (Exception e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            });
            String filePath = createFile.getAbsolutePath();

            zout.close();
            response.setContentType("application/zip");
            response.setCharacterEncoding("utf-8");

            String browser = getBrowser(request);
            String disposition = getDisposition(fileName, browser);

            response.addHeader("Content-Disposition", disposition);

            FileInputStream fis = new FileInputStream(createFile.getName());
            BufferedInputStream bis = new BufferedInputStream(fis);
            ServletOutputStream so = response.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(so);

            byte[] data = new byte[2048];
            int input = 0;
            while((input=bis.read(data))!=-1){
                bos.write(data,0,input);
                bos.flush();
            }

            if(bos!=null) bos.close();
            if(bis!=null) bis.close();
            if(so!=null) so.close();
            if(fis!=null) fis.close();

            if(fakepath.isDirectory()) {
                fakepath.delete();
            }
            createFile.delete();
//            return filePath;
        } catch(IOException ioe){
            log.error(ExceptionUtils.getStackTrace(ioe));
        }
    }

    public static void fileDownload(List<kr.component.common.domain.File> files) throws Exception {
        String dir = FAKEPATH;
        File file = new File(dir);
        file.mkdir();
        files.forEach(f -> {
            try {
//                System.out.println("-------Download Start------");
                URL Url;
                byte[] buf;
                int byteRead;
                int byteWritten = 0;
                Url = new URL(f.getPath());
                OutputStream outStream = new BufferedOutputStream(new FileOutputStream(dir + f.getName()));
                URLConnection uCon = Url.openConnection();
                InputStream is = uCon.getInputStream();

                buf = new byte[1024];
                while ((byteRead = is.read(buf)) != -1) {
                    outStream.write(buf, 0, byteRead);
                    byteWritten += byteRead;
                }
//                System.out.println("Download Successfully.");
//                System.out.println("File name :일바 " + f.getName());
//                System.out.println("of bytes  : " + byteWritten);
//                System.out.println("-------Download End--------");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    private static String getBrowser(HttpServletRequest request) {
        String header = request.getHeader("User-Agent");
        if (header.indexOf("MSIE") > -1 || header.indexOf("Trident") > -1)
            return "MSIE";
        else if (header.indexOf("Chrome") > -1)
            return "Chrome";
        else if (header.indexOf("Opera") > -1)
            return "Opera";
        return "Firefox";
    }

    private static String getDisposition(String filename, String browser)
            throws UnsupportedEncodingException {
        String dispositionPrefix = "attachment;filename=";
        String encodedFilename = null;
        if (browser.equals("MSIE")) {
            encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll(
                    "\\+", "%20");
        } else if (browser.equals("Firefox")) {
            encodedFilename = "\""
                    + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
        } else if (browser.equals("Opera")) {
            encodedFilename = "\""
                    + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
        } else if (browser.equals("Chrome")) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < filename.length(); i++) {
                char c = filename.charAt(i);
                if (c > '~') {
                    sb.append(URLEncoder.encode("" + c, "UTF-8"));
                } else {
                    sb.append(c);
                }
            }
            encodedFilename = sb.toString();
        }
        return dispositionPrefix + encodedFilename;
    }
}

 

반응형
댓글
공지사항