1. CGI (Common Gateway Interface)

World Wide Web(WWW)은 초기에는 데이터가 동적으로 변하는 것에 대한 생각은 하지 않아 정적인 페이지 위주로 개발이 되었다. 시간이 흘러 웹은 점점 더 많은 데이터를 보여주도록 변하였고, 사용자가 필요에 따라 바로바로 데이터를 보여주는 동적인 페이지에 대한 수요가 늘었다. 동적인 페이지의 예제는 요즘은 흔히 찾아볼 수 있다. 웹 화면 사이드에 여러 광고가 보인다거나, 게시글이 보여지고 내가 보고 싶은 게시글을 검색한다거나, 댓글을 다는 등의 행동은 모두 동적인 페이지에서 이루어진다. 이런 동적인 페이지를 만들기 위해 초기에 CGI라는 일종의 규약을 만들었다. CGI는 동적인 페이지를 만드는 데 필요한 언어나 도구를 정의한 것이 아니라 동적인 페이지를 주고받는 규약이다. CGI에서 정의한 스팩이 있고, 이를 따라 구현된 프로그램은 CGI 프로그램이라고 부른다. CGI의 규칙은 간단하다.

“정의한 URL로 요청이 오면, CGI 프로그램을 실행시켜 이 요청을 처리하고 그에 대한 응답 데이터를 반환다.”

이 규칙을 다음과 같이 세 가지로 정리할 수 있다.

  1. 메타데이터를 정의하였는가?
  2. 요청 방식을 정의하였는가?
  3. 요청에 대한 응답 방식을 정의하였는가?

위 세 가지를 정의하고 구현한 프로그램은 CGI 프로그램이다.

CGI에 대해 알아보다 보면, 이를 구현하는 방식에 대해 서술하고 이를 단점이라고 말한다. 대표적으로 CGI 프로그램은 요청이 들어오면 새로운 ‘프로세스’를 생성하므로, 이 새로운 프로세스를 만드는 것에 대한 비용이 크기 때문에 단점이다. 그래서 현재에는 이를 대신하는 다른 기술을 사용한다고 한다.

이는 CGI를 규약이 아닌 CGI가 나온 초기에 만들어진 CGI 프로그램에 대한 단점으로 보여진다. CGI는 구현 방식을 정의하지 않고 이는 사용자에게 맡기고 동적인 데이터를 주고 받는 것에 대한 규칙만을 설명한다. 위에 단점으로 말한 것은 예전에는 CGI 프로그램을 대부분 C 언어로 개발하였다. 그리고 그 당시 구현 방식은 C 언어로 새로운 프로세스를 생성하여 그 프로세스에 CGI 프로그램을 실행시켰다. 이에 대한 단점을 CGI 단점이라고 오해를 한 것으로 보인다. 아래 링크에서 CGI에 대한 설명을 보면 정의는 규약에 대한 것이며 단순하다.

참고로 https://kldp.org/node/73386 이 링크에서 댓글 토론을 보면서 CGI에 대한 이해도를 많이 높일 수 있었다.

자바는 CGI 프로그램으로 Servlet(서블릿)이라는 이름으로 만들었다.

서블릿에 대해서는 아래에 바로 자세히 다루겠지만, 서블릿의 정의를 찾아보면 ‘CGI 프로그램으로 자바에서 서블릿을 만들었다.’ 라는 말은 사실 찾아볼 수 없다.

위는 개인적으로 CGI와 서블릿에 대해 각각 정의와 역할을 보았을 때, 서블릿은 CGI 프로그램으로 볼 수 있다고 정리를 했다. 대부분 책과 글에서는 CGI와 서블릿이 비슷할 뿐 ‘다른 것’이라고 말한다. 그 이유는 CGI는 프로세스를 새로 만들어 처리한다고 해서 이를 발전시킨 것이 서블릿이라고 말한다. 하지만 이는 위에서 설명했던 CGI에 대한 오해에서 시작한 것으로 보인다.

2. Servlet (Jakarta Servlet / 전 Java Servlet, 서블릿)

서블릿은 CGI 프로그램 중 자바에서 구현한 것이라고 말했다. 좀 더 정확한 정의를 찾아보면, 오라클에서는 다음과 같이 정의하였다.

