Article Category

분류 전체보기 (202)
사는 이야기 (15)
Programming (174)
Photo (5)
게임 (2)
프로젝트 (0)

Recent Comment

Recent Trackback

Calendar

«   2019/08   »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

Archive

  • Total96,717
  • Today3
  • Yesterday4
  1. 2009.08.31
    Tomcat과 한글
  2. 2009.08.06
    Tomcat Virtual Host 설정 (1)

개요

웹프로그래밍을 하다보면 항상 한글인코딩과 관련된 문제에 봉착하게 된다. 그럴때면 보통 웹검색을 통해 대부분의 해결책을 찾고는 하였다. 하지만, 그 해결책이라는 것이 대부분 경험담에 불과하고, 우리가 맞닥뜨린 문제와는 미묘하게 다른부분이 있기도 하여 근본적인 해결방법이 되지 않을 경우도 있다. 우연찮게 해결을 하더라도, 시간에 쫒기다보니, 근본적인 원인을 해결하지 못하고 해결하기에 급급하여 다음에 또 비슷한문제를 겪는 악순환을 겪기도 한다.


실제로 톰캣에서 한글이 어떻게 인코딩/디코딩되는걸까?

본인 역시 늘 고민해오던 문제였는데, 마침 시간이 나게되어, JSP파일에서부터 클라이언트의 응답메시지와 요청메시지, 그리고 톰캣에서의 인코딩과정에 대해 간략하게 정리해보았다.


이 글은 톰캣 5.5.x, 와 6.0.x 에서 테스트 되었다.

JSP 인코딩과 HTML 인코딩

우리가 작성한 JSP 파일은 컨테이너(여기서는 톰캣)에 의해 JAVA 파일로 변경이 된다.
이때 첫번째 인코딩이 일어나게 되는데, 이 시점에서 확인하여야할것은 JSP 파일 인코딩과 JSP 문자 인코딩이다. JSP 파일 인코딩과 JSP 문자 인코딩이 다를 경우 여지없이 한글이 깨져 버린다.


JSP 파일 인코딩은 실제 해당 파일이 작성될때 사용된 캐릭터셋으로

Eclipse 같은 툴을 사용하면 확인할 수 있다.

사용자 삽입 이미지


JSP 문자 인코딩은 컨테이너가 JSP파일을 JAVA로 변경하기위해 읽어들일때, 해당 JSP파일이 어떤 문자셋으로 이루어져 있는가를 컨테이너에게 알려주기 위해 JSP의 page 지시자에 선언되어 있는 캐릭터셋이다.

JSP 파일의 문자 인코딩 캐릭터셋은 JSP의 page 지시자의 속성중에 pageEncoding를 확인하면 된다. 디폴트 값은 ISO-8859-1 이지만, ContentType에서 인코딩을 정의 하지 않았을 경우에만 디폴트 값이 적용된다.


예를 들어 다음과 같은 내용의 JSP파일을 생성하였다고 가정하자. (파일 인코딩은 UTF-8이라고 가정한다)


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="EUC-KR"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>테스트</title>
</head>
<body>
<div>
    <form action="/test2.jsp" method="post">
        <input type="text" name="keyword" > <input type="submit">
    </form>

</div>
</body>
</html>


다음은 위의 JSP 파일이 변경된 JAVA 파일의 일부이다.

(서블릿을 컨테이너에 배포한다음 TOMCAT_HOME/work/ 하위에서 찾을 수 있다)


