톰캣

톰캣은 한 마디로 설명 → 자바 웹 애플리케이션을 실행시켜주는 서블릿 컨테이너이자 WAS

 

Spring boot로 API 서버를 만들면 보통 우리가 직접 톰캣을 설치하지 않아도 된다. 내부적으로 내장 톰캣이 떠서 HTTP 요청을 받고 그 요청을 Spring MVC로 넘겨주게 된다.

 

1. 톰캣이 무엇인가?

  • 톰캣, 정확히는 Apache Tomcat은 자바 기반 웹 애플리케이션을 실행하기 위한 서버이다.

역할을 나누어 보면 다음과 같다.

클라이언트
   ↓ HTTP 요청
Tomcat
   ↓
Servlet / Spring MVC
   ↓
Service
   ↓
Repository / DB

즉, 사용자가 브라우저나 앱에서 요청을 보내면 톰캣이 먼저 요청을 받는다.

 

예를 들어 사용자가 다음과 같은 요청을 보냈다고 해보자

GET /api/members/1 HTTP/1.1
Host: example.com

이 요청을 가장 먼저 받는 게 톰캣이다. 톰캣은 이 요청을 분석해서 Spring MVC 쪽으로 넘겨준다.

 

2. 톰캣은 웹 서버인가? WAS인가?

  • 톰캣에 대해 알기 전에 우선 서블릿과 서블릿 컨테이너부터 공부해보자

 

2.1 서블릿을 우선 공부해보자

  • 서브릿은 HTTP 요청을 자바 코드로 처리하기 위한 표준 객체

이 서블릿이 왜 필요했냐?

  • 브라우저 요청은 원래 다음과 같은 문자열입니다. 브라우저가 /hello로 요청하면 실제로는 대충 다음과 같은 HTTP 메시지가 날아옴
GET /hello HTTP/1.1
Host: localhost:8080
User-Agent: Chrome
Accept: text/html

이건 그냥 네트워크를 타고 온 문자열 데이터라. 자바 입장에서는 원래 다음과 같은 걸 직접해야함.

1. 소켓 열기
2. 포트에서 요청 기다리기
3. HTTP 문자열 읽기
4. GET인지 POST인지 파싱하기
5. URL이 /hello인지 확인하기
6. 헤더 읽기
7. 바디 읽기
8. 응답 형식 맞춰서 직접 작성하기
9. 클라이언트에게 다시 보내기

만약 서블릿이 없으면 개발자가 이런 코드를 직접 짜야함

 

서블릿은 이 복잡한 HTTP 처리를 자바 객체 방식으로 바꿔주게 된다. 즉, 이 서블릿을 쓰면 개발자는 HTTP 문자열을 직접 파싱하지 않아도 된다.

protected void doGet(HttpServletRequest request,
                     HttpServletResponse response) throws IOException {
    response.getWriter().write("hello");
}

여기서 중요한 것은 다음과 같다.

  • HttpServletRequest = HTTP 요청을 자바 객체로 표현한 것
  • HttpServletResponse = HTTP 응답을 자바 객체로 표현한 것

 

즉 서블릿은 HTTP 세계와 자바 코드 사이의 표준 연결 규칙이다.

  1. HTTP 요청 문자열
  2. Tomcat이 파싱
  3. HttpServletRequest 객체
  4. Servlet의 doGet(), doPost() 호출
  5. HttpServletResponse에 응답 작성
  6. HTTP 응답으로 변환

왜 표준 객체이어야함은 서블릿이 표준이라는 것이 중요한데 만약 이 표준이 없으면 서버마다 요청 처리 방식이 다를 수 있다. 예를 들어

  • Tomcat에서는 handleRequest()
  • Jetty에서는 process()
  • Undertow에서는 execute()

이런 식이면 서버를 바꿀 때 마다 코드를 바꿔야함. 그런데 서블릿 표준이 있으니 개발자는 위와 같이 만들기만 하면 된다. 그래서 Tomcat이든 Jetty든 Undertow든 서블릿 규칙을 아는 서버는 위 코드를 실행할 수 있다.

 