“A servlet is a Java programming language class that is used to extend the capabilities of servers that host applications accessed by means of a request-response programming model. Although servlets can respond to any type of request, they are commonly used to extend the applications hosted by web servers. For such applications, Java Servlet technology defines HTTP-specific servlet classes.”

-https://docs.oracle.com/javaee/5/tutorial/doc/bnafe.html

서블릿은 요청-응답 모델에서 호스팅 애플리케이션(원격의 요청을 처리하는 애플리케이션) 역할을 하는 서버를 자바 프로그래밍으로 구현된 클래스이다. 서블릿은 모든 요청에 대한 응답을 처리할 수 있지만, 대부분 웹(HTTP) 요청을 처리하는 호스팅 애플리케이션으로 사용한다. 이러한 경우 자바 서블릿은 HTTP 요청을 처리하는 서블릿 클래스로 정의한다.

서블릿은 모든 요청을 처리할 수 있다는 점에서 정적인 페이지와 동적인 페이지 모두 처리할 수 있다. 하지만 대부분 구현의 복잡도는 동적인 페이지를 구현하는 데 치중되어 있어 동적인 페이지를 만들기 위해서 대부분 사용한다. 이러한 점을 보았을 때, 서블릿은 CGI 프로그램 역할을 한다고 볼 수 있다.

서블릿은 요청에 대해 필요한 로직을 처리하고 이에 대한 응답 데이터를 만들어 응답한다. 만약 서로 다른 요청이 여러 개라면, 각각 이를 처리하는 서블릿이 필요하다. 그리고 구현된 서블릿을 관리하는 것이 필요하다. URL로 요청이 왔을 때, 이 URL이 어떤 서블릿에서 처리해야 할지와 그 서블릿에 대한 생성 및 관리(라이프사이클)가 필요하다.

이와 같은 서블릿을 관리하는 역할은 웹 컨테이너에서 수행한다. 그래서 서블릿을 배포하고 실행하려면 이 웹 컨테이너가 필수로 필요하다. 그러다보니 이를 서블릿 컨테이너라고도 부른다. 웹 컨테이너의 대표적인 예는 톰캣이 있다.

웹 컨테이너는 대표적으로 다음과 같은 3가지 역할을 수행한다.

  • 서블릿의 라이프사이클을 관리한다.
  • 요청 URL과 이를 처리하는 서블릿을 매핑한다.
  • 요청한 클라이언트가 접근 권한이 있는지 확인한다.

web-container

위는 웹 컨테이너와 서블릿의 모습이다. 웹 요청이 왔을 때, 웹 컨테이너가 이를 받아서 어떤 서블릿에서 수행해야 할지 선택한다. 그리고 정의된 라이프사이클을 수행하여 웹 요청을 처리하고 서블릿을 관리한다.

서블릿의 라이프사이클은 총 3가지이고, 다음과 같다.

  • init(): 서블릿에 필요한 설정 및 리소스 초기화를 담당한다.
  • service(): 요청에 대한 처리
  • destory(): 현재 상태를 저장하고, 서블릿을 종료하기 위한 리소스 정리

서블릿의 라이프사이클을 바탕으로 웹 컨테이너는 클라이언트로부터 요청을 받았을 때, 다음과 같은 시나리오로 동작한다.

  1. 클라이언트가 요청한 URL을 바탕으로 해당 요청을 처리할 서블릿을 매핑한다.
  2. 웹 컨테이너가 수행되고 처음 받은 요청인 경우(= 해당 서블릿이 처음 메모리에 올라간 경우), 서블릿 초기화를 수행한다.
    1. 해당 서블릿 클래스 정보를 가져온다.
    2. 메모리에 객체 인스턴스화를 수행한다.
    3. init() 메서드를 호출하여 필요한 초기화 과정을 수행한다.
  3. service() 메서드를 호출하여 요청을 처리하여 응답 데이터를 만들고 반환한다.
  4. 만약 웹 컨테이너에서 해당 서블릿을 종료하기로 결정한 경우, destroy() 메서드를 호출하여 서블릿 종료 과정을 수행한다.

