자바는 웹 서비스를 서블릿(Servlet) 객체를 통해 구현한다.
서버로의 모든 웹 요청을 서블릿을 통해 받으며 그에 따른 결과를 서블릿을 통해 리턴한다.
도대체 이 서블릿이란 무엇일까?
서블렛이란?
서블릿이란, 자바를 사용하는 Request(클라이언트) - Response(서버) 모델에서 서버의 기능을 확장해주는 자바 클래스이다.
이 서블릿은 모든 종류의 요청에 응답할 수 있지만, 주로 웹 서버에서 호스팅 되는 어플리케이션의 기능을 확장하는데 사용된다.
- Java Docs
웹 상의 통신은 TCP/IP 네트워크 모델에 따라 TCP 소켓을 열어 아이피 및 포트를 바인딩을 하고, 후에 요청이 들어올때마다 새로운 환영 소켓을 만들어야 할 뿐만 아니라, 응답을 위해 Request의 IP 데이터그램에서 IP를 추출, 포트 추출하여 이를 재전송하는 작업까지 개발자가 전부 수행해야 한다. 이는 비단 복잡할 뿐만 아니라, 반복되는 코드로 인해 개발자의 생산성을 낮춘다. 이런 네트워크 하위계층에 대한 작업을 없애고 네트워크 최상위 계층에 대해서만 개발자가 작업할 수 있도록 구현한 것이 서블릿이다.
즉, 요청과 응답을 추상화하여 개발자가 TCP/IP 모델의 Application Layer에 대해서만 집중하게 해준거구나?
서블릿에서 요청 메시지로 부터 클라이언트의 IP주소, 포트 주소등의 정보를 얻을 수는 있지만, 응답 메시지에 대해 목적지 IP주소, 포트 주소를 수정할 수는 없다. 이는 후에 나오는 웹 서버 (Web Listener)가 연결을 관리하고 있기 떄문이다.
그럼 서블릿을 이용하기 전에, 먼저 요청을 어떻게 추상화하는지, 응답을 어떻게 추상화하는지 살펴볼 필요가 있겠다. 서블릿에게 이런 기능을 제공하는 서블릿 컨테이너에 대해 알아보자
서블릿 컨테이너란?
서블릿이 위와 같은 비즈니스 로작에만 집중할 수 있도록 구현할 수 있다는 건 소켓 생성, 포트 설정뿐만 아니라, 요청을 적절한 서블릿으로 매핑하는 것과 같은 복잡한 로직을 다른 객체가 수행한다는 얘기다. 이 역할 중 URI요청 매핑을 서블릿 컨테이너가 수행한다.
( 또한, 아래의 설명처럼 서블릿에는 static main 메소드가 없으므로 반드시 외부(서블릿 컨테이너)에서 실행되고 생명주기가 관리되록 구현되야 한다.)
Unlike a Java client program (but like an applet), a servlet has no static main() method.
Therefore, a servlet must execute under the control of an external container.
- Java Docs
따라서, 서블릿 컨테이너는 클라이언트의 Http 요청에 대해 서블릿들을 생성 및 실행 관리하며, 동시에 서블릿의 HTTP 요청 property(Http 헤더, Data Section)에 대한 접근을 용이하게 해준다.
- 서블릿으로의 클라이언트 Request 매핑
- 서블릿의 Response를 클라이언트에게 전달
자, 서블릿이 서블릿 컨테이너 안애 존재하며, 서블릿 컨테이너의 관리를 받는다는 사실을 알았다.
이제, HTTP 요청을 받은 서블릿 컨테이너가 서블릿을 어떻게 관리 하는지를 알아보자.
- 서블릿 컨테이너는 적절한 서블릿의 인스턴스를 init() 메소드를 통해 생성한다.
- 서블릿 컨테이너는 서블릿에 전달할 Request 객체를 생성한다.
- 서블릿 컨테이너는 서블릿이 응답할 Response 객체를 생성한다.
- 서블릿 컨테이너는 서블릿의 service() 메소드를 실행하고 위에서 생성한 객체를 파라미터로 전달한다.
서블릿 컨테이너는 적절한 타이밍에 이를 서블릿의 destroy()를 호출하여 가비지 컬렉터가 이를 수거할 수 있도록 한다.
( 하지만, 자주 사용되는 서블릿을 걔속해서 새로 인스턴스화하는 낭비하는 성능 이슈로, 일반적으로 호출되지 않는다. )
자, 서블릿 컨테이너가 서블릿들의 생명주기를 관리하며, 클라이언트의 요청을 적절한 서블렛으로 연결한다는 것을 배웠다.
하지만, Request는 TCP/IP 를 통해 전달된 패킷이다. 이 Request 패킷에서 Application 객체에 필요한 데이터를 추출하고, 또 Response를 적절한 네트워크 패킷으로 구성하는 것은 어떤 객체의 책임일까?
그 주체는, Web Listener 이다.
Web Listener?
아파치 톰캣, Jetty, 등등의 웹 서버가 이와 같은 예시이다.
일반적으로 클라이언트로부터 Http 요청이 왔을때, 이에 대한 응답을 위해 TCP 세션을 유지하는 역할을 수행한다.
요청과 응답 사이에서 웹 리스너는 서블릿 컨테이너에게 요청 메시지를 전달하고, 컨테이너로부터 응답 메시지를 받아 이를 클라이언테에게 전달하고, 세션을 종료한다.
웹 요청에 따른 동작
예제
아래 코드는 서블렛 컨테이너를 구성하고 이에 서블릿을 추가하여 웹에서의 요청을 처리하는 코드 입니다.
public class DemoApplication {
public static void main(String[] args) {
ServletWebServerFactory factory = new TomcatServletWebServerFactory();
WebServer webServer = factory.getWebServer(servletContext -> {
servletContext.addServlet("greeting", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet Runned");
if (req.getMethod().equals(HttpMethod.GET.name()) && req.getRequestURI().equals("/greetings/goaway")) {
resp.setStatus(HttpStatus.OK.value());
resp.setContentType(MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().println("Go Away!");
return;
}
if (req.getMethod().equals(HttpMethod.GET.name()) && req.getRequestURI().equals("/greetings/hello")) {
resp.setStatus(HttpStatus.OK.value());
resp.setContentType(MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().println("Hello!");
return;
}
resp.setStatus(HttpStatus.FORBIDDEN.value());
resp.getWriter().println("Its FORBIDDEN ACCESS");
}
}).addMapping("/greetings/*");
});
webServer.start();
}
}
위와 같이 서블릿을 컨테이너에 추가할때, URI 주소를 매핑하여,
해당 URI에 대한 요청이 들어왔을떄 적절한 서블릿으로 매핑되도록 합니다.
'SpringBoot' 카테고리의 다른 글
[SpringBoot] 스프링의 에러처리 탐구 (0) | 2024.01.17 |
---|---|
[Spring] Spring Security 인증 구성 (0) | 2024.01.02 |
[Spring] 다수의 SecurityFilterChain 구성 방법 (0) | 2024.01.01 |
[Spring] Spring Security Architecture (1) | 2023.12.29 |
[Spring] RestTemplate (1) | 2023.12.29 |