sitelink1 http://blog.naver.com/PostList.nhn?blogI...egoryNo=11 
sitelink2 http://blog.naver.com/anabaral/130038977294 
sitelink3  
sitelink4  
sitelink5  
sitelink6  

여기 이슈는 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;

}

 

 

번호 제목 글쓴이 날짜 조회 수
351 java.lang.IllegalArgumentException 황제낙엽 2010.01.18 130513
350 Using RSS in JSP pages (Informa Project) 황제낙엽 2006.01.10 37829
349 JSP 파일에서 getOutputStream() has already been called for this response 에러 황제낙엽 2013.04.24 11479
» 한글 파일명 깨짐으로 살펴본 다국어 처리 문제 (UTF-8) 황제낙엽 2012.03.22 10121
347 세션의 timeout 설정 >> HttpSession.setMaxInactiveInterval() 황제낙엽 2019.07.03 8311
346 [JSON기초04] 자바 JSON 데이터에서 KEY 값 알아오기 (TIP) 황제낙엽 2017.01.18 6641
345 java.util.Queue file 황제낙엽 2022.04.06 5381
344 쓰레드(Thread)를 중간에 종료시키는 방법 황제낙엽 2017.03.15 5127
343 Java 실행 옵션들 황제낙엽 2017.08.23 3367
342 일본어 전각 반각 변환 예제 소스 .첫번째 file 황제낙엽 2007.01.10 3070
341 byte배열에 대한 CRC 를 계산하는 메서드 (java.util.zip.CRC32) 황제낙엽 2010.03.14 2166
340 UTF형태 파일에서 BOM 제거하기 황제낙엽 2008.06.16 1938
339 File.delete() 와 File.deleteOnExit() 황제낙엽 2019.03.24 1887
338 [대용량 파일 업로드] multipart form parser - http file upload, database 저장 java class 연재2 file 황제낙엽 2009.06.19 1831
337 싱글톤 방식의 테스트용 Temporary Data Access Object 황제낙엽 2017.01.12 1603
336 servlet 에서의 json 한글처리 황제낙엽 2013.04.23 1519
335 날짜, 시간 문자열 값으로 Date 오브젝트로 만들기 >> SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US) 황제낙엽 2017.10.31 1516
334 J2EE object-caching frameworks (ObjectCache) 황제낙엽 2007.11.02 1495
333 [대용량 파일 업로드] multipart form parser - http file upload 기능 java class 연재1 file 황제낙엽 2009.06.19 1436
332 JavaMail - 네이버 메일 수신하기(POP3) 황제낙엽 2018.08.20 1413