출처 : http://jongsclub.com/webedit/studyView.jsp?num=28
utf-8 인코딩을 이용한 한글 url 처리
[ 조회수: 431 ] |
|
개요
- utf-8 은 유니코드를 위한 가변길이 문자 인코딩 방식중 하나로 켄 톰프슨과 롭 파이크가 만들었다.
- 유니코드 한문자를 표시하기 위해 1byte~4byte 까지 가변적으로 표현한다.
- U+0000부터 U+007F 범위에 있는 아스키 문자들은 UTF-8에서 1바이트만으로 표시된다. 4바이트로 표현되는 문자는 모두 기본 다중언어 평면(BMP) 바깥의 유니코드 문자이며, 거의 사용되지 않는다.
구조
- U+007F(0~127) 까지의 문자는 7비트 아스키 문자와 동일하게 표시된다.
- U+007F 이후의 문자는 4byte 까지의 비트 패턴으로 표시되며 7비트 아스키 문자와 혼동되지 않게 하기 위해 모든 바이트의 최상위 비트는 1 이다.
코드 범위 |
UTF-16BE 표현 |
UTF-8 표현 |
설명 |
000000 ~00007F |
00000000 0xxxxxxx |
0xxxxxxx |
아스키와 동일한 범위 |
000080 ~0007FF |
00000xxx xxxxxxxx |
110xxxxx 10xxxxxx |
첫바이트는 110 으로 시작하고, 나머지 바이트는 10으로 시작함 |
000800 ~00FFFF |
xxxxxxxx xxxxxxxx |
1110xxxx 10xxxxxx 10xxxxxx |
첫바이트는 1110으로 시작하고 , 나머지는 10으로 시작함 |
010000 ~10FFFF |
110110yy yyxxxxxx 110111xx xxxxxxxx |
11110zzz 10zzxxxx 10xxxxxx 10xxxxxx |
UTF-16 "surrogate pair" 영역(yyyy=zzzzz -1) |
|
utf-8 인코딩 예제
'위'(한글) --> U+C7404(유니코드)
U+0800부터 U+FFFF 사이의 영역에 있으므로, 표에 따라 1110xxxx 10xxxxxx 10xxxxxx 형식으로 인코딩
C704(16진수) --> 1100-0111-0000-0100(2진수) --> 11101100 10011100 10000100(utf-8 인코딩)
결과적으로 이 문자는 3바이트로 인코딩 되면 16진수로 표기하면 EC 9C 84가 된다.
(첫 128 문자는 1바이트로 표시되고, 그 다음 1920 문자 (판독 기호가 붙은 라틴 문자, 그리스 문자, 키릴 문자, 콥트 문자, 아르메니아 문자,
히브리 문자, 아랍 문자)는 2바이트로 표시되며, 나머지 문자들 중 BMP 안에 들어 있는 것은 3바이트, 아닌 것은 4바이트로 표시된다.) |
관련자료
utf-8 체크
-
- 입력된 문자열이 utf-8로 인코딩 되었는지 하기 위해서는 utf-8 의 인코딩 패턴을 조사한다. 즉 바이트 배열의 패턴이 utf-8 인코딩 패턴(1110xxxx 10xxxxxx 10xxxxxx)과 맞는지를 비교한다.
- 000080 ~0007FF 범위의 110xxxxx 10xxxxxx 패턴 일 경우엔 중복되는 코드값으로 인해 utf-8 인코딩인지 아닌지 체크하는것은 사실상 불가능하다.
|
110xxxxx 10xxxxxx 패턴의 utf-8 판독
110xxxxx 10xxxxxx 패턴의 코드일 경우 utf-8로 인코딩 되었는지 아닌지를 확인하는것은 사실상 불가능하다.
하지만 utf-8로 인코딩 되면 아스키 영역은 1byte 한글은 3byte 내지는 4byte를 사용하므로 단순하게 생각하여 2 바이트 110xxxxx 10xxxxxx 패턴의 경우는 utf-8 인코딩이 아님을 유추할수 있다.
판독 기호가 붙은 라틴 문자, 그리스 문자, 키릴 문자, 콥트 문자, 아르메니아 문자, 히브리 문자, 아랍 문자 등이 입력되면 판독 오류가 발생하겠지만 이는 무시해도 좋을듯 하다. |
- Null 값의 경우 자바에서는 변형된 utf-8 인코딩 방식에 따라 1byte가 아닌 2byte(11000000 10000000)로 표기하므로 110xxxxx 10xxxxxx 의 경우 null 인지 비교해야 정확한 판독이 가능하지만 입력된 값의 인코딩여부를 판독하므로 상황에 따라서는 무시해도 괜찮을듯 하다.
- CJK 2바이트+한글 2바이트 가 '1110xxxx 10xxxxxx 10xxxxxx 0xxxxxxx' 와 같은 패턴으로 utf-8 3바이트 와 아스키1바이트 조합과 유사할 경우 앞자리 3바이트를 유니코드 형태로 역치환 해서 000800 ~00FFFF 범위가 맞는지 체크한다.
utf-8 인코딩 체크 예제
|
utf-8 인코딩 판독 예제
1. '위'.getBytes("ISO-8859-1") : -20 , -100, -124
2. unsigned byte = 236, 156, 132
3. 16진수 : EC , 9C, 84
4. 2진수 : 11101100 10011100 10000100
5. urf-8 패턴제거: 00001100 00011100 00000100
6. 유니코드 타입으로 치환 : 00001100<<12 + 0011100<<6 + 00000100 = 11000000 00000100
7. 16진수 : 000800 <= 00E704 =< 00FFFF |
package util;
import java.io.UnsupportedEncodingException;
/**
* <pre>
* 작성자 : 이종희 (qola@naver.com)
* JAlbum.com (http: * <b>특정 문자열이 utf-8 인코딩인지 아닌지 체크한다.</b>
*
* utf-8 인코딩 패턴인 110xxxxx 10xxxxxx , 1110xxxx 10xxxxxx 10xxxxxx 인지 비교한다.
* 2바이트 (110xxxxx 10xxxxxx) 패턴의 유니 코드 중복으로 인해 100% 검증할수 없지만
* 한글의 경우 3byte 로 표기 되므로 2바이트 패턴일 경우 utf-8 인코딩이 아닌것으로 간주한다.
*
* 따라서 000080 ~0007FF 영역의 라틴 문자, 그리스 문자, 키릴 문자, 콥트 문자,
* 아르메니아 문자, 히브리 문자, 아랍 문자 등은 utf-8 인코딩을 비교할수 없다.
*
* 수정된 utf-8의 의한 null값 \u0000 은 11000000 10000000 로 표기되지만 무시하기로 한다.
*
* </pre>
*/
public class UTFUtil {
public static boolean isUTF8(String str) throws Exception{
byte[] bytes=str.getBytes("ISO-8859-1");
return isUTF8(bytes,0,bytes.length);
}
public static boolean isUTF8(byte[] buf, int offset, int length) {
boolean yesItIs = false;
for (int i=offset; i<offset+length; i++) {
if ((buf[i] & 0xC0) == 0xC0) { int nBytes;
for (nBytes=2; nBytes<8; nBytes++) {
int mask = 1 << (7-nBytes);
if ((buf[i] & mask) == 0) break;
}
if(nBytes==2) return false;
for (int j=1; j<nBytes; j++) {
if (i+j >= length || (buf[i+j] & 0xC0) != 0x80) return false;
}
if(nBytes==3){
char c = (char) (((buf[i] & 0x0f) << 12) + ((buf[i+1] & 0x3F) << 6) + (buf[i+2] & 0x3F));
if(!(c >= 0x0800 && c <= 0xFFFF)){
return false;
}
}
yesItIs = true;
}
}
return yesItIs;
}
}
한글 URL 사용시 문제 발생 케이스
- IE에는 유니코드 지원을 위해 기본적으로 URL을 UTF-8 로 전송해 주지만 옵션 사항이므로 사용자 마다 환경이 다를수 있다.
- utf-8로 다시 보내는 기능을 제공하지 않는 브라우저도 존재한다.
- RSS 리더와 같은 별도의 클라이언트를 사용할경우 서버인코딩, 클라이언트, 브라우저 인코딩 이 모두 일치하여야만 한다.
해결책
- REQUEST 요청시 URL이 UTF-8 인코딩 방식인지 아닌지를 체크하여 UTF-8 방식이면 "ISO-8859-1 --> UTF-8" 형태로 인코딩 변환을 해주고 그렇지 않으면 "ISO-8859-1 --> MS949" 로 인코딩 변환을 시켜준다.
구현 예제
- APACHE MOD_REWRITE
- 아래 예와 같이 MOD_REWRITE를 이용해 특정 패턴의 URL을 REWRITE 시킨다.
RewriteEngine on
# RewriteRule ^/tag2/(.+)$ /tag/index.jsp?tag=$1 [PT]
- JSP 샘플
- isUTF8() 체크 메소드를 이용해 파라메터의 인코딩을 각각 처리해준다.
<%@page language="java" contentType="text/html;charset=utf-8"%>
<%@page import="java.net.URLEncoder, util.*" %>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
</head>
<body>
index.jsp... <br>
<%
String uri=request.getRequestURL().toString();
String tag=request.getParameter("tag");
byte[] tagBytes=tag.getBytes("ISO-8859-1");
out.println("byte[]===");
for(int i=0;i<tagBytes.length;i++){
out.println(tagBytes[i]+",");
}
out.println("<br>");
boolean isUTF8=UTFUtil.isUTF8(tag);
if(isUTF8) {
out.println(util.Enco.toUtf(tag));
out.println("<br>");
out.println(isUTF8);
}else{
out.println(new String(tag.getBytes("ISO-8859-1"),"ms949"));
out.println("<br>");
out.println(isUTF8);
}
%>
<a href="http:>http://tag_test.com/tag2/한글</a><br>
<a href="http:>http://tag_test.com/tag2/코리아</a><br>
<a href="http:>http://tag_test.com/tag2/위</a>
</body>
</html> |