여기 이슈는 Browser에 따라 다른 파일 다운로드 & 저장 메커니즘을 웹 서버에서 어떻게 조절하느냐를 얘기한다.

 

여기 언급하는 이슈 외에도 다른 여러가지 이슈가 있기 때문에

(이를테면 Jeus가 WAS일 경우 Jeus가 response header의 content disposition 값에 직접 관여하므로 이를 고려해야 하며, 이외에도 잘 모른 채 끼어들어가는 솔루션이나 개발자의 실수 등 여러 이슈가 가능)

여기 나온 해법이 전부가 아님을 일단 일러둔다.

 

     
 

... 코드는 다음과 같은 방식으로 구현되어야 한다.

 

// 빈 간, 특수문자, 한글에 대한 테스트

String filename = "바+보 는=바보.-똥개.txt";

String disposition = getDisposition(filename, getBrowser(request););

char[] content = 적당히......;

 

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

 

// 2009. 2. 19 추가

if ("Opera".equals(getBrowser(request))){

    response.setContentType("application/octet-stream;charset=UTF-8");

}


Writer out = response.getWriter();
out.write(content); // <-- 자기 방식으로 넣으세요.. 대충 쓴 거니..
out.flush();

 

 

... 위의 코드에서 호출하는 메소드들이다.


 

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

            private String getDisposition(String filename, String browser)

                                                                            throws Exception {
                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();
                } else {
                    throw new RuntimeException("Not supported browser");
                }

                return dispositionPrefix + encodedFilename;
            }

 
     

간단히 설명하자면,

1. MSIE는 URLEncoder.encode 를 이용해 UTF-8로 인코드해 주기만 하면 된다.

    (아, 공백 문제는 해결해야 함)

    보통 new String(filename.getBytes("MS949"), "8859_1")  를 이용하는데,

    이건 철저하게 국내용이다. 한글 Windows에서 실행한 IE/FF 라면 보통 이게 먹히겠지만

    외국 로케일을 적용한 경우에는 여지없이 파일명이 깨지므로

    가능하다면 어떤 경우에든 먹히는 해법을 시도하는 것이 좋다.

    이게 의심된다면 자신이 만든 어플리케이션을 유닉스에서 LANG 환경변수를 바꿔가면서

    시도해 보자.

    (혹은 Windows라면 언어 설정을 일본어나 다른 언어로 바꾸고 시도해 보자 - 09.01.13 추가)

    (이에 대한 부가설명이 http://blog.naver.com/anabaral/130042610256 에 있다 - 09.02.15 추가)

 

2. Firefox는 URLEncoder로 인코드한 문자열을 받아들이지 못한다.

    대신 UTF-8 혹은 브라우저가 실행되는 인코딩으로 인코드된 바이너리는 받아들이므로

    일반적으로 통용되는 방식을 사용한다.

    new String(filename.getBytes("UTF-8"), "8859_1") 

    보통은 IE에서와 마찬가지로 위에 MS949를 쓰지만, 고맙게도(?) Firefox는

    위의 인코딩을 UTF-8로만 설정해도 알아서 디코드 해 준다.

    아마도 '브라우저 인코딩' 혹은 '기본 인코딩'으로 시도하고 인식이 안되면 UTF-8을 시도하는 듯.

    다만 공백이 있으면 공백 뒤가 무시되어 잘리므로 앞뒤로 따옴표 " " 를 적용해야 한다.

 

3. Opera는 상당히 독특한데, Opera는 브라우저 인코딩에 맞추어 파일명을 디코드해 저장한다.

    게다가 URLEncoder 로 인코드한 내용도 안 먹힌다.

    그래서 이 경우는 정답이 없다.

    다만 사용자가 별다른 설정을 하지 않는다면 브라우저 인코딩은 그 전까지 접했던 페이지의

    인코딩을 따라가므로 우리가 일관된 인코딩 정책을 가져가고 그에 맞춰 세팅해 주면 된다.

    위의 예제에서는 그 표준이 UTF-8 이었다.

    최근에(2009.2.19)에 테스트한 바로는 Opera는 다운로드 당시의 ContentType 헤더에 정해준

    charset에 민감하다. 그래서 Content-Type 의 charset과 문자열 인코딩의 charset을 일치

    시켜주면 다운로드에 문제가 없다.

    (그래서 다른 브라우저에서의 UTF-8과는 의미가 다르다)

 

4. 구글 Chrome도 써보았는데, 이건 URLEncoder 인코드 문자열을 받아들이지만 뭔가 부족하다.

    몇몇 글자들(+, = 등)이 깨지기 때문이다.

    그래서 ASCII 문자(0x00 ~ 0x7e)로 간주되는 부분들은 encode하지 않는 방향으로 진행한다.

 

5. Safari는 지금까지는 답이 없다..

   무조건 latin1(8859_1)으로 인식하니..

 

이 예제의 테스트는

Internet Explorer 6,

Internet Explorer 7,

Firefox 2.0,

Firefox 3.0,

Opera 9.62,

Chrome 1.0

( 실패했지만 Safari 3.2.1)

에서 진행했다.

 

위의 테스트로 빈 칸, 특수문자, 한글에 대한 테스트가 가능했다.

테스트에 협조해 주신 liquidbird 님께 심심한 감사를 표한다.

 

-------------

2010-08-28일 추가 테스트 했다.

Safari 5.0.1 이 대상이었는데

일단 위의 방법으로 저장 성공했다.

Firefox로 인식하던데.. 위 코드를 고쳐 Safari를 따로 인식하게 하더라도 다운로드 방식은 Firefox와 동일하게 맞추면 될 것 같다.

 

 

 

 

적용된 소스

 

 

/**

* 저장된 임시 엑셀파일을 클라이언트로 전송한다

* @param request

* @param response

* @param tmpFileName

* @throws ServletException

* @throws IOException

*/

private void tempFileDownload(HttpServletRequest request,

HttpServletResponse response, String tmpFileName)

throws ServletException, IOException {

 

String tmpFullName = request.getSession().getServletContext().getRealPath("/")

+ TEMP_EXPORT_DIR + "/" + tmpFileName;

 

if (log.isDebugEnabled()) {

log.debug("load temp excel file="+tmpFullName);

log.debug("request encode="+request.getCharacterEncoding());

log.debug("response encode="+response.getCharacterEncoding());

}

 

HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(tmpFullName));

 

String filename = request.getParameter("filename");

 

if (filename == null) {

filename = "unknown";

}

 

response.reset();

response.setContentType("application/vnd.ms-excel;charset=utf-8");

response.setHeader("Content-Disposition", getDisposition(filename + ".xls", getBrowser(request)));

response.setCharacterEncoding("utf-8");

 

OutputStream out = response.getOutputStream();

 

try {

workbook.write(out);

} finally {

try {

out.close();

} catch (IOException ex) {

 

} finally {

FileUtils.deleteFile(new File(tmpFullName)); //임시파일 삭제

}

}

 

}

 

