HttpServletRequest

2024. 10. 9. 12:50Spring Framework/Web on Servlet Stack

HTTP Request의 속성(attribute)서블릿 필터(Servlet Filter)핸들러 인터셉터(HandlerInterceptor) 등의 컴포넌트가 처리하는 동안 추가하는 추가적인 데이터입니다. 이 속성은 request scope 내에서 사용되며, 요청이 시작되어 끝날 때까지 유지됩니다. 요청 속성은 일반적인 요청 헤더나 본문과는 달리, 서버 쪽에서 생성 및 관리되며, 주로 서버 내에서 특정 요청에 대한 데이터를 다른 컴포넌트에 전달하기 위한 목적을 가집니다.

HTTP 요청 속성의 역할

  • 추가 데이터 전달: 필터나 인터셉터에서 요청을 처리할 때, 특정한 정보를 다른 컨트롤러로 전달해야 할 경우 요청 속성을 사용합니다. 예를 들어, 인증 정보를 필터에서 처리한 후, 이 정보를 컨트롤러로 전달할 때 요청 속성으로 저장할 수 있습니다.
  • 요청의 상태 관리: 필터 또는 인터셉터가 요청의 상태를 기록하거나 처리한 데이터를 저장하고, 이후 컨트롤러 또는 뷰에서 그 데이터를 사용할 수 있도록 합니다.

요청 속성과 관련된 컴포넌트

  1. Servlet Filter (서블릿 필터): HTTP request가 컨트롤러에 도달하기 전에 처리되는 컴포넌트입니다. 필터에서 request.setAttribute() 메서드를 사용하여 요청 속성을 추가할 수 있습니다.
  2. HandlerInterceptor (핸들러 인터셉터): 스프링 프레임워크에서 요청이 컨트롤러로 들어가기 전이나 후에 추가적인 처리를 할 수 있는 컴포넌트입니다. 인터셉터에서도 필터와 유사하게 요청 속성을 추가하거나 수정할 수 있습니다.

예시: 요청 속성 설정과 사용

  1. 서블릿 필터에서 요청 속성 설정하기
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;

public class ClientFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        Client client = new Client(1, "John Doe");  // 가상의 Client 객체 생성
        httpRequest.setAttribute("client", client);  // 요청 속성에 Client 객체 저장

        chain.doFilter(request, response);  // 필터 체인 실행
    }

    @Override
    public void destroy() {}
}
  1. 컨트롤러에서 요청 속성 사용하기
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.stereotype.Controller;

@Controller
public class ClientController {

    @GetMapping("/client")
    public String handle(@RequestAttribute("client") Client client) {
        System.out.println("Client ID: " + client.getId());
        return "clientDetails";
    }
}

요청 속성의 특징

  1. 요청 범위에서만 유효: 요청 속성은 HTTP 요청이 시작되고 끝날 때까지만 유효합니다. 요청이 완료되면 해당 속성은 더 이상 유효하지 않습니다.
  2. 서버 측에서 설정: 요청 속성은 서버 측에서 설정되는 값으로, 클라이언트는 이를 직접 전송하지 않습니다. 주로 서버 내에서 데이터를 처리하는 과정에서 다른 컴포넌트와 데이터를 공유하기 위해 사용됩니다.
  3. 클라이언트에게 노출되지 않음: 요청 속성은 HTTP 헤더나 본문과 달리 클라이언트와는 직접적인 관련이 없으며, 클라이언트는 이 속성의 존재를 알지 못합니다.

요청 속성과 세션 속성의 차이

  • 요청 속성은 한 번의 요청이 끝나면 소멸하는 데이터입니다. 즉, 요청이 끝날 때까지 데이터를 유지하고 이후에 제거됩니다.
  • 세션 속성HTTP 세션을 통해 여러 요청에 걸쳐 데이터를 유지합니다. 세션 속성은 클라이언트와의 상호작용을 여러 요청에 걸쳐 지속적으로 처리할 때 유용합니다.

WebFlux에서의 요청 속성 처리

Spring WebFlux에서는 @RequestAttribute 대신 ServerWebExchange를 통해 요청 속성에 접근합니다. WebFlux는 비동기적으로 작동하므로 요청 속성도 비동기적으로 처리합니다.

WebFlux 예시

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@RestController
public class ReactiveClientController {

    @GetMapping("/client")
    public Mono<String> handle(ServerWebExchange exchange) {
        Client client = exchange.getAttribute("client");  // 요청 속성에서 client 가져오기
        if (client != null) {
            return Mono.just("Client ID: " + client.getId());
        } else {
            return Mono.just("No client in request.");
        }
    }
}

요약

  • 요청 속성은 필터나 인터셉터에서 설정된 값을 컨트롤러에서 사용하기 위해 HTTP 요청 범위 내에서 유지되는 데이터입니다.
  • @RequestAttribute를 사용하여 이러한 요청 속성에 접근할 수 있으며, 요청이 끝나면 속성도 소멸됩니다.
  • Spring WebFlux에서는 ServerWebExchange를 통해 비동기적으로 요청 속성에 접근할 수 있습니다.

