본문 바로가기
Spring

HTTP요청부터 응답까지의 과정

by 고선제 2023. 9. 25.

전체 과정 틀

  1. 클라이언트가 HTTP 요청
  2. WAS에서 메시지를 파싱 후 웹 서버에서만 필요한 정보라면 정적인 페이지를 반환하고 동적인 컨텐츠도 필요하다면 Request,Response객체를 만들어 Filter객체로 전달.
  3. Filter객체 내부에서 요청된 내용을 처리하고 HttpServletRequest, HttpServletResponse 로 변환해 DispatcherServlet으로 전달
  4. DispatcherServlet의 doDispatch()메소드 실행
    1. HandlerMapping을 통해 요청을 처리할 Controller를 찾음
    2. Controller를 찾고 Interceptor의 prehandle이 실행
    3. DispatcherServlet은 Controller를 실행해줄 HandlerAdapter를 찾는다.
    4. Adapter를 찾고 controller를 실행하기 위한 파라미터를 생성하기 위해 Resolver 실행
    5. Controller실행 후 Interceptor의 posthandle 실행
    6. ViewResolver에게 전달
    7. ViewResolver는 View에게 전달.
    8.Interceptor의 afterCompletion 실행
  5. Client에 View 반환

1. 클라이언트가 HTTP 요청

2. WAS에서 HTTP메시지를 파싱 후 웹 서버에서만 필요한 정보라면 정적인 페이지를 반환하고 동적인 컨텐츠도 필요하다면 Request,Response객체를 만들어 Filter객체로 전달.

Filter란?

  • Dispatcher Servlet에 요청이 전달되기 전/후에 url패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있는 기능을 제공한다.
  • 주로 요청에 대한 인증, 권한 체크 등을 하는데 사용된다. 예를 들어 요청이 DispatcherServlet에 전달되기 전에 헤더를 검사해 인증 토큰이 있는지 없는지, 올바른지 아닌지 등을 검사할 수 있다.
  • Spring Context 내부에 도달하기 전인 WAS 인입 시점에 로직을 처리해 준다.

3. Filter객체 내부에서 요청된 내용을 처리하고 HttpServletRequest, HttpServletResponse 로 변환해 DispatcherServlet으로 전달

Filter 메소드

public interface Filter {
  public default void init(FilterConfig filterConfig) throws ServletException{}
  public void doFilter(ServletRequest request, ServletResponse response,
                       FilterChain chain) throws IOException, ServletException;
  public default void destroy() {}
}
  • init() : 필터가 생성될 때 수행되는 메소드, 필터 객체를 초기화 및 서비스에 추가
  • doFilter() : Request, Response가 필터를 거칠 때 수행되는 메소드
    • 이 메서드에서 다음에 체이닝 할 필터가 있으면 다음 필터로 넘겨주고, 없으면 서블릿을 호출해 줍니다.
  • destroy() : 필터가 소멸될 때 수행되는 메소드, 필터 객체를 제거

Filter Chain

  • doFilter()의 매개변수인 FilterChain으로 다음 실행할 Filter가 있는지 알려준다.
  • 필터는 다음과 같이 여러 개로 Chaining 설정할 수 있어서 클라이언트의 요청마다 URL 패턴 등을 지정해서 원하는 필터링을 할 수 있다.

Filter Order(’@Order’)

  • 값이 작을수록 우선순위가 높게 설정되어있다.
  • 값의 범위는 int값으로 지정되어 있다.
  • Order 어노테이션을 지정하지 않으면 컴포넌트의 알파벳 순으로 Filter Chaining이 지정된다.

Filter 적용 방식

  • @WebServletComponentScan, @Component 방식
    • 메인 Application에 ‘@WebServletComponentScan’을 지정한 뒤@WebFilter를 사용하면 URL 패턴을 지정할 수 있다.
    • 클래스 호출이 두 번 되기 때문에 ‘@Component’ 어노테이션을 지워야한다.
  • FilterRegistrationBean 방식
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {

	private static final Log logger = LogFactory.getLog(RegistrationBean.class);

	private int order = Ordered.LOWEST_PRECEDENCE; // Integer.MAX_VALUE
    
    // ...
}

