[23/07/10] 서블릿 (6)

yeju·2023년 7월 10일
0

Servlet

목록 보기
6/6
post-thumbnail

📖 10. 서블릿의 필터와 리스너 기능

📌 서블릿 속성과 스코프

서블릿의 속성(Attribute)이란?
HttpServletRequest, HttpSession, ServletContext 객체에 바인딩되는 객체(정보)

서블릿의 스코프(Scope)란?
서블릿에 바인딩된 속성이 유지되는 범위(라이프사이클)

  • request 스코프 : 한번 넘어간 페이지/서블릿까지 사용 가능
  • session 스코프 : 같은 브라우저(세션id) 에서 사용 가능
  • application 스코프 : 서버가 종료될 때까지 사용 가능

📌 서블릿의 URL 패턴

URL 패턴이란?
서블릿에 매핑할 때 사용되는 이름, /(슬래시) 로 시작해야 함
정확한 이름까지 일치, 디렉터리 일치, 확장자 이름 일치 등 여러 가지 URL 패턴을 지정 가능

📝 URI, URL을 구하는 다양한 HttpServletRequest의 메서드

getContextPath() : 컨텍스트 이름 (/프로젝트(컨텍스트)명)
getRequestURL() : 전체 URL (http://~) (StringBuffer 타입, 캐스팅 필요)
getServletPath() : 해당 서블릿에 매핑한 URL 패턴
getRequestURI() : URI (/컨텍스트명~)

📝 요청 URL 관련 정보 확인하기

package sec02.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("/first/test")
public class TestServlet1 extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		// 컨텍스트 이름 가져오기 : /~
		String context = request.getContextPath();
		// URL 이름 가져오기 (StringBuffer 타입, 캐스팅 필요) : http://~
		String url = request.getRequestURL().toString();
		// 서블릿 매핑 이름 가져오기 : 지정한 URL 패턴
		String mapping = request.getServletPath();
		// URI 가져오기 : /컨텍스트명부터~
		String uri = request.getRequestURI();
		
		out.print("<html><head><title>Test Servlet1</title></head>");
		out.print("<body bgcolor='lightgreen'>");
		out.print("TestServlet1 입니다.<br>");
		out.print("컨텍스트 이름 : "+context+"<br>");
		out.print("전체 경로 : "+url+"<br>");
		out.print("매핑 이름 : "+mapping+"<br>");
		out.print("URI : "+uri+"<br>");
		out.print("</body></html>");
	}
}

URL패턴을 각각 다르게 설정하여 동일한 내용의 서블릿을 2개 더 작성
TestServlet1 : /first/test
TestServlet2 : /first/*
TestServlet3 : *.do
💻 실행 결과
요청 URL에 따라 다른 서블릿으로 연결되는 것을 확인 가능

📌 필터 API 사용하기

필터(Filter)란?
브라우저에서 서블릿에 요청하거나 서블릿에서 브라우저에 응답할 때, 요청/응답 전에 여러 가지 작업을 먼저 처리하는 기능
ex) 한글 인코딩 등 여러 서블릿에서 공통으로 수행하는 작업을 필터로 만들어 사용

  • 요청 필터 : request를 매핑된 서블릿에서 처리하기 이전 수행할 작업
    • 사용자 인증 및 권한 검사
    • 인코딩 작업
  • 응답 필터 : 서블릿이 작업을 마치고 웹 브라우저에 응답하기 이전 수행할 작업
    • 응답 결과에 대한 암호화 작업
    • 로그 파일 기록
    • 서비스 시간 측정

📝 필터 관련 API 메서드

Filter 인터페이스의 메서드
doFilter() : 필터의 주요 기능을 수행, 실행할 조건이 되면 자동으로 호출됨
init() : 필터 생성 시 초기화 작업
destroy() : 필터 소멸 시 종료 작업

📄 [예제] 한글 인코딩 필터 만들기

1. 내용을 입력받고 처리할 페이지, 서블릿 작성

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
	<form action="login" method="post" encType="utf-8">
		이름 : <input type="text" name="user_id"><br>
		비밀번호 : <input type="password" name="user_pw"><br>
		<input type="submit" value="로그인">
		<input type="reset" value="다시 입력">
	</form>
</body>
</html>
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 doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 필터 처리할 부분 주석 처리하기
		// request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		
		out.print("<html><body>");
		out.print("아이디 : "+user_id+"<br>");
		out.print("비밀번호 : "+user_pw+"<br>");
		out.print("</body></html>");
	}
}

2. 요청받은 데이터를 utf-8로 인코딩하는 필터 작성

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.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebFilter("/*") // 모든 요청이 해당 필터를 거치게 함
public class EncoderFilter extends HttpFilter implements Filter {
    ServletContext context;
    
    public void init(FilterConfig fConfig) throws ServletException {
		System.out.println("utf-8 인코딩......");
		// 서블릿 컨텍스트 가져오기 (FilterConfig의 메서드)
		context = fConfig.getServletContext();
	}
    
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    	/* 참고 : 필터가 HttpFilter 클래스를 상속받으면
        doFilter() 의 매개변수를 HttpServletRequest, HttpServletResponse 타입으로
        가질 수 있음 (원래는 매개변수 타입에 Http 없음) */
    	System.out.println("doFilter 호출");
    	
    	request.setCharacterEncoding("utf-8");
    	String context = request.getContextPath();
    	String pathinfo = request.getRequestURI();
    	String realPath = this.context.getRealPath(pathinfo);
    	String mesg = " Context 정보 : " + context + "\n URI 정보 : " + pathinfo + "\n 물리적 경로 : " + realPath;
    	System.out.println(mesg);
    	
    	long begin = System.currentTimeMillis();
    	
    	// doFilter() : 다음 필터가 있으면 다음 필터 실행하고, 없으면 서블릿으로 넘김
    	
    	// 이 위의 내용은 요청 필터 기능 (요청을 서블릿에서 처리하기 전 실행)
		chain.doFilter(request, response);
		// 이 아래의 내용은 응답 필터 기능 (서블릿에서 처리 후 웹 브라우저에 응답하기 전 실행)
		
		long end = System.currentTimeMillis();
		System.out.println("작업 시간 : "+(end-begin)+"ms");
	}

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

