-
필터를 이용한 웹 프로그래밍 Part1, 필터란 무엇인가!
서블릿 2.3에 새롭게 추가된 필터가 무엇이며, 어떻게 구현하는지에 대해서 살펴본다.
요약
서블릿 2.3 규약은 2.2 규약에서 지적되었던 단점을 보완하기 위해서 노력했으며 그 노력 중의 하나가 바로 필터(Filter)이다. 이 글에서는 필터가 무엇이며 필터를 어떻게 구현하는 지에 대해서 살펴본다.
프로바이더: 최범균
--------------------------------------------------------------------------------
필터!!
현 재 서블릿 2.3 규약은 Proposed Final Draft 2 상태에 있다. 조만간 서블릿 2.3과 JSP 1.2 최종 규약 이 발표될 것으로 예상되며 우리는 당연히 새롭게 추가된 것들이 무엇인지에 관심이 쏠리게 된다. 서블릿 2.3 규약에 새롭게 추가 된 것 중에 필자가 가장 눈여겨 본 것은 바로 필터(Filter) 기능의 추가이다.
그 동안 필자는 서블릿 2.2 와 JSP 1.1에 기반하여 웹 어플리케이션을 구현하는 동안 몇몇 부분에서 서블릿 2.2 규약의 부족한 면을 느낄 수 있었으 며, 특히 사용자 인증 처리, 요청 URL에 따른 처리, XSL/T를 이용한 XML 변환(Transformation) 등 개발자들 이 직접 설계해야 하는 부분이 많았었다. 하지만, 이제 서블릿 2.3 규약에 새롭게 추가된 필터(Filter)를 사용함으로써 개발 자들이 고민해야 했던 많은 부분을 덜어낼 수 있게 되었다. 이 글에서는 필터가 무엇이며 어떻게 필터를 구현하는지에 대해 살펴볼 것 이다.
간단하게 말해서, 필터는 'HTTP 요청과 응답을 변경할 수 있는 재사용가능한 코드'이다. 필터는 객체의 형 태로 존재하며 클라이언트로부터 오는 요청(request)과 최종 자원(서블릿/JSP/기타 문서) 사이에 위치하여 클라이언트의 요 청 정보를 알맞게 변경할 수 있으며, 또한 필터는 최종 자원과 클라이언트로 가는 응답(response) 사이에 위치하여 최종 자원 의 요청 결과를 알맞게 변경할 수 있다. 이를 그림으로 표현하면 다음과 같다.
그림1 - 필터의 기본 구조
그 림1에서 자원이 받게 되는 요청 정보는 클라이언트와 자원 사이에 존재하는 필터에 의해 변경된 요청 정보가 되며, 또한 클라이언트 가 보게 되는 응답 정보는 클라이언트와 자원 사이에 존재하는 필터에 의해 변경된 응답 정보가 된다. 위 그림에서는 요청 정보를 변 경하는 필터와 응답 정보를 변경하는 필터를 구분해서 표시했는데 실제로 이 둘은 같은 필터이다. 단지 개념적인 설명을 위해 그림1 과 같이 분리해 놓은 것 뿐이다.
필터는 그림1에서처럼 클라이언트와 자원 사이에 1개가 존재하는 경우가 보통이지만, 여러 개의 필터가 모여 하나의 체인(chain; 또는 사슬)을 형성할 수도 있다. 그림2는 필터 체인의 구조를 보여주고 있다.
그림2 - 필터 체인
그 림2와 같이 여러 개의 필터가 모여서 하나의 체인을 형성할 때 첫번째 필터가 변경하는 요청 정보는 클라이언트의 요청 정보가 되지 만, 체인의 두번째 필터가 변경하는 요청 정보는 첫번째 필터를 통해서 변경된 요청 정보가 된다. 즉, 요청 정보는 변경에 변경 에 변경을 거듭하게 되는 것이다. 응답 정보의 경우도 요청 정보와 비슷한 과정을 거치며 차이점이 있다면 필터의 적용 순서가 요 청 때와는 반대라는 것이다. (그림2를 보면 이를 알 수 있다.)
필터는 변경된 정보를 변경하는 역할 뿐만 아니 라 흐름을 변경하는 역할도 할 수 있다. 즉, 필터는 클라이언트의 요청을 필터 체인의 다음 단계(결과적으로는 클라이언트가 요청 한 자원)에 보내는 것이 아니라 다른 자원의 결과를 클라이언트에 전송할 수 있다. 필터의 이러한 기능은 사용자 인증이나 권한 체크 와 같은 곳에서 사용할 수 있다.
필터 관련 인터페이스 및 클래스
필터를 구현 하는데 있어 핵심적인 역할을 인터페이스 및 클래스가 3개가 있는 데, 그것들은 바로 javax.servlet.Filter 인터페이 스, javax.servlet.ServletRequestWrapper 클래 스, javax.servlet.ServletResponseWrapper 클래스이다. 이 중 Filter 인터페이스는 클라이언트 와 최종 자원 사이에 위치하는 필터를 나타내는 객체가 구현해야 하는 인터페이스이다. 그리 고 ServletRequestWrapper 클래스와 SerlvetResponseWrapper 클래스는 필터가 요청을 변경한 결 과 또는 응답을 변경할 결과를 저장할 래퍼 클래스를 나타내며, 개발자는 이 두 클래스를 알맞게 상속하여 요청/응답 정보를 변경하 면 된다.
Filter 인터페이스
먼저, Filter 인터페이스부터 살펴보자. Filter 인터페이스에는 다음과 같은 메소드가 선언되어 있다.
public void init(FilterConfig filterConfig) throws ServletException
필터를 웹 콘테이너내에 생성한 후 초기화할 때 호출한다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
체인을 따라 다음에 존재하는 필터로 이동한다. 체인의 가장 마지막에는 클라이언트가 요청한 최종 자원이 위치한다.
public void destroy()
필터가 웹 콘테이너에서 삭제될 때 호출된다.
위 메 소드에서 필터의 역할을 하는 메소드가 바로 doFilter() 메소드이다. 서블릿 콘테이너는 사용자가 특정한 자원을 요청했 을 때 그 자원 사이에 필터가 존재할 경우 그 필터 객체의 doFilter() 메소드를 호출하며, 바로 이 시점부터 필터가 작용하 기 시작한다. 다음은 전형적인 필터의 구현 방법을 보여주고 있다.
public class FirstFilter implements javax.servlet.Filter {
public void init(FilterConfig filterConfig) throws ServletException {
// 필터 초기화 작업
}
public void doFilter(ServletRequest request,
ServletResponse response
FilterChain chain)
throws IOException, ServletException {
// 1. request 파리미터를 이용하여 요청의 필터 작업 수행
// 2. 체인의 다음 필터 처리
chain.doFilter(request, response);
// 3. response를 이용하여 응답의 필터링 작업 수행
}
public void destroy() {
// 주로 필터가 사용한 자원을 반납
}
}
위 코 드에서 Filter 인터페이스의 doFilter() 메소드는 javax.servlet.Servlet 인터페이스 의 service() 메소드와 비슷한 구조를 갖는다. 즉 만약 클라이언트의 자원 요청이 필터를 거치는 경우, 클라이언트의 요청 이 있을 때 마다 doFilter() 메소드가 호출되며, doFilter() 메소드는 서블릿과 마찬가지로 각각의 요청에 대해서 알 맞은 작업을 처리하게 되는 것이다.
위 코드를 보면 doFilter() 메소드는 세번째 파라미터 로 FilterChain 객체를 전달받는 것을 알 수 있다. 이는 클라이언트가 요청한 자원에 이르기까지 클라이언트의 요청이 거쳐가 게 되는 필터 체인을 나타낸다. FilterChain을 사용함으로써 필터는 체인에 있는 다음 필터에 변경한 요청과 응답을 건내 줄 수 있게 된다.
위 코드를 보면서 우리가 또 하나 알아야 하는 것은 요청을 필터링한 필터 객체가 또 다시 응답 을 필터링한다는 점이다. 위 코드의 doFilter() 메소드를 보면 1, 2, 3 이라는 숫자를 사용하여 doFilter() 메 소드 내에서 이루어지는 작업의 순서를 표시하였는데, 그 순서를 다시 정리해보면 다음과 같다.
request 파리미터를 이용하여 클라이언트의 요청 필터링
1 단계에서는 RequestWrapper 클래스를 사용하여 클라이언트의 요청을 변경한다.
chain.doFilter() 메소드 호출
2 단계에서는 요청의 필터링 결과를 다음 필터에 전달한다.
response 파리미터를 사용하여 클라이트로 가는 응답 필터링
3 단계에서는 체인을 통해서 전달된 응답 데이터를 변경하여 그 결과를 클라이언트에 전송한다.
1단계와 3단계 사이에서 다음 필터로 이동하기 때문에 요청의 필터 순서와 응답의 필터 순서는 그림2에서 봤듯이 반대가 된다.
필터의 설정
필 터를 사용하기 위해서는 어떤 필터가 어떤 자원에 대해서 적용된다는 것을 서블릿/JSP 콘테이너에 알려주어야 한다. 서블릿 규약 은 웹 어플리케이션과 관련된 설정은 웹 어플리케이션 디렉토리의 /WEB-INF 디렉토리에 존재하는 web.xml 파일을 통해서 하 도록 하고 있으며, 필터 역시 web.xml 파일을 통해서 설정하도록 하고 있다.
web.xml 파일에서 필터를 설정하기 위해서는 다음과 같이 <filter> 태그와 <filter-mapping> 태그를 사용하면 된다.
<web-app>
<filter>
<filter-name>HighlightFilter</filter-name>
<filter-class>javacan.filter.HighlightFilter</filter-class>
<init-param>
<param-name>paramName</param-name>
<param-value>value</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HighlightFilter</filter-name>
<url-pattern>*.txt</url-pattern>
</filter-mapping>
</web-app>
여 기서 <filter> 태그는 웹 어플리케이션에서 사용될 필터를 지정하는 역할을 하며, <filter- mapping> 태그는 특정 자원에 대해 어떤 필터를 사용할지를 지정한다. 위 예제의 경우는 클라이언트가 txt 확장자를 갖 는 자원을 요청할 경우 HithlightFilter가 사용되도록 지정하고 있다.
<init- param> 태그는 필터가 초기화될 때, 즉 필터의 init() 메소드가 호출될 때 전달되는 파라미터 값이다. 이는 서블릿 의 초기화 파라미터와 비슷한 역할을 하며 주로 필터를 사용하기 전에 초기화해야 하는 객체나 자원을 할당할 때 필요한 정보를 제공하 기 위해 사용된다.
<url-pattern> 태그는 클라이언트가 요청한 특정 URI에 대해서 필터링을 할 때 사용된다. 서블릿 2.3 규약의 11장을 보면 다음과 같이 url-pattern의 적용 기준을 명시하고 있다.
'/'로 시작하고 '/*'로 끝나는 url-pattern은 경로 매핑을 위해서 사용된다.
'*.'로 시작하는 url-pattern은 확장자에 대한 매핑을 할 때 사용된다.
나머지 다른 문자열을 정확한 매핑을 위해서 사용된다.
에를 들어, 다음과 같이 <filter-mapping> 태그를 지정하였다고 해 보자.
<filter-mapping>
<filter-name>AuthCheckFilter</filter-name>
<url-pattern>/pds/*</url-pattern>
</filter-mapping>
이 경우 클라이언트가 /pds/a.zip 을 요청하든 /pds/b.zip 을 요청하는지에 상관없이 AuthCheckFilter가 필터로 사용될 것이다.
<url- pattern> 태그를 사용하지 않고 대신 <servlet-name> 태그를 사용함으로써 특정 서블릿에 대한 요청 에 대해서 필터를 적용할 수도 있다. 예를 들면 다음과 같이 이름이 FileDownload인 서블릿에 대해 서 AuthCheckFilter를 필터로 사용하도록 할 수 있다.
<filter-mapping>
<filter-name>AuthCheckFilter</filter-name>
<servlet-name>FileDownload</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>FileDownload</servlet-name>
...
</servlet>
래퍼 클래스
필 터가 필터로서의 제기능을 하기 위해서는 클라이언트의 요청을 변경하고, 또한 클라이언트로 가는 응답을 변경할 수 있어야 할 것이 다. 이러한 변경을 할 수 있도록 해 주는 것이 바로 ServletRequestWrapper 와 ServletResponseWrapper이다. 서블릿 요청/응답 래퍼 클래스를 이용함으로써 클라이언트의 요청 정보를 변경하 여 최종 자원인 서블릿/JSP/HTML/기타 자원에 전달할 수 있고, 또한 최종 자원으로부터의 응답 결과를 변경하여 새로운 응 답 정보를 클라이언트에 보낼 수 있게 된다.
서블릿 요청/응답 래퍼 클래스로서의 역할을 수행하기 위해서 는 javax.servlet 패키지에 정의되어 있는 ServletRequestWrapper 클래스 와 ServletResponseWrapper 클래스를 상속받으면 된다. 하지만, 대부분의 경우 HTTP 프로토콜에 대한 요청/응답 을 필터링 하기 때문에 이 두 클래스를 상속받아 알맞게 구현한 HttpServletRequestWrapper 클래스 와 HttpServletResponseWrapper 클래스를 상속받는 경우가 대부분일 것이다.
HttpServletRequestWrapper 클 래스와 HttpServletResponseWrapper 클래스는 모두 javax.servlet.http 패키지에 정의되어 있으 며, 이 두 클래스는 각각 HttpServletRequest 인터페이스와 HttpServletResponse 인터페이스에 정의되 어 있는 모든 메소드를 이미 구현해 놓고 있다. 필터를 통해서 변경하고 싶은 정보가 있을 경우 그 정보를 추출하는 메소드를 알맞 게 오버라이딩하여 필터의 doFilter() 메소드에 넘겨주기만 하면 된다. 예를 들어, 클라이언트가 전송한 "company" 파 리머터의 값을 무조건 "JavaCan.com"으로 변경하는 요청 래퍼 클래스는 다음과 같 이 HttpServletRequestWrapper 클래스를 상속받은 후에 getParameter() 메소드를 알맞게 구현하면 된 다.
package javacan.filter;
import javax.servlet.http.*;
public class ParameterWrapper extends HttpServletRequestWrapper {
public ParameterWrapper(HttpServletRequest wrapper) {
super(wrapper);
}
public String getParameter(String name) {
if ( name.equals("company") ) {
return "JavaCan.com";
} else {
return super.getParameter(name);
}
}
}
오 버라이딩한 getParameter() 메소드를 살펴보면 값을 구하고자 하는 파라미터의 이름이 "company"일 경 우 "JavaCan.com"을 리턴하고 그렇지 않을 경우에는 상위 클래스(즉, HttpServletRequestWrapper 클래 스)의 getParameter() 메소드를 호출하는 것을 알 수 있다.
이렇게 작성한 래퍼 클래스는 필터 체인을 통 해서 최종 자원까지 전달되어야 그 효과가 있을 것이다. 즉, 최종 자원인 서블릿/JSP에 서 request.getParameter("company")를 호출했을 때 ParameterWrapper 클래스 의 getParameter() 메소드가 사용되기 위해서는 ParameterWrapper 객체 가 HttpServletRequest 객체를 대체해야 하는데, 이는 Filter 인터페이의 doFilter() 내에 서 ParameterWrapper 객체를 생성한 후 파라미터로 전달받은 FilterChain의 doFilter() 메소드를 호출함 으로써 가능하다. 좀 복잡하게 느껴질지도 모르겠으나 이를 코드로 구현해보면 다음과 같이 간단한다.
package javacan.filter;
import javax.servlet.*;
import javax.servlet.http.*;
public class ParameterFilter implements Filter {
private FilterConfig filterConfig;
public ParameterFilter() {
}
public void init(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
public void destroy() {
filterConfig = null;
}
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws java.io.IOException, ServletException {
// 요청 래퍼 객체 생성
HttpServletRequestWrapper requestWrapper =
new ParameterWrapper((HttpServletRequest)request);
// 체인의 다음 필터에 요청 래퍼 객체 전달
chain.doFilter(requestWrapper, response);
}
}
응답 래퍼 클래스 역시 요청 래퍼 클래스와 비슷한 방법으로 구현된다.
앞 에서도 언급했듯이 요청 정보의 변경 및 응답 정보 변경의 출발점은 래퍼 클래스이다. XML+XSL/T 기법이나 사용자 인증과 같 은 것들을 최종 자원과 분리시켜 객체 지향적으로 구현하기 위해서 요청/응답 래퍼 클래스를 사용하는 것은 필수적이다. 2부에서 실 제 예를 통해서 어떻게 필터와 요청/응답 래퍼 클래스를 효과적으로 사용할 수 있는 지 살펴보게 될 것이다.
필터 체인의 순서
앞 에서 필터는 체인을 형성할 수 있다고 하였다. 체인을 형성한다는 것은 어떤 특정한 순서에 따라 필터가 적용된다는 것을 의미한 다. 예를 들면, 여러분은 '인증필터->파라미터 변환 필터->XSL/T 필터->자원->XSL/T 필터 ->파라미터 변환 필터->인증필터'와 같이 특정 순서대로 필터를 적용하길 원할 것이다. 서블릿2.3 규약은 다음과 같 은 규칙에 기반하여 필터 체인 내에서 필터의 적용 순서를 결정한다.
url-pattern 매칭은 web.xml 파일에 표기된 순서대로 필터 체인을 형성한다.
그런 후, servlet-name 매칭이 web.xml 파일에 표기된 순서대로 필터 체인을 형성한다.
결론
이 번 1 부에서는 서블릿 2.3 규약에 새롭게 추가된 필터가 무엇인지 그리고 필터를 어떻게 구현하며 또한 필터를 어떻게 서블릿이 나 JSP와 같은 자원에 적용할 수 있는지에 대해서 알아보았다. 아직 구체적으로 필터의 응용방법에 대해서 설명하지 않았기 때문 에 필터의 장점이 머리에 떠오르지 않을것이다. 다음 2 부에서는 구체적으로 필터의 예를 살펴봄으로써 필터의 활용함으로써 얻게 되 는 장점에 대해서 살펴보도록 하자.
필터를 이용한 웹 프로그래밍 Part2, 필터의 응용!
서블릿 2.3에 새롭게 추가된 필터를 이용한 사용자 인증, XSL/T 변환을 구현해본다.
요약
1부에 이어 2부에서는 사용자 인증 필터와 XSL/T 변환 필터를 구현해 봄으로써 필터를 어떻게 응용할 수 있는 지에 대해서 살펴보도록 하자.
프로바이더: 최범균
--------------------------------------------------------------------------------
필터의 응용
사 실 필터는 그 동안 많은 개발자들이 필요로 하는 기능이었으며, 다른 페이지로 이동하거나(forwarding) 다른 페이지를 포함하 는(include) 방법을 사용하여 서블릿 체인(또는 서블릿과 JSP의 체인) 형태로 필터링을 구현하는 경우가 많았다. 이러한 필 터링을 적용할 수 있는 분야에는 다음과 같은 것들이 있을 것이다.
데이터 변환(다운로드 파일의 압축 및 데이터 암호화 등)
XSL/T를 이용한 XML 문서 변경
사용자 인증
자원 접근에 대한 로깅
이 외 에도 많은 활용 방법들이 존재할 수 있겠지만 여기서 제시한 네 가지 정도가 가장 많이 사용되지 않을까 생각된다. 특히, 데이터 변 환이나 XSL/T는 필터를 적용하기에 가장 알맞은 구조를 갖고 있다. 데이터 변환의 경우 데이터 압축 필터와 암호화 필터를 체인으 로 만들어 암호화한 데이터를 다시 압축하는 등의 다양한 방식이 존재할 수 있다. 또한, XSL/T를 사용할 경우 최종 자원 은 XML 문서를 생성하고, 그 문서를 다양한 포맷으로 변환해주는 필터가 존재할 수 있다. 예를 들어, 서블릿/JSP는 사용자 가 요청한 정보를 XML로 출력하고 필터는 XSL/T를 사용하여 자원의 출력 결과를 다양한 기기(웹브라우저, 모바일 폰 등)에 맞 게 변형할 수도 있을 것이다.
이 글에서는 사용자 인증 필터, XSL/T 필터에 대해서 살펴볼 것이다. 이 두가 지 필터는 필터를 통한 흐름 제어 및 응답 데이터의 변경 방법 등을 보여주고 있기 때문에, 이 두 가지 형태의 필터가 어떤 식으 로 구현되는 지 이해한다면 여러분은 그 외의 모든 다른 종류의 필도 어렵지 않게 구현할 수 있게 될 것이다. 지금부터 차례대로 살 펴보도록 하자.
로그인 검사 필터
요즘 많은 웹 사이트들이 회원제로 운영되 고 있으며 로그인을 한 이후에 컨텐츠에 접근할 수 있도록 제한한 곳도 많다. 특히 컨텐츠의 유료화 추세에 발맞추어 이처럼 사용 자 인증이 필요한 사이트는 점차적으로 증가하고 있다. 심지어 무료 사이트 조차도 사용자가 인증을 거친 이후에 컨텐츠에 접근 할 수 있도록 하고 있다. 이처럼 사용자 인증이 웹 사이트의 필수 요소로 등장하면서 개발자들은 각각의 JSP/서블릿 등의 코드 에 사용자가 로그인을 했는지의 여부를 판단하기 위한 코드를 삽입하고 있다. 여기서 각각의 JSP/서블릿은 같은 코드 블럭을 갖 게 되며 이는 회원 인증이 변할 경우 모든 페이지를 변경해주어야 한다는 문제를 일으키게 된다.
이러한 문제는 로그인 을 검사하는 필터를 사용함으로써 말끔하게 해소할 수 있다. 1부에서도 살펴봤듯이 클라이언트의 요청은 서블릿/JSP에 전달되기 전 에 먼저 필터를 통과하게 된다. 따라서, 필터에서 조건에 따라 알맞게 흐름을 제어할 수 있다는 것을 의미한다. 먼저 소스 코드부 터 작성해보자. 어떤 형태로 구현했는지 집중적으로 관찰하기 바란다.
import javax.servlet.*;
import javax.servlet.http.*;
public class LoginCheckFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws java.io.IOException, ServletException {
if (LoginChecker.isLogin((HttpServletRequest)request)) {
// 로그인을 했다면 다음 필터를 실행한다.
chain.doFilter(request, response);
} else {
// 로그인을 하지 않았을 경우 로그인 페이지로 이동한다.
((HttpServletResponse)response).sendRedirect(LOGIN_URL);
}
}
public void destroy() {
}
private static String LOGIN_URL = "/login.jsp";
}
로 그인 여부를 검사하는 필터는 위와 같이 매우 간단하다. 여기서 LoginChecker.isLogin() 메소드는 파라미터로 전달받 은 HttpServletRequest를 사용하여 로그인 여부를 판단해주는 일종의 보조 클래스이다. 회원이 로그인을 했 을 때 LoginChecker.isLogin() 메소드가 true를 리턴한다고 가정할 경우, 위 코드와 같이 로그인을 하면 필 터 체인의 다음 필터로 이동하고 로그인을 하지 않은 상태로 판단되면 response를 사용하여 다른 페이지로 이동하면 된다. 로그 인을 하지 않았을 때 이동하는 페이지는 일반적으로 로그인 폼을 보여주는 페이지가 될 것이다.
위 코드를 보면 알겠지 만 클라이언트의 요청이 반드시 필터 체인의 모든 필터를 통과해야 하는 것은 아니며, 어떤 필터든지 간에 다음 필터로 이동하지 않 고 중간에 체인을 끝낼 수 있도록 되어 있다. 사용자 인증 필터는 바로 그러한 특징을 이용한 것이다. 사용자 인증 필터는 거의 대 부분의 회원 서비스에서 사용될 것이며 다음과 같이 web.xml을 설정하여 사용자 인증 필터를 적용하면 될 것이다.
<filter>
<filter-name>LoginChecker</filter-name>
<filter-class>LoginCheckFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginChecker</filter-name>
<url-pattern>/board/*</url-pattern>
</filter-mapping>
인 증 필터를 사용함으로써 얻게 되는 장점은 서블릿/JSP와 같은 최종 자원에서 일일이 로그인 여부를 판단하지 않아도 된다는 점이 다. 이는 서블릿과 JSP는 클라이언트의 요청에 알맞은 작업만을 수행하는 역할을 맡게 되고 사용자 인증을 검사하는 역할은 맡지 않 아도 된다는 것을 의미한다. 즉, 역할에 알맞게 객체가 분리되는 것이다.
XSL/T 필터
필 터가 나옴으로써 객체 지향적으로 변화된 부분이 있다면 바로 XML과 관련된 부분일 것이다. 필터가 생김으로써 서블릿과 JSP 는 더 이상 XSL/T를 이용하여 XML 문서를 HTML이나 WML과 같은 다른 통신 표준으로 변경해줄 필요가 없게 되었다. 이 제, XSL/T를 이용한 변환 작업은 이제 필터가 맡게 되었으며 서블릿과 JSP는 XML 문서를 생성하는 것 이외에 다른 작업 은 할 필요가 없게 되었다.
XSL/T 필터는 응답 데이터를 변경해주는 필터이다. 즉, 서블릿/JSP가 생성 한 XML 문서를 XSL/T를 이용하여 완전히 새로운 형태로 재구성하는 것이 XSL/T 필터의 역할이다. 서블릿/JSP의 응답 결 과인 XML 문서를 완전히 새로운 형태로 변경해주기 위해서는 서블릿/JSP가 출력한 XML 데이터를 클라이언트(웹브라우저)에 곧바 로 전송해서는 안된다. 대신, 서블릿/JSP가 출력한 데이터를 임의의 버퍼에 저장한 후, 그 버퍼에 저장된 XML 데이터를 XSL /T를 사용하여 변환해야만 한다. 이를 위해 먼저 버퍼의 역할을 할 출력 스트림을 작성해야 하며, 또한 그 출력 스트림은 서블릿 과 JSP에서 주로 사용되는 PrintWriter 타입이어야만 한다. 다음은 이 예제에서 서블릿/JSP가 출력하는 데이터를 저장해 둘 버퍼의 역할을 하는 ReponseBufferWriter 클래스이다.
class ReponseBufferWriter extends PrintWriter {
public ReponseBufferWriter() {
super(new StringWriter(4096) );
}
public String toString() {
return ((StringWriter)super.out).toString();
}
}
특 별히 어렵지는 않으며, ResponseBufferWriter는 print(), println(), write() 등의 메소드를 통 해서 전달된 데이터를 StringWriter에 저장하는 기능을 한다. toString() 메소드는 StringWriter에 저장 된 데이터를 String 타입으로 변환해주는 역할을 한다.
출력 버퍼를 만들었으니 그 다음으로 해야 할 일은 최 종 자원인 서블릿과 JSP가 ResponseBufferWirter를 출력 스트림으로 사용하도록 하는 응답 래퍼 클래스를 작성하 는 것이다. 이 예제에서 사용할 응답 래퍼 클래스는 다음과 같다.
class XSLTResponseWrapper extends HttpServletResponseWrapper {
private ReponseBufferWriter buffer = null;
public XSLTResponseWrapper(HttpServletResponse response) {
super(response);
buffer = new ReponseBufferWriter();
}
public PrintWriter getWriter() throws java.io.IOException {
return buffer;
}
public void setContentType(String contentType) {
// do nothing
}
public String getBufferedString() {
return buffer.toString();
}
}
위 코 드를 보면 XSLTResponseWrapper 클래스가 복잡하지 않다는 것을 알 수 있다. XSLTResponseWrapper 클 래스의 getWriter() 메소드는 실제 클라이언트로의 응답에 해당하는 스크림을 리턴하는 대신 앞에서 작성 한 ResponseBufferWriter를 리턴한다. 이렇게 함으로써 ServletResponse의 getWriter() 메소드 를 호출하는 서블릿/JSP는 클라이언트로의 응답 스트림이 아닌 ResponseBufferWriter를 출력 스트림으로 사용하게 된 다. 또 하나 주의해야 할 부분이 바로 setContentType() 메소드가 아무 기능도 하지 않는다는 점인데, 이 이유에 대해 서는 뒤에서 설명하도록 하겠다.
이제 XML 데이터를 임시로 저장할 Writer도 구현하였고 또한 응답 래퍼도 구현하였다. 이제 마지막으로 남은 것은 필터를 구현하는 것 뿐이다. 필터는 다음과 같은 4 단계로 작업을 처리한다.
응답 래퍼(XSLTResponseWrapper)를 생성한다.
생성한 응답 패퍼를 체인의 다음 필터에 전달한다.
래퍼로부터 서블릿/JSP가 출력한 데이터를 읽어와 XSL/T를 사용하여 HTML로 변환한다.
변환된 결과인 HTML을 실제 응답 스트림에 출력한다.
이 과정을 구현한 것이 바로 XSLTFilter 클래스이다.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
public class XSLTFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws java.io.IOException, ServletException {
response.setContentType("text/html; charset=euc-kr");
PrintWriter writer = response.getWriter();
XSLTResponseWrapper responseWrapper =
new XSLTResponseWrapper((HttpServletResponse)response);
chain.doFilter(request, responseWrapper);
// XSL/T 변환
try {
TransformerFactory factory = TransformerFactory.newInstance();
Reader xslReader = new BufferedReader(new FileReader("c:/book.xsl"));
StreamSource xslSource = new StreamSource(xslReader);
Transformer transformer = factory.newTransformer(xslSource);
String xmlDocument = responseWrapper.getBufferedString();
Reader xmlReader = new StringReader(xmlDocument);
StreamSource xmlSource = new StreamSource(xmlReader);
StringWriter buffer = new StringWriter(4096);
transformer.transform( xmlSource, new StreamResult(buffer) );
writer.print(buffer.toString());
} catch(Exception ex) {
throw new ServletException(ex);
}
writer.flush();
writer.close();
}
public void destroy() {
}
}
XSLTFilter 클 래스의 doFilter() 메소드를 차근 차근 분석해보도록 하자. doFilter() 메소드가 가장 먼저 하는 것은 응답의 컨텐 츠 타입을 text/html로 지정하는 것이다. 물론, 한글을 사용하기 때문에 뒤에 charset도 추가해주었다. 여기 서 response 객체는 클라이언트에 대한 응답을 나타내며, 클라이언트는 결과 데이터를 HTML 문서로 인식하게 된다. 앞에 서 XSLTResponseWrapper의 setContentType() 메소드에서 아무것도 하지 않았었는데, 그 이유 는 XSLTFilter의 doFilter() 메소드에서 지정한 컨텐츠 타입을 변경할 수 없도록 하기 위함이다.
그 다 음에는 래퍼 클래스를 생성한다. 래퍼 클래스는 XSLTResponseWrapper이며, 생성된 래퍼 클래스 는 chain.doFilter()를 통해서 다음 필터에 전달된다. 필터 체인의 실행이 완료되 면 XSLTResponseWrapper 객체에는 서블릿이나 JSP가 출력한 XML 응답 데이터가 저장되며, 그 데이터 는 responseWrapper.getBufferedString() 메소드를 통해서 구할 수 있게 된다. 이렇게 해서 구 한 XML 데이터는 JAXP 1.1에서 제공하는 Transformer의 transform() 메소드를 통해서(즉, XSL/T를 통 해서) HTML 형식으로 변환된다.
이제 XSL/T 필터와 관련된 모든 클래스의 구현은 끝이 났다. 이제 남은 것 은 XSL/T에서 사용할 XSL 문서를 작성하고 XML 문서를 생성해주는 JSP/서블릿을 프로그래밍하고 그리고 web.xml 파일 을 통해서 XSLTFilter를 필터로 등록하는 것이다. 먼저 web.xml 파일을 필터를 사용하여 지정해보자.
<filter>
<filter-name>XSLT</filter-name>
<filter-class>XSLTFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XSLT</filter-name>
<url-pattern>/xml/*</url-pattern>
</filter-mapping>
이 제 /xml/로 들어오는 모든 요청은 XSLTFilter를 토?서 처리된다. 이제 XML 문서를 생성해주는 JSP 페이지를 작성해 보자. 여기서는 테스트를 위해서 다음과 같이 간단한 JSP 페이지를 사용하였다. (이 JSP를 book.jsp라 하자.)
<?xml version="1.0" encoding="euc-kr" ?>
<%@ page contentType="text/xml; charset=euc-kr" %>
<list>
<book>
<title>JavaCan.com의 JSP Professional</title>
<author>이동훈, 최범균</author>
<price>24,000</price>
</book>
<book>
<title>JavaCan.com의 Victory Java</title>
<author>이동훈, 최범균</author>
<price>30,000</price>
</book>
</list>
위 JSP 페이지는 보다시피 XML 문서를 생성해낸다. 이 XML 문서를 HTML로 변환해주기 위해 사용되는 XSL은 다음과 같다. (여기서는 XSL에 대한 내용은 설명하지 않겠다.)
<?xml version="1.0" encoding="euc-kr" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method = "html" indent="yes" encoding="euc-kr" />
<xsl:template match="list">
<html>
<head><title>책 목록</title></head>
<body>
현재 등록되어 있는 책의 목록은 다음과 같습니다.
<ul>
<xsl:for-each select="book">
<li><b><xsl:value-of select="title" /></b>
(<xsl:value-of select="price" /> 원)
<br />
<i><xsl:value-of select="author" /></i>
</li>
</xsl:for-each>
</ul>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
book.jsp 를 웹어플리케이션의 /xml 하위디렉토리에 복사한 후 웹브라우저에서 book.jsp를 요청한 결과의 소스 코드를 보면 다음과 같 이 XML이 아닌 XSLTFilter 필터를 통해서 변경된 결과가 오는 것을 확인할 수 있을 것이다.
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=euc-kr">
<title>책 목록</title>
</head>
<body>
현재 등록되어 있는 책의 목록은 다음과 같습니다.
<ul>
<li>
<b>JavaCan.com의 JSP Professional</b>
(24,000 원)
<br>
<i>이동훈, 최범균</i>
</li>
<li>
<b>JavaCan.com의 Victory Java</b>
(30,000 원)
<br>
<i>이동훈, 최범균</i>
</li>
</ul>
</body>
</html>
결론
여 기서 살펴본 필터의 예제는 매우 간단하게 구현되는 것들이었지만, 아마 필터를 구현하는 데 있어서 가장 기본적인 형태를 갖는 예제 가 아닐까 생각된다. 이번 필터 예제를 통해서 여러분들은 필터의 쓰임새가 많겠구나 하고 생각했을 것이다. 어쩌면 벌써부터 압축 필 터나 이미지 생성 필터 등을 생각하고 있을지도 모르겠다. 하지만 필터의 응용을 생각하기 이전에 다음과 같은 점을 염두해두었으면 한 다.
필터는 재사용이 가능해야 한다. 즉, 필터들은 객체 지향적으로 설계되고 구현되어야 한다.
필터를 통과하지 않아도 JSP/서블릿/기타 자원은 알맞은 결과를 출력해야 한다.
필터간에 커플링(coulpling; 결합도)이 존재해서는 안 된다.
이러한 것들을 염두하고서 필터를 설계하고 구현한다면, 그 필터는 여러분이 개발하게 될 웹 어플리케이션 곳곳에서 사용될 것이며 그만큼 여러분의 웹 어플리케이션은 역할별로 알맞게 분리된 구조를 갖게 될 것이다. -
추가 이미지
-
이 글은 스프링노트에서 작성되었습니다.
'JSP' 카테고리의 다른 글
include (0) | 2012.05.08 |
---|---|
IBatis (0) | 2012.05.08 |
FileUploadRequestWrapper (0) | 2012.05.08 |
FileUpLoad (0) | 2012.05.08 |
엑셀파일다운로드 (0) | 2012.05.08 |