→ 직접 필터를 빈으로 등록하는 방식이다.

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean AFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new ATestFilter());
        filterFilterRegistrationBean.setOrder(Integer.MIN_VALUE);
        filterFilterRegistrationBean.addUrlPatterns("/*");
        return filterFilterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean BFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new BTestFilter());
        filterFilterRegistrationBean.setOrder(5000);
        filterFilterRegistrationBean.addUrlPatterns("/*");
        return filterFilterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean CFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new CTestFilter());
        filterFilterRegistrationBean.setOrder(-100);
        filterFilterRegistrationBean.addUrlPatterns("/*");
        return filterFilterRegistrationBean;
    }
}

→ FilterRegistrationBean 객체를 생성해서 Filter 정보를 만들어서 넣어주면 된다. 이때 반드시 Filter는 구현되어 있어야 한다.

4. DispatcherServlet의 doDispatch()메소드 실행

DispatcherServlet

  • HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 Front Controller이다.
    • Front Controller : 웹 어플리케이션에서 주로 사용되는 디자인 패턴으로 컨트롤러에서 중복으로 처리해야 하는 사항을 공통 기능을 한 곳에서 관리하는 입구 역할을 하는 컨트롤러의 개념
  • 클라이언트로부터 요청이 오면 Tomcat과 같은 서블릿 컨테이너가 요청을 받는다. 이 모든 요청을 Front Controller인 DispatcherServlet이 가장 먼저 받고, 공통적인 작업을 처리한 후 해당 요청을 처리해야 하는 컨트롤러를 찾아 작업을 위임한다.

정적 자원 처리

  • DispatcherServlet이 모든 요청을 처리할 수 없으므로, 정적 자원 요청을 분리해야 한다.
  • Dispatcher Servlet이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원(Resource)경로를 탐색하여 자원을 탐색하는 것이다.

동작 과정(doDipatch())

1. 클라이언트의 요청을 Dispatcher Servlet이 받는다.

  • Web Context에서 Filter들을 지나 Spring Context에서 Dispatcher Servlet이 가장 먼저 요청을 받는다.

2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾음.

  • 요청을 처리할 핸들러(컨트롤러)를 찾고 해당 객체의 메소드를 호출한다.
  • 따라서 가장 먼저 어느 컨트롤러가 요청을 처리할 수 있는지를 식별해야 하는데, 해당 역할을 하는 것이 바로 HandlerMapping이다.
  • 최근에는 ‘@Controller’에 ‘@RequestMapping’관련 어노테이션을 사용해 컨트롤러를 작성하는 것이 일반적이다.
  • 이 ‘@Controller’ 방식은 RequestMappingHandlerMapping가 처리한다.
  • 이는 ‘@Controller’로 작성된 모든 컨트롤러를 찾고 파싱하여 HashMap으로 <요청 정보, 처리할 대상> 관리한다.
  • 요청이 오면 (Http Method, URI) 등을 사용해 요청 정보를 만들고, HashMap에서 요청을 처리할 대상(HandlerMethod)를 찾은 후에 HandlerExecutionChain으로 감싸서 반환한다
  • HandlerExecutionChain으로 감싸는 이유는 컨트롤러로 요청을 넘겨주기 전에 처리해야 하는 인터셉터 등을 포함하기 위해서이다.

3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 전달함

  • Dispatcher Servlet은 컨트롤러로 직접 위임하는 것이 아닌 HandlerAdapter를 통해 위임한다.
  • 그 이유는 컨트롤러의 구현 방식이 다양하기 때문이다.
  • Controller 인터페이스로 구현했던 과거와 달리 최근 어노테이션 기반 프로그래밍을 추구하기 때문에 컨트롤러가 다양하게 작성된다.
  • 그에 대응하기 위해 HandlerAdapter라는 어댑터 패턴을 적용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있도록 하였다.

