Kim Jinung

Web server와 WAS 그리고 Servlet 본문

Web

Web server와 WAS 그리고 Servlet

Kim Jinung 2023. 4. 17. 17:10

1. Web server와 WAS(Web Application Server)

웹 서버와 WAS는 HTTP 프로토콜을 사용하여 통신하는 클라이언트-서버 아키텍처에서 서버의 역할을 담당한다. 웹 서버는 정적인 컨텐츠를 제공한다. 그리고 WAS는 동적인 컨텐츠를 제공할 수 있다.

 

  • 정적인 컨텐츠(Static content)는 고정된 형태의 컨텐츠다. 추가적인 가공없이 서버에서 컨텐츠를 그대로 제공한다. 서버에 요청이 들어왔을 때 HTML 파일을 그대로 반환하는 것이 그 예시다.
  • 동적인 컨텐츠(Dynamic content)는 사용자 요청에 따라, 서버는 DB 등에 접근하여 데이터를 가져와서 로직을 실행하고 그 결과를 제공하는 컨텐츠를 의미한다. 사용자가 ID와 Password를 가지고 웹 서버에 요청을 보냈을 때, 서버가 DB에서 회원 여부를 확인하는 로그인 서비스가 그 예시다.

최근에는 WAS에서 동적, 정적 컨텐츠를 모두 제공하기도 한다. 


2. Jakarta Servlet 

웹 애플리케이션에서 HTTP 요청 및 응답을 처리하는 자바 기반의 프로그램 컴포넌트다. 

그렇다면 서블릿은 왜 탄생하게 되었을까?

너무나도 당연한 말이겠지만 클라이언트-서버 구조 기반이면서 HTTP 프로토콜을 사용하는 웹 서버를 개발하기 위해서는 HTTP 프로토콜을 처리할 수 있어야 한다. 그런데 용도와 목적이 전혀 다른 웹 앱이라 할지라도 HTTP 프로토콜을 처리하는 부분은 동일하다. 어느 서버나 HTTP 요청을 받아서 헤더와 바디 메시지를 읽는다. 그리고 HTTP 메시지를 생성해서 응답하는 이 과정이 완전히 동일하다. 그런데 개발자는 이러한 공통 작업을 프로젝트마다 그리고 각 서비스마다 반복하게 된다. 서블릿은 이러한 반복되는 공통 작업을 대신해주는 컴포넌트다. 

 

Apache Tomcat

앞서 서블릿에 대해서, 반복되는 HTTP 요청 및 응답을 처리 해주는 자바 컴포넌트라고 정리했다. 한 가지 주의할 점이 있다. 자카르타 서블릿은 그 자체로 구현체를 제공하지 않는다. 대신 서블릿을 구현하기 위한 API 스펙을 제공한다. 여기서 의미하는 스펙이란 서블릿을 구현하기 위한 인터페이스와 클래스 등을 의미한다.

 

아파치 톰캣은 서블릿 컨테이너로, 자카르타 서블릿 API 스펙을 구현한 구현체다.  

 

그렇다면 서블릿 컨테이너는 무엇일까?

서블릿 컨테이너(웹 컨테이너)는 서블릿과 상호작용하는 웹 서버의 컴포넌트다. 간단히 말해서 서블릿의 생명주기를 관리하는 컴포넌트다. 

 

아래 그림은 클라이언트가 서버에 서비스를 요청했을 때 웹 서버에서 일어나는 일을 간단히 도식화 한 그림이다. 

https://docs.oracle.com/cd/B14099_19/web.1012/b14017/overview.htm

 

클라이언트가 웹 서버에 서비스를 요청하면 웹 리스너가 이를 감지한다. 그리고 이 요청은 서블릿 컨테이너로 넘어간다. 서블릿 컨테이너는 HTTP 메시지를 읽어서 유저가 어떠한 서비스를 요청했는지 확인한다. 예를 들어서 HTTP 헤더에서 클라이언트가 어떤 주소로 서비스를 요청했는지 확인한다. 해당 URL을 기반으로 적절한 서블릿을 매핑한다. 서블릿 컨테이너에 의해 매핑된 서블릿은 HTTP 메시지를 확인하여 비지니스 로직을 처리하고 서블릿 컨테이너에게 결과를 응답한다. 

 

위 과정에서 한 가지 주의할 점이 있다. 서블릿 컨테이너는 유저 요청에 따라 적절한 서블릿에 요청을 하는데, 이때 서블릿은 미리 생성되어 있는 것이 아니라, 필요에 따라 서블릿 인스턴스를 생성하고 비지니스 로직을 수행하게 된다. 그리고 할 일을 마친 서블릿 인스턴스는 서블릿 컨테이너에 의해 파괴된다. 이를 통해 서버의 자원을 효율적으로 관리한다. 

 

여태까지의 과정을 정리해보면, 아파치 톰캣(서블릿 컨테이너)는 서블릿 구현체를 제공한다. 개발자는 서블릿 구현체를 사용해서 비지니스 로직만 개발한다. 특정 주소로 들어오는 요청에 대해서 해당 서블릿이 그 요청에 응답할 것이라는 매핑에 관한 인터페이스도 제공한다. 덕분에 HTTP 요청을 처리하는 반복 작업이 제거된다. 개발자는 서블릿 구현체로부터 HTTP 요청 메시지를 받는다. 그리고 수행한 비지니스 로직의 결과를 다시 서블릿 구현체를 사용해서 반환한다. 서블릿 컨테이너는 응답을 확인하고, 일을 마친 서블릿 인스턴스를 파괴하여 서버의 자원을 효율적으로 사용한다. 즉 반복되는 HTTP 요청 및 응답에 관한 작업을 더이상 하지 않아도 된다.

 

서블릿 구현체를 사용하는 예시 코드

TestController(위위 그림에서 서블릿에 해당하는 것이 이 컨트롤러다.)는 HttpServlet 추상 클래스를 상속하고, WebServlet 어노테이션에는 "/test" URL을 매핑했다. 해당 URL에 대한 요청은 TestController 클래스가 응답하겠다고 서블릿 컨테이너에게 알려주는 것이다.

 

다음으로 TestController는 HttpServlet의 service 메서드를 오버라이드 했다. 서블릿 컨테이너는 server-address:/test URL로 들어오는 요청은 TestController의 service 메서드를 수행해서 응답할 것이다. 이것이 서블릿 컨테이터 - 서블릿의 약속이라고 할 수 있다. 서블릿은 인터페이스로 나의 비지니스 로직은 service로 제공할 것이라고 미리 약속한다. 개발자는 해당 메서드를 오버라이드 해서 구현하는 것이다. 해당 메서드는 HttpServletRequest와 HttpServletResponse를 파라미터로 받는다. HttpServletRequest를 이용해서 유저가 요청한 HTTP 메시지를 읽을 수 있다. 그리고 비지니스 로직을 수행한 결과를 HttpServletResponse에 넘겨준다. 이와 같이 서블릿 구현체를 넘겨받는 형태로 개발자는 반복되는 HTTP 처리 작업을 생략할 수 있다.

 

위 과정이 끝나면 서블릿 컨테이너는 HttpServletResponse 를 이용해서 응답한다. 그리고 서블릿 컨테이너는 서블릿 인스턴스를 파괴하여 자원을 회수한다. (HTTP 응답 메시지를 작성하는 것은 서블릿, 서블릿 컨테이너 두 레이어 모두에서 가능하다.)