즉 Servlet = Java 웹 서버와 java 웹 애플리케이션 사이의 공통 약속

 

  • 예전에는 다음과 같이 컨트롤러 대신 직접 만들었다.
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws IOException {
        response.getWriter().write("hello");
    }
}

사용자가 /hello로 요청하면 이 doGet()이 실행되는데 문제는 이 서블릿을 누가 실행하냐 라는 것

  • 사용자 요청 → HelloServlet의 doGet() 실행

이 사이에서 요청을 받고, 서블릿을 찾아서, 메서드를 호출해주는 존재가 필요했습니다. 그것이 서블릿 컨테이너

 

2.2 서블릿 컨테이너가 하는 일

서브릿 컨테이너는 다음과 같은 일을 한다.

  1. HTTP 요청을 받음
  2. HttpServletRequest, HttpServletResponse 객체를 만듦
  3. 어떤 서블릿이 처리할 요청인지 찾음
  4. 해당 서블릿의 service(), doGet(), doPost() 등을 호출함
  5. 응답을 클라이언트에게 돌려줌
  6. 서블릿의 생성, 초기화, 종료까지 관리함

 

즉, 개발자가 직접 해야할 귀찮은 서버 작업을 대신 해준다.

흐름을 보면 브라우저에서 다음과 같이 요청했다고 해보자

GET /hello

그러면 흐름은 이렇다.

  • 브라우저 → Tomcat → HelloServlet → 응답

조금 더 자세히 보면

  1. 브라우저가 GET /hello 요청
  2. Tomcat이 요청을 받음
  3. Tomcat이 HttpServletRequest 객체 생성
  4. Tomcat이 HttpServletResponse 객체 생성
  5. /hello를 처리할 서블릿을 찾음
  6. HelloServlet의 doGet() 호출
  7. response에 "hello" 작성
  8. Tomcat이 HTTP 응답으로 변환해서 브라우저에 전달

 

2.3 Spring에서는 어떻게 연결되냐?

Spring MVC에서는 우리가 직접 HelloServelt 같은 걸 만들지 않는다. 대신 Controller를 만든다.

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

그런데 이것도 내부적으로는 서블릿 컨테이너 위에서 동작한다. Spring MVC에서는 핵심 서블릿이 하나 있다.

  • DispatcherServlet

톰캣은 모든 요청을 이 DispatcherServlet에게 넘긴다.

  • 브라우저 → Tomcat, 서블릿 컨테이너 → DispatcherServlet → Controller → Service → Repository

즉, Spring에서 서블릿 컨테이너는 DispatcherServelet을 실행시켜주는 역할을 한다.

 

2.4 웹 서버

  • 웹 서버는 주로 정적 리소스를 처리한다.

예를 들어 HTML, CSS, JavaScript, 이미지, 동영상

대표적인 웹 서버는 Nginx, Apache HTTP Server가 있다.

 

2.5 WAS

  • WAS는 동적인 요청을 처리한다.

예를 들어 로그인, 회원가입, 게시글 작성, 주문 처리, 결제 처리, DB 조회

이런 요청은 파일을 내려주는 게 아니라, 서버 내부 로직을 실행해야 한다. 대표적인 WAS는 Tomcat, Jetty, Undertow, Wildfly, WebLogic, WebSphere가 있다.

 

여기서 의문점 → 웹 서버 없이도 WAS만으로도 서비스는 가능한가?

  • 가능하다. Spring boot 내장 Tomcat만 띄워도 다음과 같이 동작할 수 있다.

Client → Spring boot 내장 Tomcat → Controller → Service → DB

실제로 로컬 개발할 때 우리가 localhost:8080을 쓰는데 이때 앞에 Nginx 같은 웹 서버 없이도 잘 동작함. 왜냐하면 Tomcat도 HTTP 요청을 받을 수 있기 때문임.

 