💻 실행 결과
필터를 적용하면 요청받은 데이터가 utf-8로 인코딩됨. 아래는 콘솔 출력 내용

doFilter 호출
 Context 정보 : /pro10
 URI 정보 : /pro10/login.html
 물리적 경로 : D:\JYJ\.metadata\.plugins\
 org.eclipse.wst.server.core\tmp0\wtpwebapps\pro10\pro10\login.html
작업 시간 : 1ms

📌 서블릿 관련 리스너 API 사용하기

이벤트 리스너(Listener)란?
특정 이벤트가 발생했을 때 그 이벤트를 감지하고 처리하는 인터페이스
이벤트 처리에 필요한 리스너 인터페이스를 구현해 사용

📝 HttpSessionBindingListener 이용해 접속자 수 표시하기

HttpSessionBindingListenerHttpSession 에 속성이 바인딩/언바인딩 될 때 작동하는 리스너로, 이 인터페이스를 구현한 클래스의 객체가 HttpSession 에 바인딩/언바인딩될 때 자동으로 valueBound() / valueUnbound() 메서드가 호출된다. 해당 메서드는 추상 메서드로, 구현 시 내용을 필수로 작성해야 한다.

1. 로그인 페이지 작성

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
	<form action="login" method="post" encType="utf-8">
		아이디 : <input type="text" name="user_id"><br>
		비밀번호 : <input type="password" name="user_pw"><br>
		<input type="submit" value="로그인">
		<input type="reset" value="다시 입력">
	</form>
</body>
</html>

2. 이벤트 리스너 작성

package sec04.ex01;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

// 리스너로 사용하기 위해 HttpSessionBindingListener 인터페이스 구현
public class LoginImpl implements HttpSessionBindingListener {
	String user_id;
	String user_pw;
    // 접속 중인 사용자 수를 카운트할 변수
	static int total_user = 0;
    // 생성자
	public LoginImpl() {}
	public LoginImpl(String user_id, String user_pw) {
		this.user_id = user_id;
		this.user_pw = user_pw;
	}
	
    // valueBound(), valueUnbound() 추상 메서드 오버라이딩
    public void valueBound(HttpSessionBindingEvent event) {
    	System.out.println("사용자 접속");
    	++total_user;
    }

    public void valueUnbound(HttpSessionBindingEvent event) {
    	System.out.println("사용자 접속 해제");
    	total_user--;
    }
}

이 리스너 객체를 속성으로 가진 세션은 이 객체를 바인딩/언바인딩할 때 valueBound() / valueUnbound() 메서드를 자동으로 호출함

3. 로그인을 처리할 서블릿 작성

package sec04.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;
import javax.servlet.http.HttpSession;

@WebServlet("/login")
public class LoginTest extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		HttpSession session = request.getSession();
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		// 이벤트 리스너 기능을 할 LoginImpl 객체 생성
		LoginImpl loginUser = new LoginImpl(user_id, user_pw);
		
		if(session.isNew()) {
			// 새로 생성된 세션이면,
            // 새로 생성한 이벤트 리스너(HttpSessionBindingListener)를 세션에 바인딩
			session.setAttribute("loginUser", loginUser);
            // 리스너가 바인딩되어서 LoginImpl 클래스 안의 valueBound() 메서드가 자동 호출됨
		}
		
		out.print("<html><head>");
		// 자바스크립트로 5초마다 자동 새로고침(요청) 설정하기
		out.print("<script type='text/javascript'>");
		out.print("setTimeOut('history.go(0);', 5000)");
		out.print("</script>");
		
		out.print("<body>");
		out.print("아이디 : "+loginUser.user_id+"<br>");
		out.print("총 접속자 수 : "+LoginImpl.total_user+"<br>");
		out.print("</body></html>");
	}
}