ClientFilter와 같은 서블릿 필터(Servlet Filter)를 스프링 애플리케이션에 등록하는 방법은 크게 두 가지가 있습니다.

  1. 스프링 부트에서 @Component 어노테이션을 사용하여 필터를 자동으로 등록하는 방법
  2. FilterRegistrationBean을 사용하여 프로그래매틱하게 필터를 등록하는 방법

1. @Component 어노테이션을 사용한 필터 등록

가장 간단한 방법은 필터 클래스에 @Component 어노테이션을 붙여서 스프링이 필터를 자동으로 감지하고 등록하도록 하는 것입니다.

예시: ClientFilter 등록

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Component  // 필터를 스프링 애플리케이션에 자동으로 등록
public class ClientFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        Client client = new Client(1, "John Doe");  // 가상의 Client 객체 생성
        httpRequest.setAttribute("client", client);  // 요청 속성에 Client 객체 저장

        chain.doFilter(request, response);  // 필터 체인 실행
    }

    @Override
    public void destroy() {}
}
  • @Component: 이 어노테이션을 붙이면, 스프링이 이 클래스를 자동으로 빈(bean)으로 등록하고, 서블릿 필터로도 자동으로 설정됩니다.
  • 필터가 등록된 이후, 모든 HTTP 요청은 이 필터를 거치게 됩니다.

2. FilterRegistrationBean을 사용한 프로그래매틱 필터 등록

두 번째 방법은 FilterRegistrationBean을 사용하여 프로그래매틱하게 필터를 등록하는 방법입니다. 이를 통해 필터의 우선순위, 특정 경로에만 필터 적용 등의 설정을 할 수 있습니다.

예시: FilterRegistrationBean을 사용한 필터 등록

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<ClientFilter> clientFilter() {
        FilterRegistrationBean<ClientFilter> registrationBean = new FilterRegistrationBean<>();

        registrationBean.setFilter(new ClientFilter());
        registrationBean.addUrlPatterns("/client/*");  // 필터가 적용될 URL 패턴 설정
        registrationBean.setOrder(1);  // 필터의 우선순위 설정 (낮을수록 높은 우선순위)

        return registrationBean;
    }
}