그러면 굳이 왜 웹 서버를 앞에 둘까?

  • WAS만으로도 가능하지만, 운영 화경에서는 WAS가 비즈니스 로직에 집중하도록 앞에 웹 서버를 두는 경우가 많다.

 

정적 파일 처리는 웹 서버가 훨씬 잘한다는 것 HTML, CSS, JS, 이미지, 동영상, 폰트 같은 것들 Tomcat도 이런 파일을 내려줄 수 있지만 Nginx 같은 웹 서버가 정적 파일 처리에 더 특화되어있다.

예를 들어 사용자가 이미지 파일을 요청했다고 한다면

GET /images/logo.png

이걸 Tomcat이 처리하면 Tomcat의 스레드, 메모리, 네트워크 자원을 사용한다. 그런데 Tomcat은 원래 이런걸 위해 존재하는 것이 아니라 보통 다음과 같은 요청을 처리하려고 있는 것

  • 로그인, 회원가입, 게시글 작성, DB 조회, 결제, 검색

그래서 정적 파일은 Nginx가 처리하고 동적 요청만 Tomcat으로 넘기는 구조를 많이 쓴다.

또한, 리버스 프록시 역할을 한다. 운영에서는 Nginx가 앞에서 요청을 받고 WAS로 넘겨주는 구조인데 이 구조에서 Nginx는 일종의 문지기 역할을 한다.

  • 외부 요청 받기, 내부 WAS 전달, 요청 경로 분기, 헤더 추가, 접근 제한

예를 들어

