본문 바로가기
IT 공부/자바와 웹 애플리케이션

[서블릿] - ServletRequest의 Character Encoding을 utf-8로 설정해야 하는 이유

by exdus3156 2024. 1. 5.

1. 서블릿 Request 인코딩 문제

@WebFilter(urlPatterns = { "/*" })
public class UTF8Filter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        req.setCharacterEncoding("UTF-8");
        chain.doFilter(req, res);
    }
}

POST 메소드로 들어오는 서블릿 리퀘스트에서 데이터를 뽑아내면 한글의 경우 깨지는 문제가 발생한다.

그래서 위와 같이 필터(Fitler)를 사용해 POST request의 body 메시지를 UTF-8로 해석하라고 request 객체의 인코딩을 설정해야만 한글이 깨지지 않고 정상적으로 동작한다.

그런데 왜 한글이 깨지는 것일까? 웹 브라우저가 HTTP 요청 메시지를 보낼 때 문자열을 UTF-8로 전달하고, 톰캣 9버전도 기본적으로는 UTF-8로 동작한다고 하는데도 말이다.

일단 문자열 깨짐의 문제의 원인은 언제나 인코딩 매칭 실패에서 오는 법이다. (링크

미리 정답부터 말하자면, HTTP Request 메시지의 헤더에 특별히 인코딩 타입을 명시하지 않으면,  HttpServletRequest는 body에 담긴 메시지를 ISO-8859-1 인코딩이라고 받아들이는 것이 문제의 원리다.

이것은 톰캣 9의 공식 홈페이지의 아래 설명에도 나와 있다. 리퀘스트 메시지에 인코딩 타입을 명시하지 않거나, 따로 개발자가 setCharacterEncoding을 하지 않으면, 메시지를 ISO-8859-1로 디코딩한다고 말한다.

If request character encoding is not known (is not provided by a browser and is not set by 
SetCharacterEncodingFilter or a similar filter using Request.setCharacterEncoding method), the default encoding is always "ISO-8859-1". The URIEncoding setting has no effect on this default.

(※ 참고로 이 문제를 구글링해보면 톰캣의 설정 파일을 수정해서 인코딩을 UTF-8로 맞추라고 하는데, 이 문제는 그것과는 아무런 관련이 없다. 설정 파일에서 건드리는 것은 URI에 대한 이야기다.)

 

 


2. 그렇다면 왜 ISO로 해석하는가?

 

Character Encoding - Apache Tomcat - Apache Software Foundation

Character Encoding Issues Permalink to this page: https://cwiki.apache.org/confluence/x/liklBg Questions WhyWhat is the default character encoding of the request or response body?Why does everything have to be this way?HowHow do I change how GET parameters

cwiki.apache.org

구글링 끝에 위와 같은 웹 사이트에서 문제를 속 시원하게 해결했다. 필수적인 부분만 따와 해석해보았다.

 

1) 디폴트 인코딩은 "ISO-8859-1" 이다.

If a character encoding is not specified, the Servlet specification requires that an encoding of ISO-8859-1 is used.

HTTP 메시지의 헤더에는 ContentType을 적을 수 있는데, 예를 들어 Content-Type: charset=utf-8 이런 식이다. 만약 이 헤더가 없다면 서블릿은 UTF-8이 아니라 ISO-8859-1로 인코딩/디코딩한다.

 

2) 서블릿 스펙이 HTTP의 과거 버전에 기반하고 있다.

Older versions of the HTTP/1.1 specification (e.g. RFC 2616) indicated that ISO-8859-1 is the default charset for text-based HTTP request and response bodies if no charset is indicated. Although RFC 7231 removed this default, the servlet specification continues to follow suit. Thus the servlet specification indicates that if a POST request does not indicate an encoding, it must be processed as ISO-8859-1

HTTP/1.1 스펙은 HTTP body에 포함되는 메시지의 인코딩이 특별히 타입을 헤더에 명시하지 않으면 디폴트로 ISO-8859-1로 한다고 명시했다. 이는 초기 웹 기술이 알파벳을 사용하는 한정된 영역에 국한된 기술이었기 때문이었다. 국제화와 다국어 지원에 대한 고민이 없었고, 당시 널리 사용되던 ISO 인코딩을 사용했던 것이다.

그러나 나중에 웹이 전세계적으로 발전하고 이에 따라 HTTP/1.1 스펙 또한 업데이트 되면서, 디폴트 인코딩을 ISO-8859-1로 설정한다는 부분을 삭제했다. 오늘날에는 유니코드 기반의 UTF-8이 기본이다. 그럼에도 불구하고! 서블릿 스펙은 여전히 예전의 HTTP 스펙에 기반하고 있다.

When working with Java servlets, the Java Servlet Specification is the primary reference, but the servlet spec itself relies on older specifications such as HTTP for its foundation.

 

3) 웹 브라우저들이 요청 메시지의 body 인코딩을 헤더에 명시하지 않고 있다.

Most web browsers today do not specify the character set of a request, even when it is something other than ISO-8859-1. This seems to be in violation of the HTTP specification. Most web browsers appear to send a request body using the encoding of the page used to generate the POST (for instance, the <form> element came from a page with a specific encoding... it is that encoding which is used to submit the POST data for that form).

오늘날 웹 브라우저들은 요청 메시지의 바디를 UTF-8로 인코딩하지만 막상 개발자 도구를 통해 요청 메시지의 헤더를 보면 charset이 명시되어 있지 않는 경우가 대부분이다. 서블릿 컨테이너는 디폴트 인코딩이 ISO-8859-1 이지만, HTTP 요청 메시지 헤더에 charset이 명시되면 그것을 따라준다. 그런데 정작 현대 웹 브라우저들이 이 charset을 UTF-8이라고 붙여주질 않는다. 따라서 서블릿 컨테이너는 서블릿 스펙 상, 인코딩 타입이 명시되지 않았으므로 인코딩을 UTF-8이 아니라 ISO-8859-1이라고 가정하는 것이다.