본문 바로가기

WEB

[WEB] 서블릿 프로그래밍

CGI 프로그램

웹 브라우저와 웹 서버 사이에는 HTTP 프로토콜을 통해 요청 형식과 응답 형식에 맞게 데이터를 주고 받게 된다. 그렇다면 웹 서버와 프로그램 사이에도 마찬가지로 데이터를 주고 받기위한 규칙이 필요할 것이다. 그 규칙이 바로 CGI(Common Gateway Inteface)이다. 그리고 이 규칙에 따라서 웹서버와 데이터를 주고 받을 수 있게 작성된 프로그램을 CGI 프로그램이라고 한다.

 

서블릿

자바로 만든 CGI 프로그램을 서블릿(Servlet)이라고 한다. 서블릿은 컴파일 방식으로 바이트 코드 파일(.class 파일)을 컴파일하여 웹서버와 소통하는데 다른 컴파일 방식의 CGI 프로그램(C/C++)과 다르게 웹서버와 직접 데이터를 주고 받지 않고, 중간 관리자인 서블릿 컨테이너에 의해 관리된다.

웹 개발자는 서블릿을 개발하게 되므로 더이상 CGI 규칙에 대해 알 필요가 없어진다. 대신 서블릿 컨테이너와 서블릿 간의 소통을 위한 서블릿 규칙에 대해 알아야 한다.

 

javax.servlet.Servlet 인터페이스 

자바의 서블릿은 Servelt 인터페이스 구현 객체를 통해 만들어야 한다. Servlet 인터페이스는 서블릿 컨테이너가 서블릿에게 호출할 메서드 5개를 가지고 있는데 다음과 같다.

  • init()
  • service()
  • destroy()
  • getServletInfo()
  • getServletConfig()

 

LifeCycle 관련 메서드 : init() / service() / destroy()

init()는 서블릿 생성 후 초기화 작업을 위한 메서드이다. service()는 클라이언트가 서블릿에게 요청을 할때 마다 실행 되는 메서드이다. destroy()는 서블릿 컨테이너의 종료, 서블릿의 비활성화, 앱의 정지 등에 의해 호출된다.

 

기타 : getServletInfo() / getServletConfig()  - 특정 정보 추출을 위한 메서드이다. 자세한 설명은 생략

public class CalculatorServlet implements Servlet {

	ServletConfig config = null;
    
        @Override
        public void init(ServletConfig config) {
            this.config = config;
        }
    
	@Override
	public void service(
			ServletRequest request, ServletResponse response)
			throws ServletException, IOException {
		String operator = request.getParameter("op");
		int v1 = Integer.parseInt(request.getParameter("v1"));
		int v2 = Integer.parseInt(request.getParameter("v2"));
		int result = 0;
		
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		switch (operator) {
		case "+": result = v1 + v2; break;
		case "-": result = v1 - v2; break;
		case "*": result = v1 * v2; break;
		case "/": 
			if (v2 == 0) {
				out.println("0 으로 나눌 수 없습니다!");
				return;
			}
			
			result = v1 / v2; 
			break;
		}
		
		out.println(v1 + " " + operator + " " + v2 + " = " + result);
	}

        @Override
        public void destroy() {}

        @Override
        public ServletConfig getServletConfig() {
            return this.config;
        }

        @Override
        public String getServletInfo() {
            return "calculator ver=1.0.0";
        }
}

위 코드는 사용자에게 간단한 수식을 입력받아 계산을 하여 화면에 출력해주는 서블릿이다. init 에서는 config 를 초기화 해주고 실제 처리하는 작업은 service() 객체를 통해 처리한다. 이때 서비스 객체는 요청시 함께 데이터를 담아오는 request 객체와 응답 할때 사용하는 responce 객체를 매개변수로 갖는다.

 

서블릿의 배치

