개발 일지

Spring Webflux, HttpHandler code 분석

북극곰은콜라 2023. 10. 27. 18:29
반응형


개요

Spring Webflux가 http Request를 받아서 handler를 찾아 mapping, execute 및 response handle하는 과정에 대한 분석을 진행한다.

 


Spring Webflux HttpHandler 분석

HttpHandler

Http 요청에 대한 전반적인 처리를 담당하는 interface
AutoConfiguration으로 Bean으로 만들어지며, Builder를 통해 생성된다.
Builder는 생성된 Bean들을 기반으로 Handler를 생성한다.
따라서 HttpHandler Bean 생성 시점은 매우 늦다 (order = -2147483638)

생성 Flow 요약

1. AutoConfig를 통해 Builder를 생성 (with applicationContext)
2. Builder 내부에 webFliters, exceptionHandler, httpHandlerDecorator, sessionManager, codecConfigurer, contextResolver, forwardHeaderTransformer 할당 (load from bean)
  a. HttpHandlerDecoratorFactory Bean을 통해서 httpHandlerDecorator를 load
  b. Decorator 패턴으로 구현된 HttpHandlerDecorator를 Fuction으로 merge
3. Builder를 통해 HttpHandler Build
  a. DispatcherHandler를 FilteringWebHandler에 delegate 할당
  b. FilteringWebHandler를 ExceptionHandlingWebHandler에 delegate 할당
  c. ExceptionHandlingWebHandler를 HttpWebHandlerAdapter에 delegate 할당
4. 기타 webflux 설정

동작 Flow 요약

1. ServerHttpRequest 수신
2. Exchange 생성
3. ExceptionHandler 추가 (onErrorResume)
  a. 추가 후 다음 handler 호출
4. FilterHandler에서 filterChain으로 filter(exchange)
  a. filter 후 다음 handler 호출
5. DispatcherHandler에서 exchange Handling
  a. HandlerMapping을 통해 target handler 찾기
  b. handler를 support하는 adapter를 통해 handler(exchange)
  c. handle 결과에 대해서 처리 (rest응답, viewModel 등…)
더보기
// HttpHandlerAutoConfiguration
@AutoConfiguration(
  after = {WebFluxAutoConfiguration.class}
)
@ConditionalOnClass({DispatcherHandler.class, HttpHandler.class})
@ConditionalOnWebApplication(
  type = Type.REACTIVE
)
@ConditionalOnMissingBean({HttpHandler.class})
@AutoConfigureOrder(-2147483638)
public class HttpHandlerAutoConfiguration {
  ...

  @Bean
  public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
    HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
    ...
  }

  ...
}
// WebHttpHandlerBuilder
// builder 생성 시점에 Bean들을 load하여, build 시점에 HttpHandler에 적용한다.

// applicationContext를 통한 생성
public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
  // webHandler(Dispatcher) 기반 Builder 생성
  WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder((WebHandler)context.getBean("webHandler", WebHandler.class), context);
  
  // WebFilter Bean 모두 가져와서 정렬 후 추가
  List<WebFilter> webFilters = context.getBeanProvider(WebFilter.class).orderedStream().toList();
  builder.filters((filters) -> {
    filters.addAll(webFilters);
  });
  
  // exceptionHandler들 로드해서 정렬 후 추가
  List<WebExceptionHandler> exceptionHandlers = context.getBeanProvider(WebExceptionHandler.class).orderedStream().toList();
  builder.exceptionHandlers((handlers) -> {
    handlers.addAll(exceptionHandlers);
  });
  
  // Decorator 모두 가져와서 정렬 후 추가
  Stream var10000 = context.getBeanProvider(HttpHandlerDecoratorFactory.class).orderedStream();
  Objects.requireNonNull(builder);
  var10000.forEach(builder::httpHandlerDecorator);

  // sessionManager 로드
  try {
    builder.sessionManager((WebSessionManager)context.getBean("webSessionManager", WebSessionManager.class));
  } catch (NoSuchBeanDefinitionException var8) {
  }

  // codecConfigurer 로드
  try {
    builder.codecConfigurer((ServerCodecConfigurer)context.getBean("serverCodecConfigurer", ServerCodecConfigurer.class));
  } catch (NoSuchBeanDefinitionException var7) {
  }

  // localeContextResolver 로드
  try {
    builder.localeContextResolver((LocaleContextResolver)context.getBean("localeContextResolver", LocaleContextResolver.class));
  } catch (NoSuchBeanDefinitionException var6) {
  }

  // forwardedHeaderTransformer 로드
  try {
    builder.forwardedHeaderTransformer((ForwardedHeaderTransformer)context.getBean("forwardedHeaderTransformer", ForwardedHeaderTransformer.class));
  } catch (NoSuchBeanDefinitionException var5) {
  }

  return builder;
}

