개인공부/Java

서블릿(Servlet)이 뭐지? (CGI, Servlet, Tomcat)

양을 좋아하는 문씨 2025. 2. 20. 02:17

Java로 구현된 (개선된 형태의)CGI 이다.

Java, JSP, 스프링부트를 순서대로 학습하면서 Servlet 이라는 기술에 대해 많이 보기도 했고 듣기도 했지만 정리가 안되고 있었다. 정리를 안하고 넘어가면 찝찝해서 정리를 해보고자 한다.


먼저 CGI에 대해 알아보자. 왜냐하면 Servlet은 CGI의 단점을 개선한 방식이기 때문이다.

* CGI란?

- Common Gateway Interface의 약자이다. 말 그대로 인터페이스이다.

  • 가령 자바 프로그램을 개발했다고 하고, 이를 통해 비즈니스 로직을 처리한다고 하자. 그러면 자바 프로그램은 자바 프로그램대로 따로 돌고 있고, 웹에서는 HTTP 프로토콜로 따로 도는 환경이다. 이를 연결하려면 웹 환경에 맞게 동시성 처리와 관련된 작업도 해줘야 하고, 웹에서 HTTP 프로토콜에 맞게 날아오는 요청을 문자열로 변환해주는 작업도 해야 한다. 이것들이 어려운 작업은 아닌데 이런 자잘한 작업을 하기에는 너무 번거롭다. 어쨌든 이러한 역할을 하는 인터페이스가 CGI이다.
  • 어쨌든 인터페이스이기 때문에 내가 직접 Java로 작성해서 쓴다고 하면 그것도 CGI가 되는것임.
  • WS가 요청을 받음 → CGI가 받음 (Java 프로그램) → 백엔드 서버가 받음 (Java 프로그램) → CGI → WS
  • 참고로 HTTP 프로토콜은 문자열 기반으로 돌아간다. 그러니까 CGI가 요청을 받고 처리하는 과정들을 문자열 파싱 작업이라고 볼 수도 있다.

CGI는 위와 같이 일종의 기술인 것이다. 그런데 문제는 CGI가 뭔가를 처리할 때마다 프로세스가 새로 생겨나서 처리하는 방식이다. Java 프로그램이 매번 새로 실행되는 것이다. 동시 사용자가 늘어나면 CGI 프로그램이 여러 개가 한꺼번에 돈다는건데, 지금도 버거운 일이고 예전같았으면 상상할 수 없는 일이다.

 

굳이 저걸 멀티프로세스로 처리해야 할까?


 

그래서 멀티쓰레드로 처리하는 Servlet이 나왔다.

* Servlet 이란?

  • Java로 구현된 웹 요청 처리 방식인데, 정확히는 Servlet 클래스를 상속받아 구현된 HttpServlet 클래스의 인스턴스이다. CGI의 단점을 개선하고자 만들어졌다.
  • 서블릿은 자신이 활동할 Servlet Container가 필요하다. Servlet Container가 클라이언트 요청에 따라 서블릿을 생성하고 파괴하는 서블릿의 전체 생명주기를 관리한다.
  • 이 Servlet Container는 하나의 프로세스이고, 서블릿은 싱글톤 방식으로 관리된다. 요청이 올 때마다 서블릿 인스턴스를 생성하는건 비효율적이니까 하나를 만들어두고 여러 쓰레드가 활용하게 된다.
  • 이때 쓰레드를 매 순간마다 상황에 맞게 만들고 없애는 것도 비효율적이다. Servlet Container는 쓰레드를 미리 만들어놓고 쓰레드 풀에 대기시킨다.
  • 요청이 올 때마다 스레드가 꺼내지면서 여러 요청들을 동시에 처리한다.

Servlet이 클래스로부터 생성되는 인스턴스이고, Servlet Container가 전체 생명주기를 관리하는 것 까지는 알겠다. 그러면 이제 이 Servlet과 Servlet Container만 가지고 클라이언트와 웹 통신을 할 수 있는걸까? 그러려면 필요한게 더 있다.

Servlet은 단독으로는 요청을 받을 수 없다. Servlet 자체는 어떤 요청을 받아 특정하게 처리하는 객체일 뿐이다. 또한 Servlet Container도 요청을 직접 받는 기능이 없다. Servlet과 Servlet Container가 세트로 해서 CGI의 개선 버전인 만큼, 본질은 결국 인터페이스인 것이다.