코드 설명:

  • @Configuration: 이 클래스는 스프링의 설정 클래스임을 나타냅니다.
  • FilterRegistrationBean: 이 빈을 사용해 필터를 등록하고 설정할 수 있습니다.
    • setFilter(new ClientFilter()): 직접 필터 인스턴스를 설정합니다.
    • addUrlPatterns("/client/*"): 필터가 적용될 URL 패턴을 지정할 수 있습니다. 이 예제에서는 /client/* 경로에만 필터가 적용됩니다.
    • setOrder(1): 필터의 우선순위를 설정할 수 있습니다. 숫자가 낮을수록 우선순위가 높습니다.

필터 적용 대상 설정

필터는 특정 URL 패턴에만 적용되거나, 특정 경로를 제외하도록 설정할 수 있습니다. 예를 들어, addUrlPatterns 또는 addInitParameter를 사용하여 필터가 적용될 경로를 지정할 수 있습니다.

registrationBean.addUrlPatterns("/api/*");  // "/api/*" 경로에만 필터 적용

필터 적용 순서 설정

여러 필터가 존재할 경우, 필터의 순서를 제어할 수 있습니다. 필터의 순서는 setOrder() 메서드로 설정하며, 숫자가 낮을수록 높은 우선순위를 가집니다.

registrationBean.setOrder(1);  // 가장 높은 우선순위

필터 등록 요약

  • @Component 어노테이션을 사용하면 스프링이 필터를 자동으로 등록하고, 모든 요청에 대해 필터를 적용할 수 있습니다.
  • FilterRegistrationBean을 사용하면 특정 URL 경로에 필터를 적용하거나, 우선순위 등을 설정할 수 있습니다.

필요에 따라 두 방법 중 하나를 선택해서 사용하면 됩니다. @Component를 사용하는 것이 간단하고 기본적인 설정에 적합하며, FilterRegistrationBean은 세부적인 필터 설정이 필요할 때 유용합니다.


필터는 스프링 애플리케이션의 요청 처리 파이프라인에서 특정 위치에 존재하며, 서블릿 컨테이너 레벨에서 실행됩니다. 즉, 필터는 서블릿이나 스프링의 컨트롤러가 실행되기 전에 요청을 가로채고, 응답이 돌아오기 전에 후처리할 수 있습니다.

필터의 위치

필터는 서블릿 컨테이너(예: Tomcat)에서 동작하는 구조이므로, 스프링 애플리케이션의 요청 처리 과정 중 다음과 같은 위치에서 실행됩니다.

  1. DispatcherServlet 이전: 모든 HTTP 요청이 필터를 거쳐서 스프링의 DispatcherServlet으로 전달됩니다. 필터는 이 과정에서 요청을 가로채거나 수정할 수 있으며, DispatcherServlet이 실행되기 전(또는 후)에 특정 로직을 수행할 수 있습니다.
  2. 인터셉터보다 먼저 실행: 스프링의 HandlerInterceptor보다 먼저 실행됩니다. 필터는 컨트롤러에 요청이 도달하기 전에 요청을 처리하거나 변경할 수 있는 위치에 있습니다.
  3. 서블릿과 비즈니스 로직에 독립적: 필터는 서블릿이나 컨트롤러 로직과는 독립적으로 동작합니다. 필터가 설정된 경로에 대해 모든 요청을 처리할 수 있으며, 보안, 인증, 로깅, 데이터 압축 등 다양한 목적으로 사용됩니다.

스프링 요청 처리 과정에서 필터의 위치

  1. 클라이언트 요청: 브라우저 또는 클라이언트가 HTTP 요청을 보냅니다.
  2. 필터: 서버는 먼저 필터 체인을 통해 요청을 전달합니다. 필터는 이 단계에서 요청을 가로채고, 수정하거나 추가적인 작업을 할 수 있습니다.
  3. DispatcherServlet: 필터를 통과한 요청은 스프링의 DispatcherServlet으로 전달됩니다.
  4. HandlerInterceptor: DispatcherServlet에서 컨트롤러로 요청을 전달하기 전, 인터셉터가 요청을 가로채고 추가 처리를 할 수 있습니다.
  5. 컨트롤러: 마지막으로 요청은 스프링의 컨트롤러로 전달되어, 비즈니스 로직을 수행하고 응답을 반환합니다.
  6. 응답 처리: 응답이 클라이언트로 돌아가는 과정에서도 필터가 다시 실행되어 응답을 처리할 수 있습니다.

필터의 실행 흐름

  • 요청(Request): 필터는 HTTP 요청이 서버에 도착할 때 제일 먼저 실행됩니다. 필터는 요청을 수정하거나, 인증/인가 처리, 로깅 등을 수행할 수 있습니다.
  • 응답(Response): 컨트롤러에서 처리된 응답이 클라이언트로 돌아가기 전에, 필터는 응답을 다시 가로채서 수정하거나, 추가적인 처리를 할 수 있습니다. 예를 들어, 응답을 압축하거나, 헤더를 추가하는 작업을 할 수 있습니다.

필터 체인

필터는 체인(chain) 구조로 구성되어 있으며, 여러 필터가 있는 경우 차례대로 실행됩니다. 하나의 필터가 완료되면 다음 필터가 실행되며, 모든 필터가 실행된 후 요청은 최종적으로 DispatcherServlet 또는 컨트롤러로 전달됩니다.

필터 체인의 흐름

  1. 요청이 필터 A를 통과
  2. 요청이 필터 B를 통과
  3. 요청이 필터 C를 통과
  4. 요청이 DispatcherServlet으로 전달되어 컨트롤러로 이동
  5. 응답이 다시 필터 C, B, A 순으로 역순으로 돌아가면서 처리됨
  6. 최종 응답이 클라이언트로 전송됨

필터의 주요 사용 사례

  • 인증 및 인가: 필터는 사용자 요청을 가로채 인증 및 인가(Authorization) 로직을 처리할 수 있습니다. 예를 들어, 특정 경로에 접근하려면 로그인이 필요한지 확인할 수 있습니다.
  • 로깅 및 모니터링: 모든 요청에 대해 로그를 남기거나, 성능 모니터링을 위한 데이터를 수집할 수 있습니다.
  • 데이터 압축 및 암호화: 응답 데이터에 대해 압축 또는 암호화를 적용할 수 있습니다.
  • CORS 처리: 크로스 도메인 요청(Cross-Origin Resource Sharing, CORS)을 허용하거나 제한하는 데 필터가 사용될 수 있습니다.

필터의 실행 순서 설정

필터의 순서는 FilterRegistrationBean을 사용하여 설정할 수 있으며, 이를 통해 필터의 우선순위를 조정할 수 있습니다.

registrationBean.setOrder(1);  // 우선순위 1로 설정 (숫자가 낮을수록 우선 실행)
  • 여러 필터가 있는 경우 setOrder() 메서드를 통해 우선순위를 지정할 수 있으며, 낮은 숫자일수록 더 먼저 실행됩니다.

Summary

  • 필터는 서블릿 컨테이너 레벨에서 동작하며, 요청이 DispatcherServlet이나 컨트롤러에 도달하기 전에 실행됩니다.
  • 필터는 인증/인가, 로깅, 데이터 처리, 보안 등의 작업을 수행할 수 있으며, 요청과 응답을 모두 처리할 수 있습니다.
  • 필터는 체인 구조로 실행되며, 순서에 따라 필터가 실행된 후 최종적으로 컨트롤러로 요청이 전달됩니다.