// HttpHandler build
public HttpHandler build() {
  // webHandler Merge (DispathcerHandler + ExceptionHandlingWebHandler + FilteringWebHandler)
  WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
  WebHandler decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers);
  HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
  if (this.sessionManager != null) {
    adapted.setSessionManager(this.sessionManager);
  }

  if (this.codecConfigurer != null) {
    adapted.setCodecConfigurer(this.codecConfigurer);
  }

  if (this.localeContextResolver != null) {
    adapted.setLocaleContextResolver(this.localeContextResolver);
  }

  if (this.forwardedHeaderTransformer != null) {
    adapted.setForwardedHeaderTransformer(this.forwardedHeaderTransformer);
  }

  if (this.applicationContext != null) {
    adapted.setApplicationContext(this.applicationContext);
  }

  adapted.afterPropertiesSet();
  return (HttpHandler)(this.httpHandlerDecorator != null ? (HttpHandler)this.httpHandlerDecorator.apply(adapted) : adapted);
}

HttpWebHandlerAdapter (implements HttpHandler)

Http Request 처리를 Decorate할 수 있는 Handler 구현체이다.
핵심 메서드는 interface의 handle 메서드와 createExchange가 있다.
1. request, response, filter 설정등을 바탕으로 exchange 생성
2. (let delegate) handle exchange
실제 exchange의 Handling 처리는 delegate를 통해 진행
default: ExceptionHandlingWebHandler → FilteringWebHandler → DispatcherHandler

createExchange(…)

protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
  return new DefaultServerWebExchange(request, response, this.sessionManager, this.getCodecConfigurer(), this.getLocaleContextResolver(), this.applicationContext);
}
ServerHttpRequest + 기타 handler들 → exchange 생성

handle(...)

public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
  if (this.forwardedHeaderTransformer != null) {
    try {
      request = this.forwardedHeaderTransformer.apply(request);
    } catch (Throwable var4) {
      ...
    }
  }

  ServerWebExchange exchange = this.createExchange(request, response);
  ...
  Mono var10000 = this.getDelegate().handle(exchange).doOnSuccess((aVoid) -> {
    this.logResponse(exchange);
  })
  ...
  return var10000.then(Mono.defer(response::setComplete));
}
1. request의 Header 처리 (Transformer)
2. exchange 생성
3. reqeust logging
4. delegate (Decorator)를 통한 exchange handle
5. success / fail 처리 (response logging, etc)
6. response complete

ExceptionHandlingWebHandler

delegate handle에서 발생한 error 시그날을
onErrorResume으로 load 된 WebExceptionHandler 처리한다.

FilteringWebHandler

private final DefaultWebFilterChain chain;

public Mono<Void> handle(ServerWebExchange exchange) {
  return this.chain.filter(exchange);
}
FilterChain으로 exchange를 filter
filter 후 delegate(DispatcherHandler)로 handle() 넘김

DispatcherHandler

 
// DispathcerHandler
public Mono<Void> handle(ServerWebExchange exchange) {
  if (this.handlerMappings == null) {
    return this.createNotFoundError();
  } else {
    return CorsUtils.isPreFlightRequest(exchange.getRequest()) ? this.handlePreFlight(exchange) : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
      return mapping.getHandler(exchange);
    })
    .next()
    ...
    }).flatMap((handler) -> {
      return this.handleRequestWith(exchange, handler);
    });
  }
}

private Mono<Void> handleRequestWith(ServerWebExchange exchange, Object handler) {
  if (ObjectUtils.nullSafeEquals(exchange.getResponse().getStatusCode(), HttpStatus.FORBIDDEN)) {
    return Mono.empty();
  } else {
    if (this.handlerAdapters != null) {
      Iterator var3 = this.handlerAdapters.iterator();

      while(var3.hasNext()) {
        HandlerAdapter adapter = (HandlerAdapter)var3.next();
        if (adapter.supports(handler)) {
          return adapter.handle(exchange, handler).flatMap((result) -> {
            return this.handleResult(exchange, result);
          });
        }
      }
    }

    return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
  }
}
1. HandlerMapping을 통해 Handler를 찾아온다.
 - 없으면 404
