Java Servlet에서 URL로 들어오는 인코딩 구별하기
서블릿 프로그램을 짜다보면 흔히 인코딩에서 문제가 생기곤 한다.
대부분은 경험 부족에서 기인하는 경우가 많지만, 쌓인 경험이 무색하게도 인코딩 문제 중 몇몇은 손을 들 수 밖에 없다.
특히 주소창에서 전해지는 URL의 경우에는 인코딩을 알 수 없어서, 브라우저에서 강제로 "UTF-8 URL 보내기"와 같은 설정을 하지 않는 이상 쉽게 해결하기 힘들다.
하지만, 컴퓨터에 익숙하지 않은 사람들에게 브라우저의 설정을 건드리라고 하는 것은, 이 사이트에는 접근하지 말아주세요라는 뜻과 다름없다.
또한, 단지 EUC-KR과 UTF-8정도의 구분이 아니라 다른 언어의 인코딩까지 다양하게 지원해야 한다고 가정을 하면 사정은 더욱 복잡해진다.
아마도 완벽하지는 않겠지만, 나름대로 해결책을 강구해보았다.
먼저, 서버에는 모질라에서 인코딩을 자동으로 검사하는데 쓰이는 chardet의 Java 버전인 jchardet(http://sourceforge.net/projects/jchardet/)가 필요하다. 참고로 jchardet의 라이센스는 MPL이다.
또한 브라우저의 경우 URL 인코딩(%XX형태)을 기본적으로 지원해야한다. (ie7,firefox 등)
byte[] buf = ... int lang = ... nsDetector det = new nsDetector(lang) ; det.Init(new nsICharsetDetectionObserver() { public void Notify(String charset) { HtmlCharsetDetector.found = true ; } }); det.DoIt(buf,buf.length, false); det.DataEnd(); String[] charsets = det.getProbableCharsets(); }
위의 코드는 jchardet를 사용하기 위한 간단한 소스다. buf에는 검사를 할 바이트 스트림이 들어가면 되고, 이 스트림에서 가능한 인코딩의 리스트를 가능성이 높은 순서대로 돌려받게 된다. lang의 경우 0은 모든 언어, 1은 일본어, 2는 중국어, 3은 본토 중국어, 4는 대만 중국어, 5는 한국어로 구분한다. CJK관련 코드의 경우 중복되는 코드가 많기 때문에 정확한 결과를 얻으려면 반드시 언어 설정을 해줘야 한다.
언어 설정을 위해서는 브라우저에서 제공하는 로케일 정보를 살펴봐야한다. 물론 로케일 정보를 제공하지 않는 브라우저의 경우에는 적용할 수 없다.
int lang = 0; String country = req.getLocale().toString(); if("ja".equals(country)||"ja_JP".equals(country)) { lang = nsPSMDetector.JAPANESE; } else if("zh".equals(country)) { lang = nsPSMDetector.CHINESE; } else if("zh_CN".equals(country)) { lang = nsPSMDetector.SIMPLIFIED_CHINESE; } else if("zh_TW".equals(country)) { lang = nsPSMDetector.TRADITIONAL_CHINESE; } else if("ko".equals(country)||"ko_KR".equals(country)) { lang = nsPSMDetector.KOREAN; }
위와 같은 코드를 사용하면 브라우저에 설정된 로케일을 보고 lang 변수의 값을 결정할 수 있다.
하지만 이 정도의 노력만으로는 부족하다. 실제로 한글을 UTF-8으로 넣거나 EUC-KR으로 넣어도, det.getProbableCharsets()가 돌려주는 리스트에는 두 가지 인코딩이 모두 포함이 되기때문에, 좀 더 많은 작업을 해야한다.
UTF-8으로 된 URL을 구별하기 위해, UTF-8 디코딩을 한번 했다가 다시 UTF-8 인코딩을 했을 때 같은 결과가 보인다면 UTF-8으로 생각할 수 있다.
logger.info("URI:"+URI); logger.info("Re-encodedURI(UTF-8):"+URLEncoder.encode(URLDecoder.decode(URI,"UTF-8"),"UTF-8").replace("%2F", "/"));
UTF-8으로 된 URI의 경우 위의 로그를 찍어보면 같은 결과가 나온다. replace 부분은 URLEncoder가 패스를 구분하는 / 기호까지 인코딩 시키기때문에 이것을 되돌리기 위한 것이다.
chardet을 통해 구한 인코딩 리스트를 구하고, 위의 코드로 URL이 UTF-8인지 아닌지 알 수 있다면, 인코딩을 구별하기는 한층 쉬워진다.
public static String detectEncoding(HttpServletRequest req) throws IOException { Logger logger = Logger.getLogger(ServletUtil.class); int lang = 0; String country = req.getLocale().toString(); if("ja".equals(country)||"ja_JP".equals(country)) { lang = nsPSMDetector.JAPANESE; } else if("zh".equals(country)) { lang = nsPSMDetector.CHINESE; } else if("zh_CN".equals(country)) { lang = nsPSMDetector.SIMPLIFIED_CHINESE; } else if("zh_TW".equals(country)) { lang = nsPSMDetector.TRADITIONAL_CHINESE; } else if("ko".equals(country)||"ko_KR".equals(country)) { lang = nsPSMDetector.KOREAN; } logger.info("LOCALE="+country); String URI = req.getRequestURI(); byte[] buf = URI.getBytes(); nsDetector det = new nsDetector(lang) ; det.Init(new nsICharsetDetectionObserver() { public void Notify(String charset) { HtmlCharsetDetector.found = true ; } }); det.DoIt(buf,buf.length, false); det.DataEnd(); String[] probableCharsets = det.getProbableCharsets(); for(String charset:probableCharsets) { logger.info("CHARSET="+charset); } logger.info("URI:"+URI); logger.info("Re-encodedURI(UTF-8):"+URLEncoder.encode(URLDecoder.decode(URI,"UTF-8"),"UTF-8").replace("%2F", "/")); String charset; if(URI.equals(URLEncoder.encode(URLDecoder.decode(URI,"UTF-8"),"UTF-8").replace("%2F", "/"))) { // UTF-8 if(probableCharsets.length>0) { charset = probableCharsets[0]; } else { charset = "UTF-8"; } } else { // NOT UTF-8 if(probableCharsets.length>0) { if("UTF-8".equals(probableCharsets[0])) { if(probableCharsets.length>1) { charset = probableCharsets[1]; } else { charset = "ISO-8859-1"; } } else { charset = probableCharsets[0]; } } else { charset = "ISO-8859-1"; } } return charset; }
위와 같이 detectEncoding 함수를 완성할 수 있었다. 다른 분들이 가져가서 더 쓸만하게 바꾸었으면 한다.
생각해 볼 것
1. URL 인코딩 되지 않는 브라우저의 경우에는 어떻게 고치면 될 것인가?
2. 로케일을 지원하지 않는 브라우저의 경우에 사용자의 언어설정을 알 수 있는 방법이 없을까?
3. 위의 코드는 jchardet에서 알려주는 리스트의 결과에서 0과 1 인덱스만을 검사한다. 이 두 가지 항목이 실제 인코딩과 전혀 다른 경우가 생기면 어떻게 해야하는가?
4. Java가 아닌 언어의 경우에는 어떻게 적용할 수 있는가?
댓글 달기