저자 John O'Conner 본 테크 팁에는 UTF-8로 인코딩된 일본어 문자세트가 포함되어 있으며, 이 문자를 보려면 사용자의 운영체제에 일본어 문자 포함 폰트가 제공되어야 한다. 일본어 폰트를 구하는 방법 중 하나로써 Cyberbit.ZIP 파일을 다운로드할 수 있다.
GET이나 POST 명령어로 애플리케이션 데이터 인코딩 방식을 결정하는 업계 표준은 현재 없다. 더군다나, 웹 서버 및 데이터베이스 관리자들이 텍스트가 브라우저에서 데이터베이스로 옮겨질 때 데이터의 정확도에 영향을 주는 문자 인코딩 변환에 관해 거의 알지 못하는 경우도 허다하다. 따라서 본 테크 팁에서는 문자 데이터를 브라우저에서 데이터베이스로(또는 역으로) 정확하게 옮기기 위한 유용한 힌트와 베스트 프랙티스를 소개하고자 한다.
브라우저 디스플레이 및 폼 제출
"Hello, world!"를 일본어로 표시하는 HTML 페이지를 검토하는 것으로 시작해보자. 다음은 페이지의 HTML 코딩이다.
<html> <head> <title>Hello World with Charset Meta Tag</title> </head>
요즘의 브라우저는 HTML 페이지가 브라우저에 충분한 힌트를 제공하기만 하면 사실상 어떤 텍스트라도 정확하게 디스플레이할 수 있으며, 브라우저는 힌트를 이용하여 적절한 폰트와 인코딩을 선택하고 이를 통해 문자를 해석한다. 브라우저에 문자세트 정보를 제공하지 않을 경우, 일단 문자는 데이터 손실 없이 HTML 페이지를 통해 수신되지만 동시에 부정확하게 해석될 가능성도 있다. 발생 가능한 한 가지 경우를 살펴보자.
Firefox 1.07 같은 브라우저는 문자세트를 추측할 수 있을 뿐인데, 이 경우 브라우저는 파일 내용을 ISO 8859-1로 인코딩된 텍스트로 잘못 해석하고 있다.
텍스트는 실제로는 UTF-8(유니코드 인코딩) 텍스트이며, 브라우저는 HTML 내의 인코딩 정보를 META 태그로 확인한 후 일본어 인사말을 정확하게 표시할 수 있다. 다음은 파일의 문자세트를 기술하는 META 태그를 포함하도록 업데이트된 페이지를 위한 HTML 코딩이다(META 태그는 포맷상의 목적으로 두 행에 걸쳐 표시된다). <html> <head> <!-- Add a META tag to describe the file's <!--charset encoding -->
더 많은 정보와 일본어 가능 폰트가 제공되면 브라우저는 메시지를 정확하게 표시할 수 있다.
JSP(JavaServer Pages) 컴포넌트에서 문자 인코딩은 어떻게 이루어지는가? JSP 기술을 이용할 경우에는 반드시 생성되는 HTML 페이지의 문자세트를 지정해 주어야 하는데, 이를 위해서는 페이지 지시어 또는 HTML META 태그를 이용하면 된다. JSP 페이지 지시어는 JSP 파일의 첫 번째 요소여야 하고, 또 문자세트 설정이 있는 contentType 속성을 포함해야 한다. JSP 컴파일러는 아래에 표시된 pageEncoding 속성을 사용하여 JSP 페이지의 인코딩 결정을 돕는다(본 팁에 게재된 다수의 코드 예제에서는 포맷상의 목적으로 JSP 지시어 <%@page ... 를 두 행에 걸쳐 표시한다). <%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> ...
이제 페이지가 정확하게 인코딩되고 필요한 정보를 브라우저에 전달한다. 하지만, 아직 브라우저에는 아무것도 전달되지 않은 상태이다. 이번에도 브라우저는 HTTP 헤더 또는 HTML META 태그에 저장된 문자세트 정보로부터 페이지의 인코딩 정보를 얻게 되는데, 브라우저 역시 GET 또는 POST 명령어를 위한 폼 데이터를 인코딩할 때 이 정보를 이용한다. HTML FORM 태그에 accept-charset 속성이 존재할 경우, 브라우저는 그 대신 해당 설정을 이용한다. 다음 JSP 페이지에는 서유럽과 아메리카의 많은 국가에서 사용하는 언어와 스크립트를 처리하는 데 흔히 이용되는 문자세트인 ISO-8859-1 텍스트가 포함되는데, 이 페이지는 사용자의 이름을 요구하는 HTML 폼을 생성한다. 사용자가 엔트리를 제출하면 동일 페이지는 들어오는 GET 명령어를 처리하고 NAME 매개변수에 지시된 사용자에게 인사말을 프린트한다. <% @page pageEncoding="ISO-8859-1" contentType="text/html; charset=ISO-8859-1" %> <html> <head> <title>Say Hello!</title> </head>
JSP 페이지를 표시한 다음 폼에 José를 입력하고 submit 버튼을 누르면 브라우저는 다음의 GET URL을 생성한다. http://localhost/charconv/sayhello.jsp?NAME=Jos%E9
URL 내의 NAME 매개변수가 이상해 보일지 모르지만 이는 맞는 것이다. %E9은 URL 인코딩 문자로, ISO 8859-1 문자세트에 있는 문자 코드포인트의 16진수 정수 값이다. 8859-1 문자세트로 된 GET과 POST 데이터를 예상하는 서버의 경우, 이 URL 인코딩 엔티티를 디코딩하는 데 아무런 문제가 없는데, 간단히 말해서 URL 인코딩은 모든 비 ASCII 문자와 특정 ASCII 문자를 %HH 형태의 16진수 문자열로 인코딩할 것을 요구하는 URL을 위한 웹 표준이라 할 수 있다. 이 표준은 데이터 인코딩 시 사용할 문자세트까지 규정해주지는 않는다. 일본, 한국, 중국의 사용자가 위에 표시한 것과 동일한 JSP 페이지에 이름을 입력한다고 가정해보자. 문자가 ISO 8859-1 문자세트에 들어 있지 않을 가능성이 크므로, 이를 해당 문자세트로 인코딩하면 브라우저에 심각한 문제가 초래된다. 브라우저는 이런 문제를 어떻게 처리할 수 있을까? 그 해답은 브라우저마다 독립적인 방식으로 서로 다르게 문제를 처리한다는 것이다. 田中(타나카)라는 일본어 이름이 주어지면, Firefox 버전 1.07 브라우저는 다음과 같은 GET URL을 생성한다(명령어는 한줄로 입력). http://localhost/charconv/sayhello.jsp? NAME=%26%2330000%3B%26%2320013%3B
브라우저는 8859-1 문자세트 내의 일본어 문자를 표현할 수 없다는 것을 알고 있으므로 (따라서 시도조차도 하지 않는다), 대신 문자를 숫자 레퍼런스로 인코딩한다. 이 경우 각 문자는 &#D; 형태로 인코딩되어 있는데, 여기서 D는 유니코드 문자세트의 10진수 코드포인트 값을 나타낸다. 문자 田의 십진수 코드포인트는 30000이고 문자 中의 코드포인트는 20013이다. 이 인코딩을 적용하면 다음과 같은 매개변수 문자열이 생성된다. NAME=田中
이어서 URL 인코딩이 수행되어 특수문자&, #, ;를 해당 %HH 값으로 변환한다. NAME=%26%2330000%3B%26%2320013%3B
JSP 페이지가 이 매개변수를 검색하여 그대로 브라우저에 반환한다면 외관상으로는 데이터 손실이 발생하지 않는다. 페이지의 문자세트는 여전히 8859-1이지만 브라우저는 숫자 레퍼런스를 해석하고 그에 따라 일본어 문자를 표시하기 위해 최선을 다한다. 하지만, 여기에는 여전히 문제점이 잠복해 있는데, 즉 GET과 POST 명령어를 처리하는 서버 측 코드가 통상적으로 URL 인코딩 문자열은 이해하지만 숫자 레퍼런스는 이해하지 못한다는 점이다. 이전의 NAME 매개변수를 읽는 대부분의 서블릿의 경우, 문자열을 田中으로 디코딩하고 이것이 일반 문자와 전혀 다른 것을 나타낸다는 사실을 모를 것이다. 달리 말해서, 田中에 대한 진짜 일본어 문자는 알 수 없게 되는 것이다(의도적으로 이런 상황을 계획하지 않는 한). 또한, 실제 문자세트 코드포인트 또는 인코딩 값 대신에 숫자 레퍼런스를 전송해서는 안 되는데, 그렇지 않을 경우 문자가 계속 애플리케이션을 통과하기 때문에 데이터 손실이 초래될 우려가 있다. 이 문제를 해결하려면 항상 처리 대상 문자를 모두 처리할 수 있는 HTML 페이지 인코딩을 선택해야 한다. 다국어 사용자를 확보하고 복수의 스크립트를 처리할 생각이라면 반드시 해당 스크립트를 나타내는 문자세트를 사용해야 한다. UTF-8은 유니코드 문자 인코딩으로, 전세계에서 실질적으로 사용되고 있는 모든 문자를 정확하게 표현할 수 있다. 페이지에서 8859-1 대신 UTF-8을 사용하면 브라우저는 다른 URL을 생성하게 된다(URL은 포맷상의 목적으로 두 행에 걸쳐 표시된다). http://localhost/charconv/sayhello.jsp? NAME=%E7%94%B0%E4%B8%AD
이는 UTF-8 문자세트를 사용하여 정확하게 인코딩된 NAME 매개변수이다. UTF-8은 田中을 문자 당 3바이트로 인코딩하는데, 각 바이트는 ASCII 범위를 넘는 값을 가지므로 브라우저는 값을 URL 인코딩한다. 이로써 각 바이트에 대해 %HH 형태가 산출되며, UTF-8 형태 데이터를 예상하는 서버는 매개변수를 정확하게 처리할 수 있게 된다. 웹 서버 처리 앞에서 본 것처럼, 요즘의 브라우저는 페이지 또는 폼 인코딩으로부터 힌트를 취하고 폼 데이터를 동일한 인코딩으로 다시 서버에 전송한다. 하지만, 대부분의 웹 서버들은 문자세트 선택을 인식하지 못하는 상태로 남아 있게 되고, 이들은 통상적으로 인코딩이 ISO-8858-1인 것으로 가정한다. 애플리케이션이 UTF-8로 된 GET 매개변수를 URL 인코딩하더라도 Tomcat 5.5.9나 Sun Java System Application Server 8.1 같은 서버는 문자세트 인코딩을 8859-1로 가정한다. 그 결과, 텍스트 데이터는 간단한 웹 기반 애플리케이션에서조차 다양한 계층을 거치면서 거의 즉시 손상되다시피 한다. 기존의 JSP 코드를 UTF-8 문자세트를 이용하도록 변경하면 새로운 코드는 다음과 같은 형태가 된다. <%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %> <html> <head> <title>Say Hello!</title> </head>
JSP 페이지를 표시할 경우에는 폼에 José를 입력하고 submit 버튼을 누르면 브라우저가 다음과 같은 GET URL을 생성한다. http://localhost/sayhello.jsp?NAME=Jos%C3%A9
%C3%A9는 José의 URL 인코딩 UTF-8 표현이고, 브라우저는 UTF-8이 문자세트로 지정되어 있는 contentType 설정에서 힌트를 취한다. 브라우저가 다음의 응답 텍스트를 표시하는 것을 알 수 있다. Hello, JosAⓒ!
비 ASCII 데이터를 POST하고 싶을 때는 어떻게 할까? 여러분은 URIEncoding 플래그 때문에 이것이 정확하게 처리될 것이라고 생각할지도 모르겠지만, 불행히도 Tomcat은 POST 된 폼 데이터를 위한 URIEncoding 플래그를 사용하지 않고 대신 ISO-8859-1을 사용한다. 따라서 위의 간단한 애플리케이션은 여전히 José가 아닌 JosAⓒ에게 인사를 하게 된다. 이와 달리 Java System Application Server는 앞에서 본 것처럼 매개변수 인코딩을 설정할 경우 GET과 POST 데이터를 모두 정확하게 해석한다. 다행히 서버 독립형 솔루션도 존재한다. 가장 기본적인 서버 독립형 솔루션으로는 애플리케이션 내의 모든 형태에 대한 문자세트 선택을 지시하는 컨텍스트 매개변수를 설정하는 경우를 들 수 있을 것이다. 사용자의 애플리케이션은 컨텍스트 매개변수를 읽고 요청 매개변수를 읽기 전에 요청 문자 인코딩을 설정할 수 있다. 또한 서블릿이나 JSP에서의 요청 인코딩 설정도 가능하다. WEB-INF/web.xml 파일에 있는 컨텍스트 매개변수를 다음과 같이 설정한다. <context-param> <param-name>PARAMETER_ENCODING</param-name> <param-value>UTF-8</param-value> </context-param>
다음은 요청 인코딩을 적절히 설정한 후의 JSP 파일의 모습이다. <%@page pageEncoding="utf-8" contentType="text/html; charset=utf-8" %>
<html> <head> <title>J2EE Tech Tip: Say Hello!</title> </head>
이제 폼 데이터를 GET 또는 POST하든 상관없이 프로세싱 JSP 또는 서블릿 코드가 매개변수를 정확하게 읽을 수 있게 되는데, 이는 요청 처리 시 웹 서버에게 어떤 문자세트를 사용할 것인지 명확하게 지정했기 때문에 가능한 것이다. 사용자의 애플리케이션에서 보다 견고한 MVC 패턴을 사용할 경우에는 제어 서블릿으로부터 이와 동일한 매개변수를 읽고 요청 인코딩을 설정하는 것을 잊지 말도록 할 것. 데이터베이스 세팅 사용자의 데이터가 지금까지 브라우저 인코딩과 웹 서버 처리의 난관을 극복했다고 가정한다면, 이제 문자 데이터 변환의 마지막 장애물은 데이터베이스가 될 것이다. 데이터를 데이터베이스에 안전하게 배치하고 손상 없이 검색하려면 반드시 데이터베이스 시스템은 브라우저와 여러분의 미들 티어 비즈니스 로직이 지원하는 모든 문자를 표현할 수 있는 문자세트에 데이터를 저장할 수 있도록 구성되어야 한다. 본 팁의 경우, 여태까지 UTF-8을 사용했으므로 데이터베이스에서 해당 문자세트 선택을 계속하는 것이 타당할 것이다. 그렇지 않으면 데이터베이스의 문자세트에 존재하지 않는 문자가 포함된 텍스트를 저장하려고 할 경우 데이터는 돌이킬 수 없는 상태로 손실될 수밖에 없을 것이다. 요약 요약하자면, 문자 데이터가 브라우저에서 데이터베이스로(또는 역으로) 전달될 때 손실을 막기 위해서는 다음과 같은 조치 사항을 취해야한다.
항상 HTML META 태그나 JSP @page 지시어를 사용하여 HTML 또는 JSP 페이지에 문자세트 정보를 제공한다. 이 정보는 브라우저에게 페이지의 텍스트를 해석하는 방법을 일러주는 역할을 한다. 모든 다국어 및 작성 스크립트를 처리할 수 있는 HTML 문자세트 인코딩을 사용하도록 한다. UFT-8은 다양한 스크립트를 표현할 수 있기 때문에 대부분의 상황에 있어서 가장 적합한 선택이라 할 수 있다. 웹 서버에게 요청 매개변수 처리 시 사용할 문자 인코딩을 일러준다. 이 경우, 서블릿 초기화 매개변수 또는 컨텍스트 매개변수를 이용한다. 애플리케이션에서 사용하게 될 모든 문자세트의 수퍼세트인 데이터베이스 문자세트를 사용한다. 이 경우에도 유니코드 인코딩은 무난한 선택이다. 또한, 데이터베이스 문자세트 인코딩으로 UTF-8이나 UTF-16을 고려하도록 할 것.