💻 실행 결과
다른 브라우저에서 로그인하면 접속자 수(리스너의 static 변수) 가 1씩 늘어남, 같은 브라우저에서 다시 로그인하면 아이디는 변경되지만 세션에 바인딩이 일어나지 않으므로 접속자 수는 그대로 유지됨

📝 HttpSessionListener 이용해 접속자 수 표시하기

HttpSessionListenerHttpSession 가 생성/소멸될 때 작동하는 리스너로, 이 인터페이스를 구현한 클래스의 객체가 HttpSession 에 등록되면 해당 세션이 생성, 소멸될 때 자동으로 sessionCreated() / sessionDestroyed() 메서드가 호출된다. 해당 메서드는 추상 메서드로, 구현 시 내용을 필수로 작성해야 한다.

위 실습에서는 로그인 후 접속자 수가 증가하는 것만 확인했지만 이번에는 로그인 후 접속자 목록 표시, 로그아웃하면 접속자 수가 감소하는 것까지 확인

1. 로그인 페이지 작성(위 실습과 동일)

2. 이벤트 리스너 작성

package sec04.ex02;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class LoginImpl implements HttpSessionListener {
	String user_id;
	String user_pw;
	static int total_user = 0;
	
    public LoginImpl() {
    }
    public LoginImpl(String user_id, String user_pw) {
		this.user_id = user_id;
		this.user_pw = user_pw;
	}

    public void sessionCreated(HttpSessionEvent se)  { 
    	System.out.println("세션 생성");
    	// 세션 생성 시 접속자 수 1 증가
    	++total_user;
    }

    public void sessionDestroyed(HttpSessionEvent se)  { 
    	System.out.println("세션 소멸");
    	// 세션 소멸 시 접속자 수 1 감소
    	--total_user;
    }
}

3. 로그인을 처리할 서블릿 작성

package sec04.ex02;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

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

@WebServlet("/login")
public class LoginTest extends HttpServlet {
	ServletContext context = null;
	/* 주의 : 서블릿 클래스의 멤버 변수는 클라이언트끼리 같은 저장 공간을 공유함 */
	List user_list = new ArrayList();
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		context = getServletContext();
		HttpSession session = request.getSession();
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		
		LoginImpl loginUser = new LoginImpl(user_id, user_pw);
		
		if(session.isNew()) {
			// 새로 생성된 세션이면(최초 로그인이면)
			// 새로 생성한 이벤트 리스너(HttpSessionListener)를 세션에 바인딩
			// 리스너가 등록되고 세션이 새로 생성되면,
            // LoginImpl 클래스 안의 sessionCreated() 메서드가 자동 호출됨
			session.setAttribute("loginUser", loginUser);
			// 접속자 id를 ArrayList에 저장하고 ServletContext의 속성으로 저장
			user_list.add(user_id);
			context.setAttribute("user_list", user_list);
		}
		
		out.print("<html><body>");
		out.print("아이디 : "+loginUser.user_id+"<br>");
		// 세션에 바인딩 이벤트 처리 후 총 접속자 수 표시
		out.print("총 접속자 수 : "+LoginImpl.total_user+"<br><br>");
		out.print("접속 아이디  : <br>");
		
		// ServletContext에 바인딩한 ArrayList 가져와 출력하기
		List list = (ArrayList) context.getAttribute("user_list");
		for(int i=0; i<list.size(); i++) {
			out.println(list.get(i)+"<br>");
		}
		out.print("<a href='logout?user_id="+user_id+"'>로그아웃</a>");
		out.print("</body></html>");
	}
}

4. 로그아웃 처리 서블릿 작성

package sec04.ex02;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

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

@WebServlet("/logout")
public class LogoutTest extends HttpServlet {
	ServletContext context;
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}
	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		context = getServletContext();
		HttpSession session = request.getSession();
		// 요청에서 넘어온 삭제할 아이디 값
		String user_id = request.getParameter("user_id");
		// 세션 소멸시키기
		session.invalidate();
		// 서블릿 컨텍스트에서 유저 목록 가져와 삭제하고 다시 set하기
		List user_list = (ArrayList) context.getAttribute("user_list");
		user_list.remove(user_id);
		context.removeAttribute("user_list");
		context.setAttribute("user_list", user_list);
		out.println("<br>로그아웃했습니다.");
	}
}

💻 실행 결과
다른 브라우저에서 로그인하면 접속자 수가 변경되고 로그아웃 시 아이디가 접속자 목록에서 삭제되며 접속자 수가 변경된 것을 확인 가능

profile
🌱

0개의 댓글