관리 메뉴

거니의 velog

(25) 서블릿의 필터와 리스너 기능 2 본문

Java_Servlet

(25) 서블릿의 필터와 리스너 기능 2

Unlimited00 2023. 8. 30. 21:12

3. Filter API

* 이번에는 필터에 대해 알아보자. 필터란 브라우저에서 서블릿에 요청하거나 응답할 때 여러 요청이나 응답과 관련해 여러 가지 작업을 처리하는 기능이다. 프로그래밍을 하다가 한글 인코딩처럼 각 서블릿에서 반복적으로 처리해야 하는 작업이 있을 수 있는데, 이런 경우 서블릿의 공통 작업을 미리 필터에서 처리하면 반복해서 작업할 필요가 없다.

* 이처럼 웹 페이지에서 입력한 한글을 서블릿에 전달하려면 setCharacterEncording() 메서드를 이용해 한글 인코딩 설정을 서블릿마다 상단에 추가해야 했다. 하지만 모든 서블릿에서 공통으로 처리하는 작업을 먼저 필터에서 처리해 주면 편리하다.

* 필터는 용도에 따라 크게 요청 필터와 응답 필터로 나뉘며 다음과 같은 API가 있다.

----------------------------------------------------------

(1) 요청 필터

- 사용자 인증 및 권한 검사

- 요청 시 요청 관련 로그 파일

- 인코딩 기능

----------------------------------------------------------

(2) 응답 필터

- 응답 결과에 대한 암호화 작업

- 서비스 시간 측정

----------------------------------------------------------

(3) 필터 관련 API

- javax.servlet.Filter

- javax.servlet.FilterChain

- javax.servlet.FilterConfig

<Filter 인터페이스에 선언된 메서드>

메서드 기능
destroy() 필터 소멸 시 컨테이너에 의해 호출되어 종료 작업을 수행한다.
doFilter() 요청/응답 시 컨테이너에 의해 호출되어 기능을 수행한다.
init() 필터 생성 시 컨테이너에 의해 호출되어 초기화 작업을 수행한다.

<FilterConfig의 메서드>

메서드 기능
getFilterName() 필터 이름을 반환한다.
getInitParameter(String name) 매개변수 name에 대한 값을 반환한다.
getServletContext() 서블릿 컨텍스트 객체를 반환한다.

(1) 사용자 정의 Filter 만들기

* 그럼 이번에는 직접 필터를 만들어 보자. 사용자 정의 필터는 반드시 Filter 인터페이스를 구현해야 한다. 그리고 init(), doFilter(), destroy()의 추상 메서드를 구현해 주어야 한다. 사용자 정의 필터를 생성하면 필터를 각각의 요청에 맞게 적용하기 위해 필터 매핑을 해야 하는데, 필터를 매핑하는 방법은 다음 두 가지이다.

- 애너테이션을 이용하는 방법

- web.xml에 설정하는 방법

* 일반적으로 애너테이션을 이용하는 방법이 편리하므로 많이 사용한다.


(2) Filter를 이용한 한글 인코딩 실습

* 우선 한글 인코딩 처리를 통해 필터 기능을 실습해 보자.

1. 다음과 같이 LoginTest, EncodeFilter 클래스 파일을 준비한다.

2. 로그인창에서 ID 대신 이름을 입력한 후 서블릿으로 전송하도록 login.html을 작성한다.

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인창</title>
    </head>

    <body>
        <form name="frmLogin" method="post" action="login" enctype="utf-8">
            이름 :<input type="text" name="user_name"><br>
            비밀번호:<input type="password" name="user_pw"><br>
            <input type="submit" value="로그인">
            <input type="reset" value="다시입력">
        </form>
    </body>
</html>

3. LoginTest 클래스를 다음과 같이 작성한다. 서블릿에서는 setCharacterEncording() 메서드를 주석 처리하여 한글 처리를 하지 않도록 한다.

package sec03.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet("/login")
public class LoginTest extends HttpServlet {
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// request.setCharacterEncoding("utf-8"); // post 방식으로 한글 전송 시 인코딩 작업을 생략한다.
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();

		String user_name = request.getParameter("user_name");
		String user_pw = request.getParameter("user_pw");
		out.println("<html><body>");
		out.println("이름는 " + user_name + "<br>");
		out.println("비밀번호는 " + user_pw + "<br>");
		out.println("</body></html>");

	}

}

4. 다음은 인코딩 처리를 하지 않았을 때의 출력 결과이다. 한글이 깨져서 표시되는 것을 볼 수 있다.

- http://localhost:8090/pro10/login.html

5. 이번에는 필터를 이용해 한글 인코딩 기능을 구현해 보자. sec03.ex01 패키지를 선택하고 마우스 오른쪽 버튼을 클릭한 후 New > Filter를 선택한다.

6. Class name으로 EncoderFilter를 입력하고 Next를 클릭한다.

7. Filter mapping에서 /EncorderFilter를 선택한 후 Edit을 클릭한다.

