출처: https://typemin.tistory.com/7 [TypeLOG:티스토리]

문제 상황

  • 특정 route의 GatewayFilter를 개별적으로 등록.
  • 보통의 상황에서는 문제가 생기지 않음.
  • 비동기 응답(Streaming Response, Mono, Flux 등)을 받을때는 사전 처리는 잘 되지만 사후 처리는 비동기 응답을 기다리지 않는 상황.

실행시키는 Proxied Service 비동기로 Streaming응답 형식을 하게되고 총 5초가 걸리게 했습니다.
(시간은 이해를 돕기위해 간소화)

0초 API 요청 
0초 GlobalFilter 사전 처리 동작 
0초 GatewayFilter 사전 처리 동작
0초 GatewayFilter 사후 처리 동작
5초 GlobalFilter 사후 처리 동작

즉, StreamResponse의 끝나는 시점에 GatewayFilter가 작동하면 되는 문제였습니다.

왜 그럴까 ?

GlobalFilter의 우선순위는 -1로 설정한 상황이었고.

스프링 공식문서 Combined Global Filter and GatewayFilter Ordering링크로 들어가보면 친절하게 우선순위를 GlobalFilter와 같이 사용하는 것이 있고 GatewayFilter도 똑같이 -1로 주어서 사용해봤더니 실패했습니다.

@Component
public class UserReqeustThrottlingFilter implements GatewayFilter, Ordered {
    private static final String REDIS_UNIQUE_USER = "unique_user::";

    @Autowired
    RedisTemplate<String, Integer> redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       // ...
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

스프링의 공식문서 내용 중 WebHandlerfilterChain을 통해서 요청이 실행된다고 합니다.

Gateway WebHandler의 구현체 FilteringWebHandler

FilteringWebHandler는 내부의 GatewayFilterAdapter를 통해서 실행이 됩니다. 우선순위를 잘 찾아보면

제가 등록한 UserRequestThrottlingFilter가 우선순위가 정상적으로 된 걸 보았는데NettyWriteResponseFilter 보다는 낮을 걸 확인 할 수 있습니다.

NettyWriteResponseFilter클래스가 비동기 응답을 처리하는 필터였고 해당 필터보다 위쪽에 있어야지 제가 원하는 응답을 얻을 수 있습니다.

해결

이제야 문제는 해결됩니다. 이유를 몰랐던 우선순위에 따른 응답이 달라지는 것은 NettyWriteResponseFilter와의 우선순위 문제였고 커스텀한 글로벌 필터는 항상 NettyWriteReponseFilter보다 위 입니다.

하지만 GatewayFilter는 아니었던 거죠 우선순위를 NettyWriteResponseFilterorder값인 -1로 주는 것이 아닌 확실하게 -2를 주면서 문제는 해결됩니다.

index 2 에 위치하게 됩니다.

ㅜㅜ

새로운 Spring Project인 Cloud Gateway에 대해서 알게된지 3일차지만 많은 공부가 되었습니다.
이 문제 해결법이 도움이 됐으면 좋겠네요.

반응형

'Programming > Spring' 카테고리의 다른 글

Spring MVC(2). 프론트컨트롤러(DispatcherServlet)  (0) 2023.04.19
Spring MVC(1). 서블릿  (0) 2023.04.19

프론트 컨트롤러 패턴은 중앙 집중형 컨트롤러를 프레젠테이션 계층의 제일 앞에 둬서 서버로 들어오는 모든 요청을먼저 받아서 처리하게 만든다

사실 프론트 컨트롤러가 받기전에 Filter가 먼저 받긴하는거 같다.

예전 방식의 서블릿들은 클라이언트가 요청을 보내면 서블릿컨테이너가 서블릿을 찾아 매핑해줘 결과를 리턴해준다.

하지만 이 방식은 단점이 아래와 같다.

  • 서블릿이 많아지고 web.xml에 작성해야되는 매핑코드가 많아지면서 유지보수가 어려워진다.
  • 서블릿들은 웹 기술의 종속적이게 되면서 안좋아진다.
  • 서블릿들은 각각의 진입점을 갖기때문에 중복되는 코드를 막을수가 없다.

그래서 해당 일들을 해주는 프론트 컨트롤러 패턴을 고안해냈다(선배 개발자들이) 프론트 컨트롤러 패턴을 하면 좋은점은 웹환경에 종속적이지 않고 순수한 JAVA POJO코드로 컨트롤러를 만들수 있다 . (이는 곧 서블릿이 아니여도 된다는 소리와 같다.)

DispatcherServlet


스프링은 프론트 컨트롤러가 있는데 이것은 DispatcherServlet이라고 한다.

해당 프론트 컨트롤러가 하는 일은 아래와 같다.