4. 핸들러 어댑터가 컨트롤러로 요청을 위임함.

  • 컨트롤러로 위임하기 전/후에 공통적인 전/후처리 과정이 필요하다.
  • 대표적으로 인터셉터들을 포함해 요청 시에 ‘@RequestParam’, ‘@RequestBody’ 등을 처리하기 위한 ArgumentResolver 처리
  • ResponseEntity의 Body를 Json으로 직렬화하는 등의 처리를 하는 ReturnValueHandler 처리
  • ArgumentResolver 등을 통해 파라미터가 준비 되면 리플렉션을 이용해 컨트롤러로 요청을 위임한다.
    • 리플렉션 : 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API

5. 비즈니스 로직을 처리한다.

  • 이후에 컨트롤러는 서비스를 호출하고 우리가 작성한 비즈니스 로직들이 진행된다.

6. 컨트롤러가 반환값을 반환한다.

  • 주로 ResponseEntity를 반환한다.
    • ResponseEntity : HTTP 요청(Request) 또는 응답(Response)에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스인 HttpEntity를 구현한 클래스

7. 핸들러 어댑터가 반환값을 처리한다.

  • 컨트롤러로부터 받은 응답을 응답 처리기인 ReturnValueHandler가 후처리한 후에 Dispatcher Servlet으로 돌려준다
  • 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태(HttpStatus)를 설정한다.
  • 컨트롤러가 View 이름을 반환하면 ViewResolver를 통해 View를 반환한다.

8. 서버의 응답을 클라이언트로 반환한다.

  • DispatcherServlet을 통해 반환되는 응답은 다시 Filter들을 거쳐 클라이언트에게 반환된다.
  • 응답이 화면이라면 View를 찾아서 반환해주는 ViewResolver가 적절한 화면을 내려준다.

Interceptor

  • 인터셉터는 DispatcherServlet과 컨트롤러 사이에서 요청을 가로채는 역할을 수행한다.
  • Spring에서 Interceptor의 구현은 HandlerInterceptor(Interface)나 HandlerInterceptorAdapter(Abstract Class)로 구현할 수 있다.
  • controller의 핸들러(클라이언트가 요청한 url에 따라 실행되는 메서드)를 호출하기 전과 후에 요청과 응답을 가로채서 원하는 동작을 추가할 수 있도록 해준다
  • (ex. 로그인 체크, 응답에서 알림 개수 조회)
  • 스프링 컨텍스트에서 동작
  • HandlerMapping에서 반환한 HandlerExecutionChain은 1개 이상의 인터셉터가 등록되어 있다면 순차적으로 인터셉터들을 거쳐 컨트롤러가 실행되도록 하고, 인터셉터가 없다면 바로 컨트롤러를 실행한다

Interceptor 장점

  • 공통 코드 사용으로 코드 재사용성 증가
  • 메모리 낭비, 서버 부하 감소
  • 코드 누락에 대한 위험성 감소

Interceptor 메소드

  • preHandle 메소드(PreHandle(HttpServletRequest request, HttpServletResponse response, Object handler))
    • 컨트롤러가 호출되기 전에 실행된다.
    • 컨트롤러 이전에 처리해야하는 작업이나 요청 정보를 가공하거나 추가하는 경우에 사용
    • 반환 타입은 boolean이다. true면 다음 작업 실행, false면 중단
    • Parameter 중 Object handler는 HandlerMapping이 찾은 Controller Class 객체이다.
  • PostHandle 메소드(PostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView))
    • 컨트롤러가 호출된 후 실행
    • 컨트롤러 이후 후처리 작업이 필요할 때 사용
    • RestAPI기반 컨트롤러(‘@RestController’)를 만들면서 자주 사용되지 않음
    • 중간에 예외가 발생하면 호출되지 않는다.
    • Parameter 중 ModelAndView modelAndView를 통해 화면 단에 들어가는 Data 등의 조작이 가능하다.
  • afterCompletion 메소드(afterComplete(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex))
    • 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행된다.
    • 중간에 예외가 발생하더라도 반드시 호출된다.

Resolver

1. ArgumentResolver

ArgumentResolver 동작 위치 및 순서

  1. 컨트롤러를 처리하기 전에 어댑터는 ArgumentResolver에 요청을 해 컨트롤러에서 필요한 파라미터를 요청
  2. ArgumentResolver는 Controller를 보고 파라미터를 찾음
  3. 핸들러 어댑터에게 파라미터를 반환
  4. 핸들러 어댑터는 파라미터를 가지고 컨트롤러를 처리한다.
  5. 컨트롤러를 처리하고 결과 값을 HandlerMethodReturnValueHandler인터페이스를 걸쳐서 처리한다. 위 인터페이스를 걸쳐 처리하는 이유는 다양한 타입을 처리하기 위함이다.