8. 모든 요청에 대해 필터 기능을 수행하도록 Pattern을 /* 로 수정한다.

9. URL pattern 에서 /* 을 확인하고 Next를 클릭한다.

10. Finish를 클릭하여 필터 클래스가 생성된 것을 확인한다.

@WebFilter 애너테이션으로 필터 생성 확인

11. 이제 다음과 같이 EncorderFilter 클래스를 작성한다. 사용자 정의 필터 클래스는 반드시 Filter 인터페이스를 구현해야 한다. 브라우저 요청 시 doFilter() 메서드의 매개변수로 request와 response가 전달되며, doFilter() 메서드는 FilterChain 타입인 chain을 세 번째 매개변수로 가진다. 전달된 request를 이용해 한글 인코딩 작업을 한다. chain.doFilter() 메서드를 기준으로 위쪽에 위치한 코드는 요청 필터 기능을 수행한다.

package sec03.ex01;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;


@WebFilter("/*") // @WebFilter 애너테이션을 통해 모든 요청이 필터를 거치게 된다.
public class EncoderFilter implements Filter { // 사용자 정의 필터는 반드시 Filter 인터페이스를 구현해야 한다.
	
	ServletContext context;

	public void init(FilterConfig fConfig) throws ServletException {
		System.out.println("utf-8 인코딩............");
		context = fConfig.getServletContext();
	}
	
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { // doFilter() 안에서 실제 필터 기능을 구현한다.
		System.out.println("doFilter 호출");
		request.setCharacterEncoding("utf-8"); // 한글 인코딩 작업을 한다.
		String context = ((HttpServletRequest) request).getContextPath(); // 웹 애플리케이션의 컨텍스트 이름을 가져온다.
		String pathinfo = ((HttpServletRequest) request).getRequestURI(); // 웹 브라우저에서 요청한 요청 URI를 가져온다.
		String realPath = request.getRealPath(pathinfo); // 요청 URI의 실제 경로를 가져온다.
		String mesg = " Context  정보:" + context + "\n URI 정보 : " + pathinfo + "\n 물리적 경로:  " + realPath;
		System.out.println(mesg);

		long begin = System.currentTimeMillis();
		chain.doFilter(request, response); // 다음 필터로 넘기는 작업을 수행한다.
		
		long end = System.currentTimeMillis();
		System.out.println("작업 시간:" + (end - begin) + "ms");

	}
	
	public void destroy() {
		System.out.println("destroy 호출");
	}

}

12. 톰캣을 재실행하고 로그인창에서 한글을 입력한다. 이번에는 필터를 거쳐 한글이 제대로 출력되는 것을 확인할 수 있다. 요청 필터 기능을 수행할 때마다 doFilter()가 수행되므로 이클립스 콘솔에도 다음과 같은 메시지가 출력된다.


(3) 응답 필터 사용

* 이번에는 응답에 대해 수행하는 응답 필터를 알아보자. 서블릿에서 요청과 응답에 대한 필터 기능은 동일한 필터가 수행한다.

* 한 필터에서 요청과 응답 기능을 수행하는 방법을 다음 그림에 나타내었다. 필터에서 doFilter() 메서드를 기준으로 위쪽에 위치한 코드는 요청 필터 기능을 수행하고, 아래에 위치한 코드는 응답 필터 기능을 수행한다.


(4) 응답 필터 기능으로 작업 시간 구하기

* 응답 필터 기능을 이용해 로그인 요청 시 작업 수행 시간을 구해 보자.

1. 앞 절의 EncoderFilter 클래스를 그대로 사용한다. chain.doFilter() 메서드 위아래에 요청 전과 후의 시각을 구하는 코드를 각각 추가한다.

package sec03.ex01;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;


@WebFilter("/*")
public class EncoderFilter implements Filter {
	
	ServletContext context;

	public void init(FilterConfig fConfig) throws ServletException {
		System.out.println("utf-8 인코딩............");
		context = fConfig.getServletContext();
	}
	
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { // doFilter() 안에서 실제 필터 기능을 구현한다.
		System.out.println("doFilter 호출");
		request.setCharacterEncoding("utf-8"); 
		String context = ((HttpServletRequest) request).getContextPath(); 
		String pathinfo = ((HttpServletRequest) request).getRequestURI(); 
		String realPath = request.getRealPath(pathinfo); 
		String mesg = " Context  정보:" + context + "\n URI 정보 : " + pathinfo + "\n 물리적 경로:  " + realPath;
		System.out.println(mesg);

		long begin = System.currentTimeMillis(); // 요청 필터에서 요청 처리 전의 시각을 구한다.
		chain.doFilter(request, response); 
		
		long end = System.currentTimeMillis(); // 응답 필터에서 요청 처리 후의 시각을 구한다.
		System.out.println("작업 시간:" + (end - begin) + "ms"); // 작업 요청 전과 후의 시각 차를 구해 작업 수행 시간을 구한다.

	}
	
	public void destroy() {
		System.out.println("destroy 호출");
	}

}

* long begin = System.currentTimeMillis() 메서드는 chain.doFilter() 메서드 위쪽에 위치하므로 요청 시 시각을 구한다. 

* long end = System.currentTimeMillis() 메서드는 chain.doFilter() 메서드 아래에 위치하므로 응답 시 시각을 구한다.

2. 실행하면 다음과 같이 로그인 요청 작업에 걸린 시간을 콘솔로 출력한다. 로컬 PC에서의 실습이므로 빠른 편이다.

* 이상으로 필터 기능에 대해 알아봤다. 서블릿이나 JSP에서 공통으로 처리해야 할 작업을 필터에 구현해 놓고 사용하면 편리하다는 것을 기억해 두자.