ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Tomcat,Spring Boot에서 GET,POST 요청은 어떻게 처리될까?
    카테고리 없음 2026. 1. 2. 08:16
    728x90
    반응형

    Tomcat → Spring MVC → Controller → Response 전체 흐름 완전 해부

     

    1️⃣ 들어가며

    Spring Boot로 개발하다 보면 우리는 보통 이렇게만 생각한다.

     
    브라우저 → @GetMapping → 응답

    하지만 실제로는 이 한 줄의 요청이 처리되기까지
    Tomcat, Servlet Container, Filter, DispatcherServlet, HandlerMapping, MessageConverter
    수많은 컴포넌트와 메소드 호출을 거친다.

    이 글에서는 GET 요청(@GetMapping) 을 기준으로
    Tomcat에서 시작해 → Spring MVC 내부를 통과 → 다시 Tomcat을 통해 응답이 반환되는 전체 흐름
    메소드 단위까지 내려가며 정리한다.

     

    [ Browser ]
         |
         |  HTTP GET /hello?name=kim
         v
    [ Tomcat Connector ]
         |
         v
    [ Servlet Container ]
         |
         v
    [ Filter Chain ]
         |
         v
    [ DispatcherServlet ]
         |
         v
    [ HandlerMapping ]
         |
         v
    [ Controller Method ]
         |
         v
    [ HttpMessageConverter (Jackson) ]
         |
         v
    [ HttpServletResponse ]
         |
         v
    [ Tomcat ]
         |
         v
    [ Browser ]

     

    3️⃣ Tomcat 단계 – HTTP 요청 수신과 파싱

    3-1. 소켓 수신 & HTTP 파싱

    Tomcat은 내부적으로 Coyote HTTP 커넥터를 사용한다.

    대표 호출 흐름:

     
    AbstractProtocol.ConnectionHandler.process()
     → Http11Processor.process()

    이 단계에서 수행되는 작업:

    • TCP 소켓에서 바이트 수신
    • HTTP 요청 라인 파싱 (GET /hello HTTP/1.1)
    • 헤더 파싱
    • 내부 Request/Response 객체 생성

    3-2. Coyote → Servlet API 변환

    CoyoteAdapter.service()

     

    • Tomcat 내부 Request → HttpServletRequest
    • Tomcat 내부 Response → HttpServletResponse
    • Servlet 컨테이너 파이프라인으로 전달

    4️⃣ Servlet Container 파이프라인

    Tomcat은 요청을 Valve 체인으로 전달한다.

    StandardEngineValve
     → StandardHostValve
       → StandardContextValve
         → StandardWrapperValve

     

    여기서 FilterChain + Servlet 호출이 시작된다.

     

    ApplicationFilterChain.doFilter()

     

    실행되는 필터 예시:

    • CharacterEncodingFilter
    • HiddenHttpMethodFilter
    • (Spring Security 사용 시)
      • DelegatingFilterProxy
      • Security Filter Chain

    ⚠️ Filter는 DispatcherServlet “밖”에서 동작한다

     

     

    6️⃣ DispatcherServlet – Spring MVC의 관문

    Filter 체인을 통과하면 드디어 Spring MVC 진입

     

    FrameworkServlet.service()
     → processRequest()
       → DispatcherServlet.doService()
         → doDispatch()

     

    7️⃣ HandlerMapping – @GetMapping 찾기

    DispatcherServlet.getHandler()
     → RequestMappingHandlerMapping.getHandlerInternal()
     → lookupHandlerMethod()

     

    매칭 기준:

    • URL Path (/hello)
    • HTTP Method (GET)
    • params / headers / produces 조건

    결과:

    • HandlerMethod (컨트롤러 메서드)

    8️⃣ HandlerAdapter & Interceptor

    DispatcherServlet.getHandlerAdapter()
     → RequestMappingHandlerAdapter.handle()

    Interceptor 호출 순서

    • preHandle() → Controller 실행
    • postHandle() → View/Response 처리 전
    • afterCompletion() → 응답 완료 후

    9️⃣ Controller 메서드 실행 & 파라미터 바인딩

    InvocableHandlerMethod.invokeForRequest()

    @RequestParam 처리

     

    RequestParamMethodArgumentResolver.resolveArgument()

     

     

    • request.getParameter("name")
    • 타입 변환은 ConversionService 사용

    실제 컨트롤러 호출

    InvocableHandlerMethod.doInvoke()
     → helloController.hello()

     

     

    🔟 반환값 처리 – JSON 응답 생성

    @RestController 기준

    RequestResponseBodyMethodProcessor.handleReturnValue()
     → writeWithMessageConverters()

    Jackson 선택 과정

    MappingJackson2HttpMessageConverter.canWrite()
     → writeInternal()

     

    • Object → JSON 직렬화
    • response OutputStream에 write
    • Content-Type: application/json

    1️⃣1️⃣ 다시 Tomcat으로 – HTTP 응답 전송

    Spring이 HttpServletResponse에 기록한 내용을

    Tomcat → HTTP Response 직렬화 → Socket write

    브라우저는 이를 받아:

    • JSON 처리 (JS)
    • HTML 렌더링

     

    POST

     

    @RestController
    class AuthController {
      @PostMapping("/login")
      public TokenRes login(@RequestBody LoginReq req) { ... }
    }

     

    하지만 이 한 줄 뒤에서는 다음과 같은 질문들이 숨어 있다.

    • HTTP Body(JSON)는 언제 읽히는가?
    • 왜 @RequestBody가 있어야 JSON이 객체가 되는가?
    • Jackson은 누가, 어떤 기준으로 선택하는가?
    • 왜 Filter에서 Body를 읽으면 Controller에서 에러가 날까?

    이 글에서는 POST + JSON 요청이 들어와서 @RequestBody 객체가 만들어지고 응답이 반환되기까지의 전체 내부 흐름
    Tomcat → Spring MVC → Jackson → Tomcat 순서로 차분히 정리한다.

     

    POST /login (JSON)
     → Tomcat (HTTP 파싱, InputStream 준비)
     → Filter Chain
     → DispatcherServlet
     → HandlerMapping (컨트롤러 찾기)
     → HandlerAdapter (실행 준비)
     → @RequestBody 처리
     → Jackson 역직렬화
     → Controller 실행
     → Jackson 직렬화
     → Tomcat 응답 전송

     

    1️⃣ Tomcat 단계 – HTTP 요청을 Servlet Request로 만들기

    클라이언트가 다음과 같은 요청을 보낸다고 가정하자.

    POST /login HTTP/1.1
    Content-Type: application/json
    
    {"id":"kim","pw":"1234"}

    Tomcat에서 일어나는 일은 다음과 같다.

    ✔ Tomcat의 역할

    • 소켓에서 HTTP 바이트 수신
    • 요청 라인, 헤더 파싱
    • Content-Type: application/json 저장
    • Body는 아직 JSON 객체가 아님
    • 단지 InputStream 형태로만 준비됨

    📌 중요

    Tomcat은 JSON을 해석하지 않는다.
    단지 “읽을 수 있는 상태”로만 만들어 준다.

     

    2️⃣ Filter Chain – Spring MVC 진입 전 단계

    Tomcat은 요청을 Filter Chain으로 전달한다.

    ApplicationFilterChain.doFilter()

     

    여기서 실행되는 것들:

    • CharacterEncodingFilter
    • (있다면) Spring Security Filter Chain
    • 사용자 정의 Filter

    ⚠️ 이 시점에서도:

    • JSON은 아직 읽히지 않았다
    • Body는 여전히 InputStream 상태다

     

    3️⃣ DispatcherServlet – Spring MVC의 시작점

    Filter 체인을 통과하면 Spring MVC의 관문인 DispatcherServlet에 도착한다.

    DispatcherServlet.doDispatch()

    이 메서드 안에서 Spring MVC의 핵심 흐름이 시작된다.


    4️⃣ HandlerMapping – 어떤 컨트롤러가 처리할지 결정

    가장 먼저 수행되는 단계는 HandlerMapping이다.

    getHandler(request)
     → RequestMappingHandlerMapping

     

    이 단계에서 Spring은 다음을 기준으로 컨트롤러 메서드를 찾는다.

    • URL (/login)
    • HTTP Method (POST)
    • consumes / produces
    • params / headers 조건

    결과:

    • HandlerMethod (예: AuthController#login)
    • 적용될 Interceptor 목록

    📌 정리

    HandlerMapping의 역할은
    **“이 요청을 누가 처리할지 찾는 것”**이다.


    5️⃣ HandlerAdapter – 컨트롤러를 실행할 수 있는 실행기 선택

    컨트롤러를 찾았다고 바로 실행하지 않는다.
    먼저 이 handler를 실행할 수 있는 Adapter를 찾는다.

     

    getHandlerAdapter(handler)
     → RequestMappingHandlerAdapter

    📌 정리

    • HandlerMapping = 대상 찾기
    • HandlerAdapter = 실행 방법 결정

    6️⃣ @RequestBody 처리 시작 지점 (핵심)

    이제 HandlerAdapter가 실제로 컨트롤러를 호출한다.

    RequestMappingHandlerAdapter.handle()
     → invokeHandlerMethod()

     

    컨트롤러를 실행하기 전에,
    Spring은 메서드 파라미터를 먼저 만들어야 한다.

     

    7️⃣ @RequestBody를 처리하는 진짜 주인공

    @RequestBody LoginReq req를 담당하는 클래스는 바로 이것이다.

     
    RequestResponseBodyMethodProcessor
    InvocableHandlerMethod.invokeForRequest()
     → HandlerMethodArgumentResolverComposite.resolveArgument()
     → RequestResponseBodyMethodProcessor.resolveArgument()

     

    이 시점에서 Spring은 이렇게 판단한다.

    “아, 이 파라미터는 request body를 읽어야 하는구나”


    8️⃣ HttpMessageConverter 선택 과정

    Spring은 등록된 HttpMessageConverter들을 순서대로 확인한다.

    선택 기준은 다음과 같다.

    1. 요청의 Content-Type
    2. 파라미터 타입 (LoginReq)
    3. converter.canRead(type, contentType)
    readWithMessageConverters()
     → MappingJackson2HttpMessageConverter 선택

     

    9️⃣ Jackson 역직렬화가 실제로 일어나는 순간

    Jackson이 선택되면 여기서 실제 변환이 일어난다.

    MappingJackson2HttpMessageConverter.readInternal()
     → ObjectMapper.readValue(InputStream, LoginReq.class)

     

    이때 발생하는 일:

    • request InputStream 한 번 소비됨
    • JSON → Java Object 변환
    • 실패 시 HttpMessageNotReadableException

    ⚠️ 중요 포인트

    Request Body는 한 번만 읽을 수 있다.
    Filter에서 미리 읽으면 여기서 깨진다.


    🔟 컨트롤러 메서드 실행

    이제 모든 파라미터가 준비되었다.

     

    AuthController.login(LoginReq req)

     

    이 시점부터는:

    • 순수 자바 코드
    • 비즈니스 로직
    • DB / 외부 API 호출

    1️⃣1️⃣ 응답 반환 – Jackson 직렬화 (WRITE)

    컨트롤러가 객체를 반환하면, 다시 Jackson이 등장한다.

     

    RequestResponseBodyMethodProcessor.handleReturnValue()
     → writeWithMessageConverters()
     → MappingJackson2HttpMessageConverter.writeInternal()
     → ObjectMapper.writeValue(OutputStream, response)

     

    • Java Object → JSON
    • HttpServletResponse에 write
    • Content-Type 설정

    1️⃣2️⃣ 다시 Tomcat으로 – HTTP 응답 전송

    Spring이 HttpServletResponse에 써 둔 내용을

    • Tomcat이 HTTP 응답 포맷으로 직렬화
    • 소켓을 통해 클라이언트로 전송

    브라우저/클라이언트는 이를 받아:

    • JSON 파싱
    • 화면 갱신 또는 후속 처리

    마무리 – 핵심 정리

    POST JSON 요청
     → Tomcat은 파싱만 한다
     → HandlerMapping이 컨트롤러를 찾는다
     → HandlerAdapter가 실행을 담당한다
     → @RequestBody가 있어야 Body를 읽는다
     → HttpMessageConverter가 Jackson을 선택한다
     → ObjectMapper가 JSON을 객체로 만든다
     → 응답도 같은 경로로 다시 JSON이 된다

     

    @RequestBody는 단순한 어노테이션이 아니라
    Spring MVC의 파라미터 해석 전략 + 메시지 컨버터 + Jackson이 만나는 지점이다.

    이 흐름을 이해하면:

    • JSON 파싱 에러 원인을 정확히 알 수 있고
    • Filter / Security / Logging 설계를 실수 없이 할 수 있으며
    • Spring MVC가 왜 이렇게 설계되었는지도 자연스럽게 보인다.
    728x90
    반응형
Designed by Tistory.