ArgumentResolver 용도

  • 요청에 필요한 parameter에 필요한 정보를 HttpRequestServlet, SecurityContext 또는 그 외에 어떤 곳에서든 가져와서 바인딩하고 싶다면 ArgumentResolver를 활용하면 된다.

2. ViewResolver

  • ViewResolver란 view 객체를 생성하는 방법을 결정하는 인터페이스이다. ViewResolver는 컨트롤러가 반환하는 view이름을 view 객체로 변환하는데, 변환된 view 객체는 디스패처 서블릿이 클라이언트에게 반환하는 응답을 생성하기 위해 사용된다.
  • 특정 ViewResolver를 Bean으로 등록하지 않으면, DispatcherServlet은 기본 viewResolver인 InternalResourceView를 사용한다.
  • InternalResourceViewResolver(default)
[applicationContext.xml]  
<bean id ="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">     
<property name="prefix" value="/WEB-INF/jsp/">      
<property name="suffix" value=".jsp">  
</bean>

→미리 지정된 접두사, 접미사를 사용

HTTP message converter

  • Spring내부에서는 HTTP message body의 내용을 문자 또는 객체로 변환하는 방법으로 HTTP message converter를 사용한다.
  • Spring은 보통 Jackson을 기본적으로 사용하며, 개발자의 설정에 따라 다른 라이브러리로 교체가 가능하다.
  • Request body를 읽거나 Response body를 작성하는 로직에는 관여하지 않는다. 단지 변환할 뿐이다.

HTTP message converter의 발동조건

  1. ‘@RequestBody’, HttpEntity를 사용하는 경우
  2. ‘@ResponseBody’, HttpEntity를 사용하는 경우
  • 즉, 발동하는 상황
    • Request body에 담긴 JSON 또는 String 데이터를 읽는 경우
    • Response body에 JSON 또는 String 데이터를 담아 반환하는 경우

→ HTTP message body를 사용한 요청과 응답을 처리할 때 사용된다.

HTTP message converter의 역할

  • Request일 경우 JSON을 객체 및 문자열로 변환해주고, Response일 경우 객체 및 문자열을 JSON으로 변환하는 역할을 해준다.
  • 객체로 반환하기 위해서는 HTTP message converter의 JSON 컨버터가 동작한다.(StringHttpMessageConverter)
  • 문자열을 반환하면, HTTP message converter의 String 컨버터가 동작한다.(MappingJackson2HttpMessageConverter)

Spring MVC구조에서 HTTP message converter 호출 과정

  • Request
    1. RequestMappingHandlerAdapter에서 ArgumentResolver를 호출
    2. Argument Resolver는 Handler가 필요로 하는 객체를 생성한 후, HTTP message converter를 호출
    3. HTTP message converter는 Request body의 데이터를 ArgumentResolver가 생성한 객체에 바인딩한다.
    4. HTTP message converter는 바인딩된 객체를 Argument Resolver에게 반환한다.
    5. Argument Resolver는 반환된 객체를 최종적으로 Handle에게 전달한다.
  • Response
    1. Handler에서 비즈니스 로직을 완료하고 결과 값을 반환한다.
    2. 반환된 결과 값은 ReturnValueHandler에게 전달된다.
    3. ReturnValueHandler는 Handler의 annotation 정보 또는 사용하는 매개변수에 따라 결과 값을 변환한다.
    4. HTTP message converter를 호출하여 변환된 결과 값을 전달한다.
    5. HTTP message converter는 Response 메시지를 생성하고 변환된 결과 값을 Response body에 담는다.

[Reference]

'Spring' 카테고리의 다른 글

DTO의 생성 위치(with 장점)  (2) 2025.01.19
Spring Security의 흐름과 개념 설명  (0) 2024.08.11
Layered Architecture  (0) 2023.10.05
IoC 컨테이너  (0) 2023.09.20