서블릿 예제 코드 (위키 코드 참고: https://en.wikipedia.org/wiki/Jakarta_Servlet)

import java.io.IOException;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class ServletLifeCycleExample extends HttpServlet {
    private Integer sharedCounter;

    @Override
    public void init(final ServletConfig config) throws ServletException {
        super.init(config);
        getServletContext().log("init() called");
        sharedCounter = 0;
    }

    @Override
    protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        getServletContext().log("service() called");
        int localCounter;
        synchronized (sharedCounter) {
            sharedCounter++;
            localCounter = sharedCounter;
        }
        response.getWriter().write("Incrementing the count to " + localCounter);  // accessing a local variable

        // retrun html
        // response.getWriter().write("<html><body>Result!</body></html>");
    }

    @Override
    public void destroy() {
        getServletContext().log("destroy() called");
    }
}

Servlet 코드를 살펴보면, service() 메서드에서 HTML 결과를 만들어주고 있다. 자바 코드 내에 HTML 코드를 문자열로 관리하고 있는 것이다. 이는 관리면에서 매우 불편함을 주었다. 이를 해결해주기 위해 JSP가 나왔다.

3. JSP (Jakarta Server Pages /전 JavaServer Pages)

JSP는 HTML, XML 등 기반의 동적인 페이지를 만들어주는 도구이다. 위에서 설명했듯이, 서블릿을 사용해서 자바 코드 내에 HTML과 같은 뷰 페이지를 개발했던 것을 좀 더 편리하게 하기 위함이다. 실제로 JSP는 컴파일 이후에 실행가능한 서블릿 코드로 변경되어 사용된다.

JSP는 서블릿과는 반대로 HTML과 같은 뷰 페이지 내에 자바 코드를 표현할 수 있는 문법으로 동작한다. 아래 예제를 간단히 살펴보자.

<table>    
   <tr align="center">
       <td id=title>아이디</td>
       <td id=title>비밀번호</td>
       <td id=title>이름</td>
       <td id=title>성별</td>
       <td id=title>생년월일</td>
       <td id=title>이메일</td>
       <td id=title>전화</td>
       <td id=title>주소</td>
       <td id=title>가입일</td>
   </tr>

<%
   for( MemberBean member : memberList){
%>            
   <tr>
       <td><%=member.getId() %></td>
       <td><%=member.getPassword() %></td>
       <td><%=member.getName() %></td>
       <td><%=member.getGender() %></td>
       <td><%=member.getBirth() %></td>
       <td><%=member.getMail() %></td>
       <td><%=member.getPhone() %></td>
       <td><%=member.getAddress() %></td>
       <td><%=member.getReg() %></td>
   </tr>
<%} %>    
</table>

위는 서버에서 가져온 회원 정보 리스트를 HTML로 표현한 모습이다.

자세한 문법은 https://docs.oracle.com/cd/B14099_19/web.1012/b14014/genlovw.htm 이 링크를 참고할 수 있다.

JSP Model 1 Architecture

JSP 모델 1 아키텍처는 JSP가 나온 초기의 아키텍처이다.

이 아키텍처를 살펴보기 전에 웹 요청을 처리하는 데 일반적으로 사용하는 한 패턴을 살펴보자. 바로 MVC 패턴이다. 이는 웹을 처리하는 데 필요한 역할을 다음과 같이 각각 3가지로 나눈 것이다.

  • Model: 요청을 처리하기 위한 로직 (비즈니스 로직)
  • View: 요청의 결과를 보여주는 응답 화면
  • Contoller: 요청이 들어오면 이를 받아서 모델과 뷰를 사용하여 응답하기까지 흐름을 제어하는 역할

JSP 모델 1 아키텍처는 JSP 내에서 뷰와 컨트롤러 역할을 하였다. 그리고 모델은 단순히 자바 객체를 활용하여 구현하여 JSP에서 이를 가져다 쓰는 형식이다. (물론, 모델 역할까지 JSP에서도 할 수 있다.) 이를 그림으로 표현하면 다음과 같다.

jsp-model-1

  • JSP: View + Controller 역할
  • JavaBeans (자바 빈 객체): 데이터 접근 + Model 역할