서블릿 객체를 작성만 한다고 해서 웹에서 사용할 수 있는 건 아니다. 해당 서블릿을 웹에 배치해야 한다. 웹에 배치를 한다는 것은 서블릿을 선언하고 url을 매핑해주는 것을 의미한다. 그러면 해당 URL에 접속하면 서블릿을 통해 화면이 출력된다. 서블릿 배치 정보는 DD(Deployment Descriptor)파일인 web.xml에 작성하거나 @WebServelt 에노테이션 통해 작성하면된다.

 

(1) web.xml에 작성

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <display-name>web03</display-name>
  
  <!-- 서블릿을 정의 -->
  <servlet>
  	<servlet-name>Calculator</servlet-name>
  	<servlet-class>example.servlets.Calculator</servlet-class>
  </servlet>
  
  
  <!-- 서블릿과 URL을 연결 -->
  <servlet-mapping>
  	<servlet-name>Calculator</servlet-name>
  	<url-pattern>/calculator</url-pattern>
  </servlet-mapping>
  
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>

<servlet> 태그를 통해 서블릿을 선언하고, <servlet-mapping> 태그를 통해 url과 연결한다. 이때 url-pattern 안에 적힌 url은 상대경로이다. => http://(서버의 URL)/(프로젝트이름)/calculator (e.g. http://localhost:9999/practice/calculator)

 

(2) @WebServlet 에노테이션 

서블릿을 생성할때 마다 위의 작업을 해주기 번거로울 경우 @Servlet 에노테이션을 서블릿 구현 객체에 선언해주면 된다.

@WebServlet("/calculator")
public class CalculatorServlet implements Servlet {
	....
}

다음과 같이 에노테이션 안에 매핑할 URL을 적어주면 된다. 이 경우도 URL은 상대 경로이다.

 

인터페이스 구현의 불편함 : GenericServlet

기존의 서블릿 객체는 Servlet 인터페이스를 구현하는 방식을 통해 생성하였다. 이 방식은 인터페이스의 특성 상 Servlet 인터페이스의 모든 메서드를 반드시 오버라이딩 해야했다. 하지만 service() 메서드는 제외한 다른 메서드들의 경우 많은 상황에서 사용하지 않았고, 빈 메서드로 선언해야 했다. 이러한 비효율을 줄이기 위해 인터페이스를 구현하는 방식이 아닌 추상 클래스를 상속하여 서블릿 객체를 생성하는 방식인 GenericServlet이 등장하였다.

 

GenericServlet은 말 그대로 추상 클래스이므로 필요 없는 메서드는 구현할 필요가 없다. 예를 들어 내가 만들 서블릿이 service() 메서드만을 필요로 한다면 service() 메서드만 구현하면 그만이다. 알다시피 추상 클래스는 모든 메서드의 구현을 강제하지 않는다.

public class CalculatorServlet extends Servlet {

	@Override
	public void service(
			ServletRequest request, ServletResponse response)
			throws ServletException, IOException {
		String operator = request.getParameter("op");
		int v1 = Integer.parseInt(request.getParameter("v1"));
		int v2 = Integer.parseInt(request.getParameter("v2"));
		int result = 0;
		
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		switch (operator) {
		case "+": result = v1 + v2; break;
		case "-": result = v1 - v2; break;
		case "*": result = v1 * v2; break;
		case "/": 
			if (v2 == 0) {
				out.println("0 으로 나눌 수 없습니다!");
				return;
			}
			
			result = v1 / v2; 
			break;
		}
		
		out.println(v1 + " " + operator + " " + v2 + " = " + result);
	}
}

위 코드는 GenericServlet으로 수정한 Calculator 서블릿 객체이다. 기존의 코드의 경우 5개의 메서드를 모두 정의했고, 이를 위해 불필요한 config 필드값까지 선언해주어야만 했다. 하지만 GenericServlet을 상속하여 구현함으로써 service() 메서드만을 오버라이딩하고 불필요한 나머지 메서드는 작성하지 않았다.