즉, HTTP 소켓을 열고, 요청을 받을 엔드포인트를 설정해줄 웹 서버가 따로 필요하다.


그래서 우리가 사용하는 Tomcat에는 그런 기능들이 모두 포함되어있다.

* Tomcat의 구조.

  • Tomcat은 Java기반의 WAS이다.
  • 톰캣 내부는 아래와 같이 여러 컴포넌트들이 중첩된 구조를 띈다.
Server  
 ├── Service  
 │   ├── Connector (HTTP, AJP 등)  
 │   └── Engine (서블릿 엔진)  
 │       ├── Host (가상 호스트)  
 │       └── Context (웹 애플리케이션)
  • 위의 그림을 보면, 최상위에는 톰캣 그 자체인 Server가 있다.
  • 그리고 하위에 Service가 있는데, 얘는 엔진과 커넥터들을 묶는 컴포넌트다.
  • 커넥터도 자바 객체인데, 아까 Servlet 부분에서 짚고 넘어갔던 HTTP 소켓을 열고 HTTP 요청을 처리하는 역할을 한다. HTTP 형식의 요청이 오면 Tomcat Request 객체로 변환하고, Servlet Container에 전달한다.
  • 엔진은 서블릿 컨테이너의 조건을 만족하는 어떤 컴포넌트이다. 별다른 설정을 안하면 기본적으로 Catalina라는 엔진이 설정되어있는데, 이 카탈리나라는 서블릿 엔진이 서블릿을 호스팅한다.
  • host는 말 그대로 호스트인데, 톰캣 내부에서 구분되는 가상호스트이다. 필요에 따라 여러 가상호스트를 추가할 수 있고, 요청이 오면  해당하는 호스트로 간다.
  • context는 war로 배포된 어플리케이션이다. 여기의 설정에서 루트 폴더 위치를 엔진과 호스트에 알려줘야 한다.
  • 이 모든 기능들은 JVM 위에서 돌아간다. 이것들이 모두 포함된 어플리케이션이 Tomcat이고, 얘는 하나의 JVM에서 독립적으로 실행된다.

이제 전체 동작 순서를 살펴보면 다음과 같다.

  1. Tomcat의 Http11NioProtocol이 HTTP 요청을 받음
    • 여기서 HTTP 요청을 처리하는 소켓을 관리하는 역할을 하는 게 Coyote이다.
    • 예를 들어 클라이언트가 유저들을 조회하기 위해 어떤 요청을 보낸다고 하면, /api/users 요청을 보낼 것이고 Coyote가 이를 받는다.
    • 이 HTTP 패킷을 파싱하여 HttpServletRequest 객체로 변환한다.
  2. Coyote가 Catalina(서블릿 컨테이너)에 요청 전달
    • Coyote는 자기가 처리한 HTTP 요청을 Catalina라는 서블릿 컨테이너에 전달한다.
  3. Catalina가 요청을 서블릿으로 매핑 
    • web.xml 혹은 어노테이션을 기준으로 요청 URL에 해당하는 서블릿을 결정한다.
    • 이때 그 서블릿을 최초로 쓰는 요청이었다면 서블릿 객체를 생성하고 init 메서드를 호출한다.
    • 필터링 과정은 서블릿을 처리하기 전에 실행되지만 init 메서드는 필터 이전에 호출된다.
  4. ApplicationFilterChain의 필터링 과정
    • Catalina 내부의 ApplicationFilterChain이 요청을 필터링한다. 여기서 필터는 요청과 응답을 전처리하거나 후처리하는 역할을 수행할 수 있다. 이 과정을 통해 서블릿이 실행되기 전에 요청을 수정하거나 보안 처리를 추가로 할 수 있다.
  5. Servlet 실행
    • 필터체인 다 통과하고 나면 서블릿 컨테이너에서는 해당 요청을 처리할 서블릿을 찾아 service 메서드를 호출한다. 그리고 service 메서드는 HTTP 메서드에 대응되는 doGet 메서드나 doPost 메서드를 호출한다.
    • 그 결과로 동적 페이지가 생성되고, 그 결과를  HttpServletResponse 객체에 담고 클라이언트에게 반환한다.
  6. 응답 처리했으면 HttpServletRequest, HttpServletResponse는 서블릿 컨테이너에 의해 소멸된다.