/**

* 브라우저 종류

* @param request

* @return

*/

private String getBrowser(HttpServletRequest request) {

String header = request.getHeader("User-Agent");

if (header.indexOf("MSIE") > -1) {

return "MSIE";

} else if (header.indexOf("Chrome") > -1) {

return "Chrome";

} else if (header.indexOf("Opera") > -1) {

return "Opera";

}

return "Firefox";

}

 

/**

* 다국어 파일명 처리

* @param filename

* @param browser

* @return

* @throws UnsupportedEncodingException

*/

private 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();

} else {

throw new RuntimeException("Not supported browser");

}

return dispositionPrefix + encodedFilename;

}

 

 

번호 제목 글쓴이 날짜 조회 수
347 java.util.Optional 에서의 map() 함수의 이해 황제낙엽 2026.06.24 666
346 linux(ubuntu) 의 jdk 21 이 설정된 환경에서 jdk 17 로 다운그레이드 절차 황제낙엽 2025.09.22 1532
345 [ChatGPT] javax.* 패키지와 jakarta.* 패키지의 혼합 사용 방안 황제낙엽 2025.04.15 2459
344 [ChatGPT] *.servlet.http.HttpServlet 클래스를 사용할 수 있는 주요 WAS 목록 황제낙엽 2025.04.14 2038
343 [ChatGPT] JDBC API에서 java.sql과 javax.sql 패키지의 차이점 황제낙엽 2025.01.31 2218
342 jakarta.servlet 패키지를 이용한 File Upload 구현 (2024.03.03, Servlet 5.0) 황제낙엽 2025.01.02 2310
341 [ChatGPT] Java EE가 Jakarta EE로 전환 황제낙엽 2025.01.01 2525
340 [ChatGPT] File.delete() 함수로 파일 삭제가 되지 않는 경우 황제낙엽 2024.12.10 2809
339 [ChatGPT] <jsp:include>에서 flush="true"를 설정시의 몇 가지 문제점 과 대안 황제낙엽 2024.07.19 2493
338 [Copilot] JSP, Servlet, web.xml, mysql 한글 설정 황제낙엽 2024.07.12 2555
337 Base64 클래스의 encode, decode 에서 URL-safe 황제낙엽 2024.07.10 2624
336 [Copilot] JSP 에서 다른 페이지를 현재 페이지에 포함시키는 문법 (include) 황제낙엽 2024.06.26 2570
335 Exception 출력에 대한 고찰 황제낙엽 2024.06.10 2198
334 google-auth-library-oauth2-http 라이브러리 다운로드 황제낙엽 2023.11.19 4025
333 firebase-admin-java 라이브러리 다운로드 (firebase admin sdk library) 황제낙엽 2023.11.19 2805
332 Enum 활용 (개인블로그, Effective Java) file 황제낙엽 2023.11.02 2759
331 Enum 활용 (우아한기술블로그) file 황제낙엽 2023.11.02 2790
330 servlet name 에 camel case 를 사용해서는 안되는 이유 황제낙엽 2023.10.24 2508
329 org.apache.commons.fileupload.servlet.ServletFileUpload 이용 예시 황제낙엽 2023.08.25 3336
328 ServletFileUpload.parseRequest() 실행 결과 리스트가 비어있는 오류 황제낙엽 2023.08.25 2935