[스프링/Spring] 서블릿(Servlet)과 서블릿 컨테이너
kindof
·2021. 11. 3. 15:53
0. 들어가면서
자바 웹 기술 역사를 살펴보면 서블릿은 JSP보다도 이전에 생긴 근본적인 개념입니다.
아래에서 서블릿에 대해 자세히 기술하겠지만, 서블릿을 공부하다보면 그 당시 개발자들이 서블릿'만'으로 개발을 하면서 얼마나 고통받았을지 안타깝기도 합니다.
아무튼 서블릿에 대해 공부한다는 것은 현재 스프링 MVC 패턴의 필요성과 MVC 패턴이 서블릿을 활용하여 어떻게 수많은 기술적 어려움을 쉽게 풀어냈는지를 이해하는 데 도움이 되리라 생각합니다.
1. 서블릿(Servlet)
자바 서블릿(Java Servlet)은 자바를 사용하여 웹페이지를 동적으로 생성하는 서버측 프로그램을 말하는데요.
쉽게 이야기하면, 클라이언트가 서버에 요청을 보낼 때와 응답을 받을 때 필요한 HTTP 작업을 도와주는 녀석이 서블릿입니다.
그리고 '동적으로'라고 말할 수 있는 것은 스프링에서 Servlet을 사용할 때 HttpServlet을 상속받아 사용하는데, 이 때 HTTP request와 response 안에 Header, Body 등의 내용을 입력할 수 있기 때문입니다.
아래 예시를 보겠습니다.
<form action="/save" method = "post">
<input type = "text" name="username" />
<input type = "text" name="password" />
<button type = "submit">전송</button>
</form>
위의 폼은 회원의 이름과 비밀번호를 받아서 save(저장) 요청을 수행하는 로직을 수행할 수 있게 하는 하는데요.
이 폼을 이용해서 사용자가 실제로 HTTP Request를 보내면 어떤 일이 일어날까요?
1. 우선 처음에는 웹 브라우저가 생성한 요청 HTTP 메시지가 아래처럼 생성됩니다.
POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username = "sunghyeon"&password="1234"
2. 그리고 서버는 웹 브라우저가 생성한 요청 메시지를 받아서 아래 작업들을 처리해야 하는데요.
- 서버 TCP/IP 연결 대기, 소켓 연결
- HTTP 요청 메시지를 파싱해서 읽기
- POST 방식, /save URL 인지
- Content-Type 확인
- HTTP 메시지 바디 내용 파싱
- 저장 프로세스 실행
- 비즈니스 로직 실행 → 데이터베이스에 저장 요청
- HTTP 응답 메시지 생성
- TCP/IP에 응답 전달, 소켓 종료
그런데 우리가 실제로 개발을 하는 것을 생각해보면 형광색으로 표시한 비즈니스 로직을 작성하는 것 외의 서버 수행해야 하는 여러가지 일들을 일일이 처리한 적이 없었다는 것을 알 수 있습니다.
바로 이 부분을 서블릿이 처리해주기 때문이죠.
따라서, 서블릿을 지원하는 WAS를 사용함으로써 개발자는 의미있는 비즈니스 로직에 중심을 두게 되고 서블릿은 HttpServletRequest, HttpServletResponse를 통해 HTTP 통신에서 필요한 여러가지 작업을 하게 됩니다.
즉, HTTP 요청시 WAS는 Request, Response 객체를 새로 만들어서 서블릿 객체를 호출하고 개발자는 Request 객체에서 HTTP 요청 정보를 편리하게 꺼내서 사용하며 Response 객체에 HTTP 응답 정보를 편리하게 입력할 수 있게 되었습니다.
간단하게 아래 예시를 보겠습니다.
위 코드는 서블릿의 HttpServletRequest를 이용해 Request 객체의 Body 정보를 읽는 코드입니다.
서블릿 객체는 urlPatterns에 매핑되는 URL로 요청이 올 때 service() 메서드를 호출합니다.
그러면 위 코드와 같이 ServletInputStream을 통해 Body의 JSON 데이터를 읽을 수 있는 것이죠.
Postman을 통해 아래 그림처럼 POST 요청을 보내보겠습니다.
그러면 아래와 같이 실제로 요청을 보낸 Body의 JSON 데이터를 스트링 형태로 읽어오는 것을 확인하실 수 있습니다.
다음으로 서블릿 객체의 Response 객체에 HTTP 응답 정보를 입력하는 예시를 보겠습니다.
마찬가지로 포스트맨을 통해 해당 urlPattern에 POST 요청을 보내보겠습니다.
그러면 아래와 같이 HTML이 화면에 표시되게 되고, Content-Type과 CharacterEncoding이 우리가 입력한대로 들어가있음을 볼 수 있습니다.
앞서 말했듯, 서블릿을 통해 개발자는 편리하게 비즈니스 로직을 수행하고 request, response를 입력하고 수정할 수 있습니다.
하지만 스프링을 조금이라도 써 보신 분들에게는 위와 같이 request, response를 입력하고 꺼내는 것과 HTML을 자바 코드로 작성한다는 사실이 쉽지만은 않아보이는 것 같습니다.
이런 이유로 JSP가 등장하게 되었고, 서블릿을 더 간편하고 효율적으로 활용하기 위한 스프링 MVC 프레임워크의 DispatcherServlet이 등장하게 된 것이죠.
한편, 서블릿 객체들을 관리하는 컨테이너를 서블릿 컨테이너(Servlet Container)라고 하는데요. 톰캣처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 합니다.
서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리하고 서블릿 객체들을 싱글톤으로 관리하게 해줍니다.
그리고 사용자 100명, 1000명, 10000명이 서버에 요청을 보내도 그 많은 동시요청들을 처리할 수 있게 해주는 멀티 쓰레드 처리를 지원하는데요.
이 멀티쓰레드 처리의 지원에 관한 내용이 WAS의 핵심적인 기능이기 때문에 아래에서 좀 더 자세히 살펴보겠습니다.
2. 서블릿 컨테이너와 멀티쓰레드
아래와 같이 한 명의 클라이언트가 서버에 HTTP 요청을 하는 상황을 생각해보겠습니다.
이 때, 클라이언트가 보낸 요청에 대해 서버에서 연결을 받고, WAS(서블릿 컨테이너)에서는 Servlet 객체를 호출하여 HTTP 요청을 처리합니다.
그런데 여기서 서블릿 객체를 호출하는 주체는 누구일까요?
서블릿 컨테이너에서 서블릿 객체를 호출하는 역할을 하는 녀석이 바로 쓰레드입니다.
쓰레드는 애플리케이션 코드를 하나하나 순차적으로 실행하는 작업을 하는데요.
자바 Main 메서드를 실행하면 main이라는 쓰레드가 실행되고, 동시 처리가 필요하면 쓰레드를 추가로 생성해서 처리를 하게 됩니다.
따라서, 동시에 여러 클라이언트가 서버에 요청을 보내게 되면 여러 개의 쓰레드를 생성하고 각 쓰레드는 서블릿 객체를 호출하여 작업을 처리하는 것이죠.
하지만 모든 요청마다 쓰레드를 생성하게 되면 동시 요청은 처리할 수 있지만, 쓰레드의 생성 비용이 많이 드는 단점이 존재하고 쓰레드의 컨텍스트 스위칭(Contetxt Switching) 비용이 발생할 수 있습니다(물론 멀티 프로세스보다는 덜 하겠지만요).
그리고 너무나 많은 요청이 들어오면 쓰레드를 무한정 생성해야 하기 때문에 CPU의 한계를 넘어서서 서버가 다운될 수 있는 상황까지 초래하게 되죠.
그래서 WAS는 아래처럼 쓰레드 풀(Thread Pool)이라는 개념을 사용합니다.
쓰레드 풀은 처음에 일정한 수의 쓰레드를 미리 생성해놓고 클라이언트의 요청이 오면 쓰레드풀 안에 있는 쓰레드를 할당하고 쓰레드가 클라이언트의 요청을 처리하면 해당 쓰레드를 다시 반납받습니다.
톰캣은 최대 200개의 쓰레드를 쓰레드 풀에 생성하는데요. 이런식으로 쓰레드를 미리 생성해놓음으로써 쓰레드를 생성하고 종료하는 비용이 절약되고, 응답 시간이 빨라질 수 있는 장점을 얻을 수 있습니다.
한편, 쓰레드가 모두 사용 중인 상황에서는 기다리는 요청에 대해 특정 숫자만큼만 대기하도록 설정할 수 있도록 만들수도 있죠.
결국, WAS(서블릿 컨테이너)의 핵심 기능은 멀티 쓰레드 지원을 통한 동시 요청 처리를 해준다는 데 있습니다.
개발자가 멀티 쓰레드 관련 코드를 일일이 작성하지 않고 설정을 통해 멀티 쓰레드를 사용할 수 있기 때문에, 마치 싱글 쓰레드 프로그래밍을 하듯이 편리하게 비즈니스 로직에만 초점을 맞춰 개발할 수 있는 것이죠.
이렇게 이번 시간에는 서블릿(Servlet)이 무엇인지, 그리고 서블릿 컨테이너는 무엇인지에 대해 공부해봤습니다.
그리고 서블릿 컨테이너가 제공하는 멀티쓰레드 기능을 보면서 우리가 개발할 때 어떻게 편리해지는지(?)에 대한 이유를 알게 된 것 같습니다.
이후에 스프링 MVC 프레임워크가 서블릿 기반으로 어떻게 발전되어 가는지를 조금씩 더 따져보겠습니다.
감사합니다.
'Spring & Springboot' 카테고리의 다른 글
[스프링/Spring] 스프링 MVC? DispatcherServlet 이해하기 (0) | 2021.11.07 |
---|---|
[스프링/Spring] FrontController 패턴과 스프링 MVC (0) | 2021.11.05 |
[스프링/Spring] 스프링 시큐리티(Spring Security) 가이드라인 따라하기 (0) | 2021.10.30 |
[스프링/Spring] 연관관계를 갖는 DTO를 엔티티로 저장할 때 고민 (4) | 2021.10.03 |
[스프링/Spring] @OneToMany/@ManyToOne 연관관계에서의 Infinite Recursion 문제를 DTO로 해결하기 (0) | 2021.09.28 |