JSP 모델 1 아키텍처는 하나의 JSP에 대부분의 역할을 맡기기 때문에 구현이 매우 단순하다. 그래서 요청이 복잡하지 않은 경우에는 빠르게 구현할 수 있는 장점이 있다. 하지만, 늘그렇듯 하나에 모든 것이 담겨있으면 기능이 커질수록 복잡도가 올라간다. 그래서 요청이 복잡해지고 할 일이 많아지면 관리하기가 매우 힘들어진다는 단점이 있다.

이를 해결하기 위해서 모델 2 아키텍처가 나왔다.

JSP Model 2 Architecture

JSP 모델 2 아키텍터는 모델 1에서 문제가 되었던 JSP의 복잡도가 높아지는 것을 해결하였다. JSP가 갖고 있던 역할을 분리한 것이다. JSP는 View 역할만 하고, Controller의 역할은 서블릿에게 맡기는 것이다. 아래 그림을 보자.

jsp-model-2

Controller 역할을 서블릿에게 위임하여 분리하여 JSP가 갖고 있던 복잡도를 줄여 관리하기가 좀 더 용이해졌다.

이 구조부터 MVC 각각의 역할을 나누었기 때문에 현재 흔히 말하는 MVC 패턴의 역할과 같다. JSP 모델 2 아키텍처를 기반으로 나온 프레임워크가 Apache Struts이다.

스프링 프레임워크가 나온 초기에는 웹을 개발할 때 스프링 프레임워크 기반에 Spring MVC 대신 Apache Struts를 대부분 사용했다고 한다.

그 외 Java View Template

JSP 모델 2 아키텍처에서 JSP는 온전히 View 역할에 집중하게 된다. JSP 이 외에도 Java에서는 View 역할을 해주는 여럿 템플릿이 존재한다.

Spring MVC (with. Front Controller Pattern)

Spring MVC는 스프링 프레임워크에서 제공하는 MVC 패턴으로 정식 명칭은 ‘Spring Web MVC’이다. 이름에서 알 수 있듯이, 웹 요청을 처리하는 MVC 패턴으로 구현되어 있고, 편리하게 웹 요청을 처리할 수 있게 도와준다.

Spring Web MVC의 가장 큰 특징은 Front Controller Pattern을 도입한 MVC 패턴이다. 프론트 컨트롤러는 여러 곳에서 공통으로 사용하는 로직을 앞단의 컨트롤러로 분리하여 처리하여, 관리 및 개발 편리성을 높여준다. Spring Web MVC에서는 프론트 컨트롤러로 DispatcherServlet 클래스를 구현하였다.

DispatcherServlet의 역할을 대표적으로 다음과 같다.

  • 모든 요청을 받아서 처리할 핸들러를 찾아서 매핑한다. (URL 기반)
  • 공통적인 인증, 보안, 다국어 처리 등을 한다.
  • 공통적인 응답 데이터(or 뷰)를 처리한다.

여기서 핸들러는 스프링 프레임워크에서 HandlerAdapter 인터페이스를 구현한 클래스입니다. 이는 여러 방법으로 스프링에 등록할 수 있는데, 최근에는 AnnotationMethodHandlerAdapter 이 방식을 기본적으로 사용합니다. 이는 @ReqeustMapping 애노테이션을 메서드에 붙여서, 해당 매서드에 웹 요청을 처리하는 로직을 선언하는 방법입니다.

개인적인 생각으로 DispatcherServlet은 모든 요청 받아 URL 기반으로 처리할 핸들러를 찾아서 매핑해주는데, 이는 웹 컨테이너에서 하던 서블릿을 매핑하는 역할과 같다. 스프링 사용 전에는 웹 요청 처리를 서블릿이 담당했는데, 이를 스프링에서는 핸들러로 대체되었고, 그러면서 이 핸들러를 찾아주는 웹 컨테이너와 같은 기능이 필요했던 것으로 보인다. 이를 프론트 컨트롤러인 DispatcherServlet 에게 맡긴 것이다.

spring-web-mvc

Spring Boot

  • 스프링 부트는 웹 컨테이너에 대해 신경쓰지 않고, 그 외에도 각종 스프링 설정을 자동으로 편리하게 설정할 수 있도록 도와준다.
  • 톰캣(웹 컨테이너)를 내장하고 있다.
  • 토비님 강의 모두 듣고 정리 예정