2. 현재 handler를 support 하는 HandlerAdapter를 찾아와서 handle()
3. handlerAdapter는 @RequestMapping, RouterFunction등으로 만들어진 Bean
4. Adapter는 각 Handler의 handle()을 호출
 - exchange → 각 handle()의 파라미터로
5. Adapter는 handler의 return 값을 HandlerResult에 담아서 return
6. resultHandler들 중에서 support하는 resultHandler를 찾아서 handlerResult
 

Webflux HttpHandler 구현 아이디어

목표 (추정)

1. 여러 하위 로직을 합쳐서 하나의 handling 로직을 만들고 싶다
2. handling 로직을 확장성 있게 가져가고 싶다. (기존 로직에 추가하거나, 다음 단계로 놓거나..)

아이디어

1. Decorator 패턴을 이용해서 특정 로직에 기능을 추가할 수 있는 구조를 갖는다.
2. Delegate 패턴을 이용해서 외부에서 위임 클래스 로직을 호출할 수 있게한다.
3. Adapter 패턴을 통해 decorate된 클래스를 최종 클래스에서 사용한다.
   // 이 부분은 다른 프로젝트에서 만든 httpHandler에 WebHandler를 붙이기 위함

코드 분석

public interface HttpHandler {
  Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);
}

public interface WebHandler {
  Mono<Void> handle(ServerWebExchange exchange);
}
조합 및 확장이 필요한 기능
 - 여러 HttpHandler 모듈에서 exchange를 받아서 req 처리 → res 처리

Decorator 패턴 적용

public HttpHandler build() {
  WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
  WebHandler decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers);
  ...
}
확장이 필요한 기능은 WebHandlerDecorator를 확장하여 handle() 메서드를 구현한다.
Concrete는 DispatcherHandler에 해당되며, ExceptionHandlingWebHandler, FilteringWebHandler 등은 decorate class에 해당된다.

Delegate 패턴 적용

// httpHandler에서 ExceptionHandler 호출
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
	...
	return getDelegate().handle(exchange)
			.doOnSuccess(aVoid -> logResponse(exchange))
			.onErrorResume(ex -> handleUnresolvedError(exchange, ex))
			.then(Mono.defer(response::setComplete));
}

// ExceptionHandler에서 super(Decorator)를 통해 FilteringHandler 호출
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
    ...
	completion = super.handle(exchange);
	...
}
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
	return this.delegate.handle(exchange);
}

// FilteringHandler에서 DefaultWebFilterChain을 통해 DispatcherHandler 호출
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
	return this.chain.filter(exchange);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
	return Mono.defer(() ->
			this.currentFilter != null && this.chain != null ?
					invokeFilter(this.currentFilter, this.chain, exchange) :
					this.handler.handle(exchange));
}
Decorator class에 delegate가 있다.
앞쪽 클래스에서 자신의 로직 처리 후 delegate를 통해 다음 webHandler 처리를 위임한다.

Adapter를 통한 WebHandler를 HttpHandler에 적용

WebHandler + HttpHandler
 

Conclusion

<HttpHandler>
HttpHandler는 Http Request를 처리하는 목적으로 만든 인터페이스이다.
Spring Webflux는 httpHandler를 통해서 http request를 handling한다.
HttpHandler를 확장하여 구현하면, 기본 제공하는 handler를 교체할 수 있다.

<WebHandler>
WebHandler는 web에서의 request 처리를 주 목적으로 만든 인터페이스이다.
WebHandler를 구현한 핵심 클래스는 DispatcherHandler 이다.
DispatcherHandler는 request dispatcher에 대한 기능을 구현했다.

<HttpHander + WebHandler>
두 인터페이스를 Adapter 패턴으로 합쳤다.
HttpHandler에서 WebHandler의 인터페이스를 호출한다.

<WebHandler 동작 구조>
기본적으로 DispatcherHandler를 호출하게 되며
dispatch 이외의 기능은 decorator 패턴과 delegate 패턴으로 확장했다.
decorator 클래스에서 delegate로 dispatcher 또는 다른 decorator들을 가지고 있으며, delegate 호출 전 후로 기타 로직들을 넣었다.
위의 Adapter또한 decorator로 다른 decorator를 delegate로 가지고 있다가 호출한다.

<기본 flow>
WehHandlerAdapter -> ExcpetionHandlingWebHandler -> FilteringWebHandler -> DispatcherHandler
Decorator, Delegate 패턴으로 기능의 확장을 확인할 수 있었다.
http handle 과정에서 추가로직을 확장하기 쉬운 구조이다.
반응형