본문 바로가기
Server

내 코드가 서버에서 실행되기까지 (WAS, Apache, Nginx, Tomcat, Maven, Gradle, Servlet)

by daun_up 2026. 4. 16.

Web Server

정적인 콘텐츠(HTML, CSS, JS, 이미지 등)를 브라우저에 전달하는 역할을 한다.

Apache HTTP Server, Nginx 등이 있다.

WAS (Web Application Server)

WAS 는 동적인 비즈니스 로직을 처리하는 서버다. Java 진영에서는 Apache Tomcat 이 가장 많이 쓰인다.

클라이언트 → HTTP 요청 → WAS (Tomcat) → 응답 반환
http://localhost:8080

실무에서는 Nginx (웹 서버) + Tomcat(WAS) 를 함께 쓰는 구조가 일반적이다. Nginx 가 정적 자원을 처리하고, 동적 요청만 Tomcat 으로 넘긴다. 두 구조를 같이 사용하면 정적 파일은 웹 서버가 빨리 처리하고, 복잡한 로직만 WAS 가 담당하게 해서 부하가 분산된다. 또한, DB 정보 등 중요한 데이터를 WAS 에 저장하고 앞단에 웹 서버를 두어서 직접적인 노출을 막는다. (리버스 프록시)

Container

WAS 자체를 컨테이너(Container) 라고도 부른다. Servlet/JSP 같은 Java 웹 컴포넌트가 동작할 수 있는 실행 환경을 제공하기 때문이다.

http://localhost:8080   ← 이게 컨테이너(WAS) 루트에 접근하는 것

Tomcat이 컨테이너 역할을 하면서 그 위에 여러 애플리케이션(Context)을 올려서 실행한다.

Context

Context는 WAS 위에 배포된 개별 애플리케이션의 실행 단위이다. 각 Context는 독립적인 URL 경로, 클래스 로더, 설정을 가진다.

http://localhost:8080/contextA   ← contextA 애플리케이션
http://localhost:8080/contextB   ← contextB 애플리케이션

엄밀히는 "애플리케이션 코드" 와 "실행 환경 설정" 이 분리된 개념이지만, 실무에서는 거의 동일하게 취급한다. Tomcat에서는 server.xml이나 context.xml에 Context를 정의한다. 스프링 부트를 쓰면 내장 Tomcat이 포함되어 있어서 Context 설정을 직접 건드릴 일이 거의 없긴 하다.

Maven

Maven은 Java 프로젝트의 빌드 자동화 + 의존성 관리 도구이다. 핵심 파일은 pom.xml

  • 프로젝트 구조 표준화 - 모든 Maven 프로젝트는 동일한 디렉토리 구조를 따른다.
src/main/java      ← 소스 코드
src/main/resources ← 설정 파일
src/test/java      ← 테스트 코드
pom.xml            ← Maven 설정 파일
  • 의존성 관리 - 라이브러리를 직접 다운로드해서 넣을 필요 없이 pom.xml에 선언만 하면 자동으로 받아온다.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>3.2.0</version>
</dependency>
  • 빌드 자동화 - mvn package 명령 하나로 컴파일 - 테스트 - .jar/.war 파일 생성까지 자동으로 처리한다.

Gradle

Maven과 같은 빌드 자동화 + 의존성 관리 도구인데, XML 대신 Groovy/Kotlin DSL로 설정한다. 핵심 파일은 build.gradle 이다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:3.2.0'
}

Servlet

서블릿은 HTTP 요청을 받아서 동적인 HTML 또는 데이터를 생성하여 응답하는 자바 클래스이다. 서블릿은 일반 자바 객체처럼 직접 new 로 생성하지 않는다. 서블릿 컨테이너 (Tomcat) 가 생성부터 소멸까지 관리한다.

  • 초기화 - init()
    • 서블릿이 처음 호출될 때 딱 한 번 실행된다. 설정 정보를 읽어오거나 자원을 준비하는 단계이다.
  • 서비스 실행 - service()
    • 클라이언트 요청이 들어올 때마다 호출된다. 내부적으로 요청 방식에 따라 doGet(), doPost() 등을 호출한다.
  • 소멸 - destroy()
    • 서버가 종료되거나 서블릿이 더 이상 필요 없을 때 호출된다. 사용하던 자원 정리

