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;

}

 

 

번호 제목 글쓴이 날짜 조회 수
231 싱글톤 방식의 테스트용 Temporary Data Access Object 황제낙엽 2017.01.12 1603
230 SimpleDateFormat Symbol file 황제낙엽 2016.12.20 74
229 JSON-lib Java Library file 황제낙엽 2013.04.09 91
228 JSP 파일에서 getOutputStream() has already been called for this response 에러 황제낙엽 2013.04.24 11479
227 servlet 에서의 json 한글처리 황제낙엽 2013.04.23 1519
226 -file.encoding의 역할 (다국어, 한국어) 황제낙엽 2013.04.10 235
225 [The type HttpUtils is deprecated] javax.servlet.http.HttpUtils 황제낙엽 2013.03.20 276
224 com.oreilly.servlet.multipart 를 이용한 파일 업로드 file 황제낙엽 2013.03.19 104
223 String to InputSource 황제낙엽 2012.12.03 77
222 Class.getResource() vs. ClassLoader.getResource()/getResources() 황제낙엽 2012.06.24 57
221 Jar파일에 포함된 리소스 접근하는 방법(How to read a resource from a JAR file ) file 황제낙엽 2012.06.24 164
220 Java에서 URL 다루기 file 황제낙엽 2012.06.24 88
219 HttpServletResponse.setContentType(java.lang.String type) 과 MIME 타입 황제낙엽 2012.04.20 172
218 code, codebase 속성과 applet object 동적 생성 file 황제낙엽 2012.04.17 85
» 한글 파일명 깨짐으로 살펴본 다국어 처리 문제 (UTF-8) 황제낙엽 2012.03.22 10121
216 BufferedReader.readLine() hangs 황제낙엽 2012.02.23 520
215 OS 쉘 명령어(shell script) 실행하기 [ProcessBuilder, Runtime.getRuntime().exec()] 황제낙엽 2012.02.22 664
214 PreProcess 실행 (전처리기 만들기) file 황제낙엽 2012.01.04 169
213 javax.script와 타입변환 황제낙엽 2012.01.03 62
212 Scripting within Java 황제낙엽 2012.01.03 69