out.write("\r\n");
      out.write("<head>\r\n");
      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n");
      out.write("<title>�����/title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("<div> \r\n");
      out.write("\t<form action=\"/test2.jsp\" method=\"post\">\r\n");
      out.write("\t\t<input type=\"hidden\" name=\"actions\" value=\"test2\" >\r\n


JSP의 <title>의 한글이 깨져 있음을 확인할 수 있다.


page 지시자에는 pageEncoding 뿐만 아니라 contentType 이라는 속성도 있는데, 이 속성은 MIME 타입과 해당 페이지에서 사용될 인코딩 문자셋을 설정한다. 즉, 이 값에 따라 브라우져가 서버의 메시지를 어떤 문자셋으로 해석할지가 결정되며, 페이지에서 사용되는 문자들의 인코딩 문자셋이 결정된다. 대부분의 브라우져의 기능을 살펴보면 문자 인코딩을 변경하는 메뉴가 있는데, 여기에 기본으로 선택된 값이 contentType에 설정된 값이다.

사용자 삽입 이미지


contentType 이 생략될 경우 pageEncoding 값을 따르며, pageEncoding 까지 생략되면 기본적으로 ISO-8859-1 문자셋을 따른다.


예를 들어 contentType의 값이 UTF-8 이라면 클라이언트의 브라우져는 서버의 응답을 UTF-8로 해석하여 HTML 파일을 화면에 뿌리게 된다. 그리고 페이지의 모든 문자들은 UTF-8의 문자셋을 기반으로 하게된다.


HTML의 meta 태그내의 content 속성은 단순히 메타 태그로써 페이지의 정보를 알려주는 기능외에 실제 인코딩에 관여하지 않는다는 것에 주의하도록 한다.

 <head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>test</title>
</head>


서블릿과 톰캣 인코딩

톰캣은 클라이언트에서 서버로의 요청메시지를 서블릿 스펙에 따라 기본적으로 ISO-8859-1 로 인코딩되어 처리되도록 되어 있다. 즉, 클라이언트에서의 문자열이(contentType) UTF-8 이든, EUC-KR 이든 ISO-8859-1로 인코딩 된다는 것이다.

(메시지의 인코딩은 앞에서 언급한바대로 웹페이지의 ContetType 값에 따른다)


즉, 다음의 코드가 실행되는 것과 같다.

//UTF-8 인코딩된 메시지
new String( "UTF-8".getByte("UTF-8"),"ISO-8859-1");

//EUC-KR 인코딩된 메시지
new String("EUC-KR".getByte("EUC-KR"),"ISO-8859-1");


어라? 이상하지 않은가? 한글 처리를 위한 UTF-8이나 EUC-KR은 2바이트 문자인데, 1바이트 문자인 ISO-8859-1로 인코딩하면 문자열이 깨지지 않겠는가? 깨어지는 것처럼 보이지만 실제로는 문자열의 바이트값이 보존된다. 여기서 인코딩/디코딩의 개념과 ISO-8859-1 이라는 문자셋에 대한 이해가 필요하다.


우선 문자셋이라는 것은 글자와 바이트의 매핑관계를 정의하는 것으로 문자셋에 따라 이 매핑관계가 달라진다.

예를 들어 EUC-KR문자셋에서 "테스트" 라는 문자열은 "c5d7bdbac6ae" 바이트배열값으로 매핑된다.

이때 EUC-KR 문자셋은 2바이트 문자셋이므로 c57b값이 "테" 라는 문자열값에 매핑되며 7bdb와 c6ae는 각각 "스"와 "트"에 매핑이된다.

이렇게 문자열을 바이트 시퀀스로 변환하는 과정을 인코딩, 그 반대 과정을 디코딩이라고 한다.


이론적으로 EUC-KR은 2바이트 문자로써 65535개의 문자를 매핑하는것이 가능하지만 실제로는 대부분의 바이트가 비어있다. 이는 UTF-8이나 KSC5601, MS949 등 마찬가지이다. 문자셋들은 동일한 바이트값에 대한 매핑문자가 서로 다르며, 문자 구성또한 달라 매핑이 불가능한 문자도 있다. 예를 들어 유니코드 "뷁" 문자를 EUC-KR 로 인코딩할경우 "뷁" 문자가 EUC-KR 문자셋에 정의되어 있지 않으므로, 자바는 이를 "?(0X3F)"로 변경해버린다. (이 또한 JVM 벤더에 따라 다르게 동작한다.) "?"로 매칭된 문자는 원래의 유니코드로 디코딩된다고 해도 원래의 "뷁" 문자를 표현하지 못하게된다. 이처럼 문자간의 인코딩/디코딩시에 데이터 손실이 일어나게되는데, ISO-8859-1 문자셋은 1바이트로 이루어져있지만, 유일하게 빈공간없이 모든 바이트값에 대해 문자를 매핑하고 있다. 따라서, ISO-8859-1 문자셋으로 인코딩되었다하더라도 원래 문자셋으로 디코딩만 하면 데이터의 손실없이 원래의 문자를 표현할수 있게된다.

ISO-8859-1 문자셋에 대해서는 다음을 참고하라 (http://en.wikipedia.org/wiki/ISO/IEC_8859-1#ISO-8859-1)


다시 톰캣이야기로 돌아가서 위에서 언급하길 서블릿 스펙에 따라 톰캣은 모든 요청메시지를 ISO-8859-1로 인코딩한다고 하였다.

즉, EUC-KR 문자열의 바이트 값인 "c5d7bdbac6ae"를 톰캣이 받게되면 이 바이트 배열을 ISO-8859-1 문자셋으로 인코딩된것으로 간주하고 ISO-8859-1문자셋을 이용해 디코딩해버린다. 그렇게 되면 "c5d7bdbac6ae" 바이트배열은"Å×½ºÆ®" 이라는 문자열로 디코딩되는데, ISO-8859-1문자셋은 각 바이트에 매칭되는 문자값이 모두 존재하기때문에 의미없는 문자로 표현될지언정 매칭문자가 없어 "?"로 표현될 일이 없다. 즉, 문자열이 깨지지 않는다. 따라서, "Å×½ºÆ®" 문자열을 다시 EUC-KR 문자셋으로 인코딩하게되면 원래의 문자열로 돌아가게되는것이 보장된다.

byte[] bytes = "테스트".getBytes("EUC-KR");
String str = new String(bytes,"ISO-8859-1");
System.out.println(hex(bytes)); //hex() 는 바이트 배열값을 HEX코드로 출력하는 기능
System.out.println(str);
bytes = "Å×½ºÆ®".getBytes("ISO-8859-1");
str = new String(bytes,"EUC-KR");
System.out.println(hex(bytes));
System.out.println(str);
=============================================
c5d7bdbac6ae
Å×½ºÆ®
c5d7bdbac6ae
테스트


톰캣으로 전달된 ISO-8859-1 인코딩된 데이터는 톰캣 내부에서 사용자의 요청메시지를 처리하기위해 request 객체를 생성하는 과정에서 인코딩 과정을 거치게된다. 이때 적용되는 인코딩 문자셋은 기본적으로 null로써 시스템 프로퍼티인 file.encoding에 정의된 인코딩 문자셋(이하, 시스템 인코딩 문자셋)을 사용하도록 되어 있다. 클라이언트의 요청메시지의 원래 인코딩 문자셋과 시스템 인코딩 문자셋이 다르다면 원래의 문자셋으로 복원이 불가능해지므로 한글 인코딩이 깨어지게 된다. 따라서 한글 데이터를 보존하기 위해서는 new String("문자열".getByte("ISO-8859-1"), "EUC-KR") 같이 클라이언트에서 전송시에 사용된 문자셋으로 디코딩 과정을 먼저 실행한다음 원래대로 문자열을 복원하여야 한다. 번거로운 인코딩작업 대신에 클라이언트의 요청 메시지 전체에 대해 명시적으로 인코딩 문자셋을 지정해주는 방법이 있는데, 그 방법은 아래와 같다. (단 인코딩 문자셋은 request 객체가 생성되기전에 정의되어야 한다)

request.setCharacterEncoding("문자셋이름")

톰캣은 클라이언트의 요청메시지에 대한 request 객체의 생성이 끝나면, 응답 메시지를 작성하기위해 response 객체를 생성하게 된다. 이때 response 객체의 생성시에 사용되는 인코딩 문자셋은 JSP 파일의 page 지시자의 contentType 속성에 정의된 인코딩 문자셋을 따르게 된다. 서버의 응답 메시지를 생성할때 사용되는 인코딩 문자셋과 브라우져에서 이를 해석하는 문자셋이 항상 contentType을 따르도록 되어있으므로 프로그래머는 이 단계에서는 인코딩에 대해 신경쓰지 않아도 된다. 하지만 문제는 request 객체와 response 객체 사이에서 일어난다. 대부분의 응답메시지는 요청메시지의 데이터를 포함하게 되는데, 요청메시지에 대해 디코딩을 완벽하게 수행했다하더라도, request 객체와 response 객체의 인코딩 문자셋이 다르다면 request 객체의 데이터가 response 객체에서 인코딩이 깨어지게 된다.


이렇게 생성된 서버의 응답메시지는 ISO-8850-1 인코딩되어 클라이언트의 브라우져에 전달된다. 전달된 응답 메시지는 클라이언트의 브라우져에서 contentType에 정의된 인코딩 문자셋을 이용하여 해석된 다음 출력되게 된다.


GET과 POST

앞절에서 클라이언트 요청메시지에 전체에 대해 명시적으로 인코딩 문자셋을 지정하는 방법을 소개하였다. 하지만 이 경우 request 객체가 생성되기 이전에 인코딩 문자셋을 지정하여야 하므로 톰캣에서는 필터를 사용하여 인코딩 문자셋을 지정할 것을 권장하고 있다. 하지만 이 경우에도 약간의 문제가 있다. 클라이언트의 요청메시지는 GET과 POST 두 가지 방식으로 서버에 전달되는데, 필터에 의한 인코딩 문자셋을 지정하는 방법은 POST요청에 한해서만 유효하다는 점이다. GET 방식으로 전달 되는 파라메터는 URL에 포함되어 전달되는데, URL은 요청 메시지 내부의 파라메터는 지정된 인코딩 문자셋을 사용하지않고 기본적으로 ISO-8859-1 인코딩 문자셋에 따라 해석을 하기 때문이다. 따라서 URL에 대한 인코딩 문자셋을 명시적으로 지정해줄 필요가 있다. 이를 위해서는 톰캣의 tomcat_home/conf/server.xml 파일의 Connector 부분의 설정을 변경하여야 한다.

 ...
    <Connector ... port="8080" URIEncoding="UTF-8" />
...


정리

지금 까지의 이야기를 정리하자면 한글을 적절하게 처리하기 위해서는 우선, 요청 페이지의 contentType을 확인해야 하며, 사용자의 요청메시지가 GET인지, POST인지를 확인한다. 그런다음 각각의 요청메시지 타입에 따라 서버에서 메시지를 처리하는 인코딩 문자셋을 확인한 다음, 마지막으로 응답 페이지의 contentType을 확인하여 이 4가지 인코딩 문자셋이 일치하는가를 살펴보아야 한다.


이상의 내용은 실제 톰캣을 소스레벨까지 내려가서 살펴본것이아니라, 다분히 개인적인 지식과 경험에 의해 쓰여진 것이므로 다소 사실과는 다를지도 모른다. 하지만, 여러가지 실험과 근거에 의해 작성되었으므로 신뢰할 만하다고 생각한다. 한글 처리로 곤란함을 격게된다면 위의 과정을 밟아가면서 어디에서 인코딩이 깨지는가를 확인해보면 빠르게 문제를 해결을 할 수 있으리라 생각한다. 부디 한글로 인해 고민하는 날이 없기를 바란다.
Trackback 0 and Comment 0
1. Context Path 설정
톰캣 4 버전대의 경우, Context Path를 [CATALINA_HOME]/conf/server.xml 에 기술 했지만 5 버전부터는 그 방법이 바뀌었습니다.

  - 1단계 :  [CATALINA_HOME]/conf/server.xml 에 HOST 항목 추가하기
     톰캣 5.5 를 설치하고 난 후 server.xml 를 열어보면 아래와 같이 기본적으로 localhost 란 이름으로 HOST
     항목이 정의되어 있을 겁니다.

    <Host
        appBase="webapps"
        autoDeploy="false"
        liveDeploy="false"
        name="localhost">
    </Host>
      이 아래에 이어서 추가하고자하는 HOST항목을 추가 해 줍니다.
    <Host
        appBase="webapps"
        autoDeploy="false"
        liveDeploy="false"
        name="www.new_site.com">
    </Host>

   - 2단계 :  [CATALINA_HOME]/conf/Catalina 디렉토리에 HOST 디렉토리 추가하기
      디폴트로 [CATALINA_HOME]/conf/Catalina/localhost 란 디렉토리가 있을겁니다.
      [CATALINA_HOME]/conf/Catalina 디렉토리 내에 1단계에서 추가한 HOST ( www.new_site.com )의 이름을 갖는 디렉토리를 추가합니다.

      [CATALINA_HOME]/conf/Catalina/www.new_site.com

    - 3단계 :  ROOT.xml 생성
      2단계에서 생성한 디렉토리 내에 빈 ROOT.xml 을 만들고 그 파일에 context path를 기술해 줍니다. 
      <?xml version="1.0" encoding="UTF-8"?>
      <Context
          docBase="D:/Project2007/AirForceClubReservationSystem/build/web"
          privileged="true">
      </Context>

      여기서 docBase는 웹어플리케이션이 위치하는 절대경로를 기술해 줍니다.


2. 공용 라이브러리 설정

    - 4단계 : 공용 라이브러리 배치하기
      위와같은 순서로 톰캣서버에 여러개의 호스트가 존재 할 수 있는데 모든 호스트가 공통으로 사용하는 라이브러리 ( JDBC드라이버와 같은 ) 등이 있을 수 있습니다. 각각의 호스트별로 java 옵션을 통하여 클래스패스를 걸어주셔도 되지만 톰캣이 올라가는 와중에 참조해야 하는 라이브러리라면 굳이 옵션으로 주시지 않으셔도 됩니다.

      [CATALINA_HOME]/common/lib 디렉토리안에 필요한 라이브러리를 넣어두시면 톰캣 스타트업시 별도의 클래스패스를 잡지 않아도 자동 참조를 시도 합니다.

Trackback 0 and Comment 1