Ajax 기본 예제와 JSP 엔진에서 한글 인코딩 충돌 문제 처리
Ajax가 필요한 일이 발생해서, 급조해서 Ajax를 공부했다.
공부한 책은 Ajax 입문이며, 처음 약 5~60 페이지만 읽었다. 급조한 내용이니 너무 신뢰하지 말 것. 아.. 그리고 이 책, 중대한 오탈자가 은근히 있다. 혹시 이 책으로 공부하고자 한다면 오탈자를 확인한 뒤에 공부해서 불필요한 시간 낭비를 줄이는 것이 좋겠다.
아무튼, 책의 내용중 Ajax의 기본적인 사용에 관한 문제를 정리하고, 또 JSP/Servlet 엔진에서 Ajax사용시에 발생하는 한글 인코딩(encoding)문제의 처리방법도 정리해 둔다.
Ajax의 개념에 관한 설명은 인터넷 상에 차고 넘치므로 생략.
* A Simpler Ajax Path가 Ajax 입문에 좋은 글.
Ajax의 개발 순서
1. XMLHttpRequest 객체 생성
2. HTTP 요청을 발생시킴(open(), send())
3. 서버측에서 XMLHttpRequest를 통해 보낸 요청을 받아서 파라미터를 분석하고, 작업을 한 뒤에 결과를 XML이나 문자열로 리턴한다.
4. XMLHttpRequest로 서버가 리턴한 데이터를 받아서 처리(onreadystatechange, responseText, responseXML)
XMLHttpRequest에 의한 송수신 상세 예
JavaScript에서 XMLHttpRequest Object 생성하기
// XMLHttpRequest 오브젝트 생성 함수
// @sample oj = createHttpRequest();
// @return XMLHttpRequest 오브젝트
function createHttpRequest()
{
if (window.ActiveXObject) {
try {
// IE6
return new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
// IE4, IE5
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
return null;
}
}
} else if (window.XMLHttpRequest) {
// Mozilla, FireFox, Opera, Safari, Konqueror3
return new XMLHttpRequest();
} else {
return null;
}
}
HTTP 요청 발생
1. open() 메소드 실행 (POST/GET, 요청URL, 동기/비동기지정)
var request = createHttpRequest();
request.open("GET", "/test.xml");
// param 1 : GET/POST
// param 2 : URL
// param 3 : 생략가능. 동기/비동기 여부. 기본 비동기.
2. send() 메소드(데이터 송신)
request.send(""); // 데이터 없이 전송할때 혹은
request.send(null); // Konqueror에서는 오류 발생함. Konqueror를 제외하고 데이터 없이 전송할 때 사용가능
위 a,b가 기본적인 모양새이지만, 실제로 GET과 POST 방식에 따라 차이가 많이난다.
* GET 방식 : GET 방식은 URL에 파라미터를 직접 붙여 보내지만, 한글 등의 문제로 인코딩이 필요하고, RequestHeader 설정도 필요하다. 일반적으로 다음과 같은 모양이 된다.
var paramName = encodeURIComponent("파라미터명"); // 파라미터이름을 UTF-8로 인코딩
var paramValue = encodedURIComponent("파라미터값"); // 파라미터 값을 UTF-8로 인코딩
// 파라미터 구분에 사용되는 ?와 &는 인코딩 되면 안된다. 그래서 따로 붙인다.
var fullParameter = '?' + paramName + '=' + paramValue; // URL에 사용할 파라미터 구성
request.open("GET", "/test.cgi" + data);
// setRequestHeader()는 open()보다 이후에 나와야만 한다.
// 아래는 파라미터 값을 UTF-8로 인코딩해서 보내겠다는 의미.
// GET방식에서는 필요 없고, POST방식에서는 필수이다.
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.send(null);
* POST 방식 : send() 메소드에 인수를 데이터로 넘긴다.
request.open("POST", "/test.cgi");
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
// POST 방식에서도 파라미터를 인코딩하여 send()메소드의 인자로 넘겨주면 된다.
request.send("name=test&data=123");
A Simpler Ajax Path에 보면 HTML폼에 입력된 값을 자동으로 파라미터 문자열로 변경해주는 메소드 예가 있다.
착신과 데이터 처리
* onreadystatechange 이벤트(송수신 상태 변할때 기동) - IE 이외 부라우저에서는 콜백 스타일의 onload 이벤트 사용가능
* readyState 프라퍼티 (송수신 상태를 나타내는 값. "4"가 송신 완료) - onload의 경우에는 불필요
onreadystatechange는 요청 처리 상태를 나타내는 readyState 프라퍼티의 값이 바뀔 때 발생한다.
* 착신을 처리할 함수 지정은 request.open() 함수를 호출하기 전에 선언해야 정상 작동했다. 항상 요청을 보내기 전에 요청을 처리할 함수 지정을 해둔다.
request.onreadystatechange = callbackFunction; // callbackFunction() 함수가 호출된다.
function callbackFunction() {
// readyState == 4는 착신 완료를 의미한다.
if (request.readyState == 4) {
// 착신시의 처리
}
}
다른 방법으로, callback 함수를 인라인으로 정의하고, HTTP 상태 코드가 200일때만 작업하도록 할 수도 있다. 두가지를 한꺼번에 보면,
request.onreadystatechange = funcation() {
if (request.readyState == 4 &&
request.status == 200) {
// 착신시의 처리
}
}
onreadystatechange 대신 onload를 사용할 수 있다. Opera 8은 버그때문에 onload만 사용한다. (IE를 제외한 다른 브라우저에서 다 된다)
request.onload = function() {
// 착신시의 처리
}
onload와 onreadystatechange를 동시에 이용하기 위해서 다음과 같이한다.
if (window.opera) {
request.onload = function() { callback(request); }
} else {
request.onreadystatechange = function() {
if (request.readyState == 4) {
callback(request);
}
}
}
function callback(request) {
// 실제 착신시의 처리를 구현하는 부분
}
* responseText 또는 responseXML (데이터를 텍스트 혹은 DOMDocument로 수신)
* responseText : 텍스트로 받기
* responseXml : XML로 받기
* 여러줄의 CSV 텍스트를 받았을 때의 일반적 처리
var res = request.responseText;
var rows = res.split(" "); // 여러 줄을 한 줄씩 배열로 만든다.
var cols = rows[0].split(","); // 첫번째 줄을 쉼표 단위로 분리하여 배열로 만든다.
* JSON 처리
eval("res = " + request.responseText)
//... 기타 처리
* XML 처리
Toshiro Takahashi
hello
var res = request.responseXML;
var msgs = res.getElementsByTagName("msg"); // DOM 객체 사용
alert(msg[0].firstChild.nodeValue);
서버측 스크립트
XMLHttpRequest.send() 에 의해 요청을 받아 처리하게 되는 서버측 스크립트(JSP, Servlet, ASP, PHP 등)은 요청 파라미터를 분석하여 작업을 처리한 뒤에 결과를 Text나 XML로 리턴하면 된다.
* 리턴시 문자 인코딩은 기본적으로 UTF-8로 한다.
* 텍스트로 리턴할 경우, Opera 8, Konqueror 3, Safari 등은 UTF-8을 인식하지 못한다. 서버는 응답 문자열을 UTF-8기준으로 URI 인코딩을 해서(Java의 경우 java.net.URLEncoder.encode() 메소드 사용) 리턴하고, 받는 측(웹 브라우져)는 다음과 같이 해석하면 정상적인 문자열을 받게 된다.(실제로는 작동하지 않으므로 URLEncoder를 사용하지말고 받는 자바 스크립트 측에서도 아래와 같이 받지 말고 그냥 request.responseText를 받을 것)
// Mozilla FireFox와 IE에서는 Encode/Decode할 경우
//공백이 +로 바뀌는 현상이 발생했다. 그래서 안쓴다.
// JavaScript측에서 decodeURIComponent를 안하면 서버측에서도 URLEncoding을 하면 안된다.
var res = decodeURIComponent(request.responseText);
JSP/Servlet 에서 Ajax와 한글 인코딩 문제
Ajax는 기본적으로 UTF-8 만으로 통신을 한다고 보면된다. 그 이외의 Encoding을 지원하는 인코딩 함수가 없기 때문에 EUC-KR로 데이터를 전송하려는 꿈은 접어야만 한다.
헌데, 아직 우리나라는 EUC-KR 인코딩으로 된 웹 어플리케이션이 압도적으로 많은 상황이다(어서 빨리 UTF-8로 옮겨가길 바라마지 않는다).
거기다가 보통 JSP/Servlet 웹 어플리케이션은 Servlet 스펙 2.3이후부터 문자 인코딩 서블릿 필터를 사용해 모든 요청에 대해 일관된 문자 인코딩을 사용하는 것이 보편적인 방법으로 자리잡았다.
서블릿 필터는 일관성있게 모든 요청을 EUC-KR로 받아들이게 했는데, 몇몇 Ajax관련 요청만 UTF-8로 받아들여야만 하는 것이다.
필터를 적용할 URL-Pattern을 따로 줘보려 했으나, 너무 복잡해졌다.
그래서 HTTP 요청의 헤더를 이용해서 해결 했다.
아.. 한가지 더. 현재 한글 문제는 "XMLHttpRequest 요청 -> JSP/Servlet" 이 상황에서만 발생하는 것이다.
"JSP/Servlet -> XMLHttpRequest"의 상황(서버에서 클라이언트로 값을 리턴)에서는 이 문제가 발생하지 않는다.
서버가 리턴하는 문자열은 간단하게 다음처럼 하면 WAS가 자동으로 UTF-8로 값을 변경해서 전달하기 때문이다.
contentType에서 text/plain은 텍스트나 JSON으로 값을 리턴할 때이다. XML로 리턴할 때는 text/xml.
아래는 Ajax 요청을 처리하기 위해서 만들어본 간단한 Encoding Filter 이다.
package ajax.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 어플리케이션 전체에 적용되는 필터이다.
*
*
*
encoding 파라미터 : encoding 파라미터를 설정하면 request 객체에
* setCharacterEncoding(encoding)을 실행한다.
*
ajaxFlag 파라미터 : Ajax요청임을 나타내는 HTTP 파라미터 이름이다. ajaxFilter로 지정한 HTTP 파라미터의
* 값이 true 로 설정되면 인코딩을 무조건 UTF-8로 설정한다.
*
*
* @author 손권남(kwon37xi@yahoo.co.kr)
*
*/
public class EncodingFilter implements Filter {
private Log log = LogFactory.getLog(this.getClass());
/** HTTP 요청 문자 인코딩 */
private String encoding = null;
/** Ajax 요청임을 나타내는 플래그 파라미터 이름 */
private String ajaxFlag = null;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (ajaxFlag != null
&& "true".equals(((HttpServletRequest) request)
.getHeader(ajaxFlag))) {
// Ajax 처리 요청일 경우 무조건 UTF-8 지정.
request.setCharacterEncoding("UTF-8");
if (log.isDebugEnabled()) {
log.debug("요청 헤더에 " + ajaxFlag + "가 "
+ ((HttpServletRequest) request).getHeader(ajaxFlag)
+ "로 설정되어 있어 문자 인코딩에 UTF-8을 사용합니다.");
}
} else if (encoding != null) {
// Ajax 플래그가 true가 아니면, 기본적인 인코딩을 적용한다.
request.setCharacterEncoding(encoding);
if (log.isDebugEnabled()) {
log.debug("문자 인코딩에 " + encoding + "을 사용합니다.");
}
}
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
encoding = config.getInitParameter("encoding");
ajaxFlag = config.getInitParameter("ajaxFlag");
if (log.isDebugEnabled()) {
log.info("encoding : " + encoding + ", ajaxFlag : " + ajaxFlag);
}
}
public void destroy() {
}
}
이 필터를 적용하고서, web.xml에 다음 내용을 추가하면 필터가 작동한다.
이중 인코딩 필터
EncodingFilter
ajax.filter.EncodingFilter
encoding
euc-kr
ajaxFlag
Ajax
EncodingFilter
/*
여기 내용을 보면, 기본적인 인코딩은 EUC-KR이고, 요청 헤더에 "Ajax" 헤더의 값이 "true"일 경우에는 강제로 UTF-8을 지정하라고 한 것이다. "ajaxFlag"의 값을 변경하면 헤더의 키을 "Ajax"가 아닌 다른 값으로도 지정할 수 있다. 하지만 아무튼 해당 헤더의 값을 "true"로 지정하면 Ajax로 인식하게 되는 것이다.
이를 위해서는 XMLHttpRequest에도 한가지 처리를 더 보태야 한다.
request.open("GET", "AjaxProcessor.jsp" + fullParameter);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('Ajax', 'true');
당연히 헤더에 값 Ajax요청임을 명시하는 값을 설정하는 것이다.
그리고 다음 문제가 또 있는데, Tomcat 버전별로 문자 인코딩 설정이 달라질 수 있다는 것이다.
위 내용은 Tomcat 4.x에서는 정상 작동하지만, Tomcat 5.x 에서는 제대로 작동하지 않는다.
Tomcat 5.x에서는 server.xml 에 GET 방식의 요청에 대한 인코딩을 지정하기 때문이다.
여기서 URIEncoding="euc-kr" 을 사용하지 않고, useBodyEncodingForURI="true"을 사용하면 Tomcat 4.x 처럼 request.setCharacterEncoding()의 값을 따라가게 할 수 있다.
Tomcat과 한글에 대해서는 Tomcat/JSP와 한글문서를 참조한다.
또하나 Ajax임을 나타내는 플래그를 HTTP 요청 헤더에 설정하도록 했는데, 그러지 않고 요청 파라미터(request.getParameter()로 값을 얻어올 수 있는 것)으로 설정하면 안된다.
request.getParameter()가 실행되어 Ajax 플래그의 값을 감지하는 그 순간, 그 이후 호출되는 request.setCharacterEncoding()는 완전히 무시되어 버리기 때문이다.
예제
급조한 예제이다.
* AjaxCaller.jsp - Ajax 호출부(클라이언트) : 수식을 만들어서 서버측에 계산을 요청한다.
이름 :
+
-
*
/
* AjaxProcessor.jsp - Ajax 처리부(서버) : 수식을 계산한 결과를 JSON 형태로 리턴한다
이름 입력 부분에 한글을 입력하여 문제없이 처리되는지 확인해보기 바란다.
JSON
Ajax(JavaScript)는 데이터를 리턴 받는 방법으로 XML/Text/JSON을 지원한다. JSON은 텍스트 형태로 객체를 정의하는 방식이다. 이것은 XML과 1:1 매칭을 할 수도 있다. XML보다 훨씬 만들기 쉽고 사용법도 자바 객체를 사용하는 것과 유사하다. 그래서 이걸 사용해서 프로그램을 작성했다.
{
"test1": "hello",
"test2": "hello2"
}
위와 같은 메시지를 서버에서 응답으로 내보냈다고 할 때
// JSON 형태의 텍스트를 JavaScript 객체화 한다.
eval("res = " + request.responseText);
// 객체를 사용한다.
alert(res.test2); // 이 명령은 "hello2"를 출력한다.
배열은 다음과 같이 생성한다.
[
["test1", "test2"],
["test3", "test4"]
]
아래와 같이 사용한다.
eval("res = " + request.responseText);
// test4를 출력한다.
alert(res[1][1])
* JSON 홈페이지 : http://www.json.org/
* Java 객체를 이용해서 JSON 객체를 위한 텍스트 생성하기 : http://www.json.org/java/simple.txt 매우 단순한 방법으로 핵심 기능만 가지고 있다. 이것을 사용하길 권장한다. 라이브러리 다운로드는 http://www.JSON.org/java/json_simple.zip 에서 한다.
* XML과 JSON간의 변환
(출처 : http://ingenuity.egloos.com)