HTTP 요청이 들어왔을 때 상세 과정

  1. 요청 수신: 웹 브라우저가 요청을 보내면 WAS(Tomcat)가 받는다.
  2. 객체 생성: 톰캣은 요청 정보를 담은 HttpServletRequest 객체와, 응답을 담을 빈 HttpServletResponse 객체를 생성한다.
  3. 스레드 할당: 톰캣의 스레드 풀(Thread Pool) 에서 놀고 있는 스레드 하나를 이 요청에 매핑한다.
  4. 서블릿 매핑: web.xml 이나 @WebServlet 어노테이션 정보를 확인하여 어떤 서블릿 클래스를 실행할지 결정한다.
  5. 메서드 호출: 배정된 스레드가 해당 서블릿의 service() 메서드를 실행한다.
  6. 응답 및 소멸: 로직 처리가 끝나면 톰캣이 Response 객체에 담긴 내용을 HTTP 응답 메시지로 변환해 브라우저에 보내고, 생성했던 두 객체를 소멸시킨다.

서블릿의 핵심 객체

  • HttpServletRequest (요청 주머니)
    • 클라이언트가 보낸 모든 정보가 들어 있다. 
    • 파라미터 읽기: request.getParameter("id")
    • 헤더 확인: request.getHeader("User-Agent")
    • 저장소 역할: request.setAttribute("key", value)를 통해 다음 페이지(JSP 등)로 데이터를 넘길 수 있다.
  • HttpServletResponse (응답 주머니)
    • 서버가 클라이언트에게 보낼 정보를 담는다.
    • 콘텐츠 타입 설정: response.setContentType("text/html")
    • 데이터 출력: response.getWriter().println("Hello!")
    • 리다이렉트: response.sendRedirect("/login")

서블릿은 싱글톤

서블릿 컨테이너는 각 서블릿 클래스 당 객체를 딱 하나만 생성해서 돌려 쓴다. (싱글톤) 왜냐하면, 요청이 1,000 개 들어온다고 해서 서블릿 객체 1,000 개를 만들면 메모리가 견디지 못 하기 때문이다. 객체 하나를 여러 스레드가 공유해서 쓰기 때문에, 서블릿 클래스의 멤버 변수(필드) 에 데이터를 저장하면 안 된다. (A 사용자의 정보가 B 사용자에게 노출될 위험이 있음) 모든 데이터는 메서드 내부의 지역 변수나 Request 객체에 담아야 안전하다.

서블릿이 진화한 형태: DispatcherServlet

요즘에는 서블릿을 수십 개 만들지 않음! 스프링은 Front Controller 패턴을 사용한다. 예전에는 요청마다 각각의 서블릿이 문지기 역할을 했다. 지금은 DispatcherServlet 라는 '최강의 문지기 서블릿' 하나만 등록해 둔다. 얘가 요청을 다 받은 뒤에 우리가 만든 @Controller 들 중 어디로 보낼지 결정 (Handler Mapping) 해서 배달해 주는 구조인 것이다.

서블릿 : '톰캣이라는 공장' 에서 돌아가는 일꾼

톰캣이 시키는 대로(Life Cycle) 움직이고, 손님(Client)이 오면 요청 주머니(Request)를 뒤져서 주문을 확인하고,응답 주머니(Response)에 결과물을 담아서 내보내는 역할이다.

 

 

전체 흐름 요약

[1단계] 프로젝트 생성
  → Maven 또는 Gradle 선택
  → 의존성 선언 (Spring Web, JPA 등)
  → 서블릿 등록 (전통 방식: web.xml에 서블릿 클래스와 URL 직접 등록)
  → 서블릿 등록 (현재 방식: @WebServlet("/주소") 어노테이션으로 대체)

[2단계] 개발
  → HttpServlet을 상속받는 클래스 작성
  → doGet() / doPost() 안에 비즈니스 로직 구현
  → Gradle/Maven이 라이브러리 자동 다운로드 & 버전 관리

[3단계] 빌드
  → ./gradlew build 또는 mvn package
  → .jar / .war 파일 생성 (서블릿, JSP, 설정 파일 등 모두 포함)

[4단계] 배포
  → .jar → 서버에서 직접 실행 (스프링 부트 내장 Tomcat)
  → .war → 외부 Tomcat(WAS)에 Context로 등록

[5단계] 운영
  → Nginx가 앞단에서 HTTP 요청을 받아 Tomcat으로 전달
  → Tomcat은 요청마다 Request / Response 객체를 새로 생성하고 처리할 스레드 배정
  → 각 Context(애플리케이션)가 독립적으로 요청 처리

스프링 부트 기준으로 .jar 로 빌드하기 때문에 내장 Tomcat 으로 바로 실행하는 것이 표준이다. DispatcherServlet 하나가 모든 요청을 받아서 @Controller로 분기하기 때문에, HttpServlet을 직접 상속해서 개발하는 2단계 방식은 거의 볼 일이 없다. 즉, Tomcat/Context 설정을 직접 건드릴 일은 많지 않다. (이게 스프링 부트 쓰는 이유임)