/api/*       → Spring Boot 서버
/admin/*     → 관리자 서버
/images/*    → 정적 파일 서버

 

또한, HTTP 처리를 앞단에서 하기 편하다. WAS만 써도 HTTPS 설정 가능하다. 하지만 운영에서는 보통 Nginx나 로드밸런서에서 HTTPS를 처리하고, 내부에서는 HTTP로 WAS에 전달한다.

 

이렇게 하였을 때 인증서 관리, TLS 설정, 갱신을 한 곳어세 처리하기 편하다.

또한, 로드밸런싱이 쉽다. WAS 서버가 여러 대일 때를 생각해보자.

  • Spring boot 서버 1, Spring boot 서버 2, Spring boot 서버 3

NGINX나 로드밸런서를 앞에 두면 요청을 여러 WAS로 나눠줄 수 이다.

  • client
    • NGINX
      • Tomcat1
      • tomcat2
      • tomcat3

WAS만 직접 노출하면 이런 분산 처리를 직접 구성하기가 더 번거롭다.

또한, WAS를 보호한다. Tomcat은 비즈니스 로직을 실행하는 핵심 서버이다 그래서 가능하면 외부에 직접 노출하지 않고 앞에 NGINX나 로드밸런서를 두는 경우가 많다.

  • 인터넷 → NGINX or Load Balancer → 내부망 Tomcat

이렇게 하면 Tomcat은 내부에서만 접근 가능하게 만들 수 있다. 또 NGINX에서 요청 크기 제한, 특정 IP 차단, Rate Limit, 잘못된 요청 필터링, 정적 파일 캐싱, 압축

또한, 장애 대응과 배포에도 유리하다. WAS를 재배포를 해야한다고 할 때 NGINX 앞단에서 트래픽을 조절하면 특정 WAS를 잠시 뺴고 배포할 수 있다.

Nginx
  ├── Tomcat 1  ← 배포 중이라 잠시 제외
  └── Tomcat 2  ← 계속 요청 처리

이런식으로 무중단 배포나 헬스 체크 구성도 쉬워진다. → 물론 요즘은 AWS ALB, Kubernetes Service/Ingress 같은 게 이 역할을 한다.

 

2.6 웹 서버랑 WAS를 분리한 이유

역사적 배경을 한 번 보자

  • 처음에는 웹 서버가 정적 파일만 잘 내려주면 됐는데, 점점 로그인 및 게시판, 주문 같은 동적 기능이 필요해지면서 WAS가 등장함

 

처음 웹은 거의 정적 파일이었다.

초기 웹은 대부분 다음과 같은 요청이 많았다.

GET /index.html
GET /style.css
GET /logo.png

서버는 그냥 파일 찾아서 내려주면 됐다.

브라우저
  ↓
웹 서버
  ↓
HTML 파일 응답

이때 웹 서버의 역할은 단순했다.

요청받기
파일 찾기
파일 내려주기

대표적으로 Apache HTTP Server 같은 웹 서버가 이 역할을 잘했다.

 

그런데 동적인 기능이 필요해졌다. 시간이 지나면서 웹이 단순 문서가 아니라 서비스가 되기 시작했다.

  • 로그인, 회원가입, 게시판 글쓰기, 댓글 작성 등등

이런 것들은 단순히 HTML 파일을 내려주는 걸로는 불가능하다. 로그인 같은 기능들은 DB를 봐야하기 때문

사용자가 아이디/비번 입력
  ↓
DB에서 회원 조회
  ↓
비밀번호 검증
  ↓
로그인 성공/실패 응답

즉, 웹 서버 혼자 파일만 내려주는 방식으로는 부족해진 것

 

그래서 CGI 같은 방식이 나왔다. → 초기에는 웹 서버가 직접 프로그램을 실행하는 방식이 있었다.

예를 들어 요청이 오면

Apache
  ↓
외부 프로그램 실행
  ↓
프로그램이 DB 조회
  ↓
결과 HTML 생성
  ↓
Apache가 응답

이런 방식이 CGI이다. 문제는 요청이 올 때마다 프로그램을 새로 실행하는 식이라서 비효율적이었다.

  • 요청 1개 → 프로세스 1개 실행
  • 요청 100개 → 프로세스 100개 실행

비용이 크고, 느리고, 많은 요청을 처리하기 어려웠다.

 

그래서 Java 는 Servelet이라는 표준이 나왔다.

  • 요청마다 프로그램을 새로 실행하지 말고 자바 객체를 메모리에 올려두고 재사용하자. → Servlet 방식 == 서블릿 객체를 미리 만들어두고 요청 마다 메서드 호출

ex

protected void doGet(HttpServletRequest request,
                     HttpServletResponse response) {
    // 요청 처리
}

요청이 올때마다 자바 프로그램을 새로 실행시키는 게 아니라 이미 떠 있는 JVM 안에서 메서드만 호출하는 방식 → 이걸 관리하는 게 서블릿 컨테이너 이고 대표가 Tomcat 이다.

 

그래서 웹 서버 옆에 WAS가 붙었다.

처음에는

Client
  ↓
Web Server
  ↓
HTML/CSS/JS/이미지 응답

그런데 동적 처리가 필요해지니까

Client
  ↓
Web Server
  ↓
CGI / 외부 프로그램
  ↓
DB

이후 java 웹 에서는

Client
  ↓
Web Server
  ↓
Servlet Container / WAS
  ↓
DB

 

예전 Java 웹 운영에서는 이런 구조가 흔했다

Client
  ↓
Apache HTTP Server
  ↓ mod_jk / AJP
Tomcat
  ↓
Servlet / JSP
  ↓
DB

Apache는 정적 파일과 앞단 요청 처리를 맡고, Tomcat은 Java Servlet/JSP 실행을 맡았다. 그때 관점에서는 Tomcat을 “웹 서버 뒤에 붙는 Java 실행 서버”처럼 많이 썼다

 

2.7 톰캣(Web Application Server)은 정확히 무엇인가?

  • 톰캣은 원래 서블릿 컨테이너이다. 하지만 HTTP 요청을 직접 받을 수 있고, 동적 요청도 처리할 수 있기 떄문에 일반적으로 WAS라고도 부른다.

정리하면 다음과 같다.

Tomcat = Servlet Container + 간단한 Web Server 기능

다만 Nginx처럼 정적 파일 처리, 리버스 프록시, 로드밸런싱, TLS 종료 같은 기능에 특화된 서버는 아니다.

Spring MVC에서 모든 요청의 시작점은 사실상 DispatcherServlet이며 그 DispatcherServlet을 실행시켜주는 게 톰캣이다.

Tomcat, 서블릿 컨테이너 → 어떤 Servlet을 호출할지까지만 안다.

DispatcherServlet, Spring MVC의 Servlet → 어떤 Controller를 호출할지 안다.

 

  • Tomcat이 하는 일

Tomcat은 서블릿 컨테이너니까 이런 일을 한다.

HTTP 요청 받기
↓
HttpServletRequest / HttpServletResponse 생성
↓
이 요청을 처리할 Servlet 찾기
↓
DispatcherServlet 호출

예를 들어 요청이 이렇게 오면:

GET /members/1

Tomcat 입장에서는 이렇게 보는 것이다.

/members/1 요청이 왔네?
이 요청은 DispatcherServlet한테 보내야겠다.

Tomcat은 여기까지만 안다. Tomcat은 이런 건 모른다.

MemberController가 있는지
getMember() 메서드가 있는지
@PathVariable Long id를 어떻게 넣는지
JSON으로 응답해야 하는지
  • DispatcherServlet이 하는 일

DispatcherServlet은 Spring MVC의 핵심 서블릿이며, Tomcat이 DispatcherServlet을 호출하면 그때부터 Spring MVC 내부 로직이 시작된다.

DispatcherServlet
↓
HandlerMapping에게 물어봄
"이 요청을 처리할 Controller가 누구야?"
↓
HandlerAdapter가 Controller 메서드 실행
↓
ArgumentResolver가 파라미터 넣어줌
↓
HttpMessageConverter가 JSON 변환
↓
응답 반환

이 요청을 처리할 Controller가 누구지?
어떤 메서드를 실행해야 하지?
파라미터는 어떻게 넣어야 하지?
반환값은 JSON으로 바꿔야 하나?

이건 Tomcat 자체가 하는 게 아니라, Tomcat 안에서 실행되는 DispatcherServlet이 Spring MVC 기능을 사용해서 하는 일이다.

 

3. 요청 흐름 자세히 보기

예를 들어 다음과 같은 요청이 있다.

GET /members/1

Spring Boot + Tomcat에서는 대략 이렇게 처리된다.

  1. 클라이언트가 HTTP 요청을 보냄
  2. Tomcat의 Connector가 요청을 받음
  3. Tomcat이 HttpServletRequest, HttpServletResponse 객체를 생성
  4. 해당 요청을 처리할 Servlet을 찾음
  5. Spring MVC에서는 DispatcherServlet이 요청을 받음
  6. DispatcherServlet이 HandlerMapping에게 물어봄
    "GET /members/1 처리할 Controller가 누구야?"
  7. HandlerMapping이 MemberController의 getMember()를 찾음
  8. HandlerAdapter가 Controller 메서드를 실행
  9. Service, Repository, DB 로직 수행
  10. 반환값을 JSON으로 변환
  11. HttpServletResponse에 응답 작성
  12. Tomcat이 클라이언트에게 HTTP 응답 반환

 

4. 톰캣의 주요 구성 요소

톰캣 내부 구조를 보면 대표적으로 이런 요소들이 있다.

Server
 └── Service
      ├── Connector
      └── Engine
           └── Host
                └── Context
                     └── Servlet

Sever → 톰캣 전체를 의미한다. == 톰캣 프로세스 하나라고 생각하면 된다.

Service → Connector와 Engine을 묶는 단위이다. == 요청을 받는 쪽과 요청을 처리하는 쪽을 연결한다.

Connector → Connector는 클라이언트의 요청을 받는 부분이다. 예를 들어 HTTP 요청은 보통 8080 포트로 들어온다.

<http://localhost:8080>

이 8080 포트에서 요청을 기다리는 게 Connector이다.

Spring boot 에서 다음과 같은 설정을 하면

server.port=8080

톰캣 Connector가 8080 포트에서 요청을 받게 된다. Connector는 HTTP 요청을 파싱해서 내부 객체로 바뀐다.

HTTP 요청 문자열
   ↓
HttpServletRequest
HttpServletResponse

Engine → Connector가 받은 요청을 실제 처리할 곳으로 넘긴다. 예를 들어 여러 도메인이나 여러 애플리케이션이 있을 때 적절한 Host, Context를 찾는다.

Host → 가상 호스트를 의미한다. 예를 들어 api.example.com, admin.example.com 같은 도메인 단위

Context → 하나의 웹 애플리케이션이다. 예를 들어 톰캣에 여러 애플리케이션을 올릴 수 있다. /app, /app2 각각 Context가 될 수 있다. Spring boot 애플리케이션 하나도 하나의 Context라고 볼 수 있다. 보통 Spring boot에서는 context-path를 설정할 수 있다.

server.servlet.context-path=/api

그러면 모든 요청 앞에 /api가 붙는다.

Servelet → 실제 요청을 처리하는 자바 객체이다. Spring MVC에서는 대표적으로 DispatcherServlet이 있다.

 

5. 톰캣의 스레드 모델

톰캣은 요청이 들어올 때마다 스레드를 사용해서 요청을 처리한다. 흐름은 다음과 같다.

요청 1 → Thread-1
요청 2 → Thread-2
요청 3 → Thread-3

Spring boot의 기본 MVC 방식은 일반적으로 요청 하나당 스레드 하나가 할당되는 구조이다. 예를 들어 동시에 100명이 요청하면 톰캣의 워커 스레드들이 각각 요청을 맡는다.

 

5.1 중요한 설정 - max-threads

  • 톰캣은 무한정 스레드를 만드는 것이 아니다. 설정된 최대 스레드 수가 있다.
  • Spring boot 에서는 보통 이런식으로 설정할 수 있다.
server.tomcat.threads.max=200 // 최대 200개의 요청 처리 스레드를 사용할 수 있다.
server.tomcat.threads.min-spare=10 // 최소로 대기시켜둘 스레드의 수

 

5.2 만약 요청이 너무 많이 들어오면?

톰캣 최대 스레드 200개라고 가정해보았을 때

  • 동시 요청 300개 → 200개 처리, 나머지 100개는 대기

대기열까지 꽉 차면 요청이 거절 될 수 있다. 관련 설정으로는 다음과 같은 것들이 있다.

server.tomcat.threads.max=200
server.tomcat.accept-count=100
server.tomcat.max-connections=8192

대략 의미는

  • max-connections = 동시에 연결 가능한 최대 커넥션 수
  • threads.max = 실제 요청을 처리하는 최대 스레드 수
  • accept-count = 스레드가 부족할 때 대기 가능한 요청 수

 

6. 톰캣과 DB 커넥션 풀의 관계

톰캣 스레드 수와 DB 커넥션 풀 크기는 서로 영향을 준다. 예를 들어

server.tomcat.threads.max=200
spring.datasource.hikari.maximum-pool-size=10

이렇게 설정되어있다고 가정해보자 그러면 톰캣은 동시에 200개의 요청을 처리하려고 할 수 있다. 그런데 DB 커넥션은 10개 밖에 없다. 그러면 DB를 사용한느 요청은 10개만 실제로 DB에 접근할 수 있고 나머지는 커넥션을 기다린다.

Tomcat Thread 200개
DB Connection 10개

→ DB 작업이 필요한 요청 200개가 들어오면
→ 10개만 DB 사용
→ 190개는 커넥션 대기
→ 응답 지연 증가
→ 타임아웃 가능

그래서 성능 튜닝할 때는 톰캣 스레드만 늘리면 안 된다. 같이 봐야하는 것들은 다음과 같다.

Tomcat threads
HikariCP maximum-pool-size
DB max_connections
API 응답 시간
CPU 사용률
메모리 사용률
외부 API 호출 시간

 

7. 톰캣과 Spring boot 내장 톰캣

예전에는 보통 다음과 같이 진행했다.

  1. 톰캣 서버 설치
  2. WAR 파일 빌드
  3. 톰캣 webapps 폴더에 배포
  4. 톰캣 실행

즉, 톰캣이 먼저 있고 그 위에 애플리케이션을 올렸다.

External Tomcat
  └── my-app.war

하지만 Spring boot에서 이걸 업그레이드를 했다. 애플리케이션 안에 톰캣이 포함된다.

my-app.jar
  └── embedded Tomcat

그래서 다음과 같이 실행한다.

java -jar app.jar

그러면 Spring boot가 실행되면서 내부에서 톰캣도 같이 실행된다.

즉,

Spring Boot 실행
  ↓
내장 Tomcat 실행
  ↓
DispatcherServlet 등록
  ↓
8080 포트에서 요청 대기

 

8. 톰캣이 없으면 Spring MVC가 동작할까?

일반적인 Spring MVC 웹 애플리케이션은 서블릿 기반이다. 그래서 요청을 받아서 DispatcherServlet을 실행시켜 줄 서블릿 컨테이너가 필요하다 그 역할을 톰캣이한다. 다만 반드시 톰캣이어야하는 것은 아니다. Spring boot에서는 다른 서버도 사용할 수 있다.

  • Tomcat, Jetty, Undertow

 

9. 톰캣 vs NGINX

구분 Tomcat Nginx
주요 역할 자바 웹 애플리케이션 실행 웹 서버, 리버스 프록시
동적 요청 처리 가능 직접 비즈니스 로직 처리 X
정적 파일 처리 가능하지만 전문은 아님 매우 강함
Java Servlet 실행 가능 불가능
Spring Boot 실행 가능 불가능
로드밸런싱 가능은 하지만 보통 Nginx 사용 강함
TLS/HTTPS 처리 가능 보통 Nginx에서 처리

 

10. 톰캣과 서블릿 생명주기

톰캣은 서블릿 객체를 직접 관리한다. 서블릿 생명주기는 보통 다음과 같다.

  1. 서블릿 클래스 로딩
  2. 서블릿 인스턴스 생성
  3. init() 호출
  4. 요청마다 service() 호출
  5. 종료 시 destroy() 호출

 

코드로 보면

public class MyServlet extends HttpServlet {

    @Override
    public void init() {
        System.out.println("서블릿 초기화");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("요청 처리");
    }

    @Override
    public void destroy() {
        System.out.println("서블릿 종료");
    }
}

요청 마다 서블릿 객체를 새로 만드는 것이 아니라 보통 서블릿 객체는 하나 만들어두고 여러 요청을 여러 스레드가 동시에 처리한다. 그래서 서블릿이나 Controller에서 공유 상태를 조심해야한다. 예를 들어 다음고 ㅏ같은 코드는 위험하다

@RestController
public class MemberController {

    private String name;

    @GetMapping("/members")
    public String getMember(@RequestParam String name) {
        this.name = name;
        return this.name;
    }
}

Controller는 싱글톤 빈이기 때문에 여러 요청이 동시에 들어오면 this.name 값이 서로 덮어써질 수 있다.

그래서 요청마다 달라지는 값은 지역 변수로 둬야 한다.

@GetMapping("/members")
public String getMember(@RequestParam String name) {
    return name;
}

 

11. 톰캣 요청 처리와 HTTP 메서드

톰캣은 HTTP 메서드에 따라 서블릿 메서드를 호출한다.

GET     → doGet()
POST    → doPost()
PUT     → doPut()
DELETE  → doDelete()
PATCH   → service() 내부에서 처리

Spring MVC에서는 우리가 직접 doGet()을 구현하지 않고 다음과 같이 쓴다.

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

내부적으로는 톰캣이 요청을 받고, DispatcherServlet을 거쳐서 알맞은 Controller 메서드가 실행된다.