  1. 요청을 분석한다.
  2. 핸들러 매핑에게 위임하여 요청을 처리할 핸들러를 찾는다.
  3. 해당 핸들러(컨트롤러)를 실행할수 있는 핸들러 어댑터를 찾는다.
  4. 찾아낸 핸들러 어댑터를 사용해서 컨트롤러로 요청과 응답을 처리한다.
  5. DispatcherServlet은 ViewResolver를 통해 View를 생성한다.
  6. DispatcherSerlvet은 View를 통해 요청결과를 생성 및 반환한다. 
  7. 부가적으로 컨트롤러 실행중에 예외가 발생했다면, 예외처리 핸들러에게 요청처리를 위임한다.

핸들러 매핑


핸들러 매핑은 HTTP 요청정보를 이용해서 이를 처리할 핸들러 오브젝트, 즉 컨트롤러를 찾아주는 기능을 가진 DispatcherServlet의 전략이다.

핸들러 매핑은 컨트롤러의 타입과는 상관없다. 하나의 핸들러 매핑 전략이 여러가지 타입의 컨트롤러를 선택할 수 있다는 뜻이다. (이와 관련된 내용은 핸들러 어댑터에서 다루겠다)

핸들러 인터셉터


핸들러 매핑의 역할은 기본적으로 URL과 요청정보로부터 컨트롤러 빈을 찾아주는 것이다. 한 가지 더 중요한 기능중 핸들러 인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터 역할인 인터셉터의 실행 체인을 돌려준다.

핸들러 어댑터


dispatcherServlet이 가지는 무한한 확장성의 비결

핸들러 매핑이 끝났다면 DispatcherServlet이 어떤 컨트롤러의 메소드를 실행할지 알고있어야 한다. 그렇다면 dispatcherServlet은 호출 가능한 컨트롤러는 특정 인터페이스를 구현해야 한다는 식의 규약을 따라서 작성해야 할까?

그건 아니다. 핸들러 어댑터를 이용하면 된다. 전형적인 오브젝트 어댑터 패턴을 사용해서, 특정 컨트롤러를 호출해야 할때는 해당 컨트롤러 타입을 지원하는 어댑터를 중간에 껴서 호출하는것이다. 그러면 항상 일정한 방식으로 컨트롤러를 호출하고 결과를 받을수 있다.

뷰 리졸버


컨트롤러가 어떤 식으로든 다시 DispatcherServlet에 돌려줘야할 두가지 정보가 있는데 그중 하나가 모델이고 하나가 뷰다. 컨트롤러가 직접 뷰 오브젝트를 리턴할수 있지만, 보통은 DispatcherServlet의 전략이 viewResolver를 통해 뷰 오브젝트를 리턴해준다.

이렇게 되면 View는 어떤 모델을 받아서 Table로 보여주든 Excel로 보여주든 html로 보여주든 자기 입맛대로 할수있다.

핸들러 예외 리졸버


컨트롤러의 작업 중에 발생한 예외를 어떻게 처리할지 결정하는 전략이다.

web.xml에 를 지정해서 에러 안내페이지를 보여줄 수도 있다. 그런데 핸들러 예외 리졸버가 등록되어 있다면 DispatcherServlet은 먼저 예외 리졸버에게 해당 예외를 처리 할수 있는지 확인한다. 핸들러 예외 리졸버가 있다면 예외는 DispatcherServlet 밖으로 던지지 않고 해당 핸들러 예외 리졸버가 처리한다.

 

작성하면서 정리를 하는데도 아직도 혼란스럽습니다.. 혹시 지적해줄곳이 있으시면 꼭 댓글로 알려주시면 감사하겠습니다.

반응형

서블릿이란 동적 웹페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다. 서블릿은 웹 요청과 응답 흐름을 간단한 메소드 호출만으로 체계적으로 다룰수 있게 해준다.

동작과정


위에 그림을 조금만 설명하자면 아래와 같다.

  1. 클라이언트가 요청을 한다.
  2. Http요청을 하면 request, response객체가 생성이된다.
  3. 그리고 비즈니스 로직을 전문으로 처리하는 서버인 WAS(Tomcat) 에 요청을한다.
  4. 서블릿 컨테이너는 요청에 대한 기능을 수행하기 위해 서블릿에게 위임을 하고
  5. 결과를 반환해 클라이언트에게 전달해준다.

서블릿 컨테이너


서블릿 컨테이너는 웹서버와 서블릿이 쉽게 통신할수 있는 환경을 제공 해준다.

웹 애플리케이션 서버는 서블릿컨테이너라는 서브 시스템을 포함하고 있다. 서블릿 컨테이너는 서블릿 라이프 사이클을 관리하고 , 요청에 따라 서블릿을 분기,실행하며, HTTP응답을 생성해내는 역할을 한다. 또한 서블릿 컨테이너는 여러개의 서블릿 인스턴스를 생성하고, 이를 효율적으로 관리하기 위해 스레드 풀과 같은 기능을 제공한다.

 

❓그래도 나는 서블릿 컨테이너가 서블릿을 관리하는것이 이해가 안간다…. WebApplicatoinServer가 바로 관리할수있지 않을까? 알고 계시는 분께선 댓글 남겨주시면 감사합니다..

생명주기


서블릿도 자바 클래스 이므로 실행하면 초기화 ,서비스, 소멸 의 과정을 거친다.

우선 클라이언트에게 요청이 들어오면 컨테이너는 해당 서블릿이 메모리에 로드가되어있는지 확인을 한다. 그런데 없다면 init()메소드를 실행한다.

그리고 service()메소드를 통해 요청에 대한 처리를 위해서 HTTP요청을 분석해 GET,POST 적절한 것으로 분기한다.

그리고 컨테이너가 종료요청을 하면 destroy()메소드가 호출되는데 이것이 서블릿의 생명주기이다.

 

 

반응형

+ Recent posts