[컴] 확장성, 유지보수성, Scalability and Maintainability

Scalability and Maintainability

ref. 1 의 Chapter 1, Reliable, Scalable, and Maintainable Applications

확장성 Scalability

성능에 대한 표현

Scalability > Describing Performance

p.14

  • 응답시간의 평균은 유저들이 일반적으로 어떤 평균 응답시간을 경험했는지를 말해주지 못한다.
  • 이때는 percentiles(백분위수)를 사용하는 것이 낫다.
  • 중앙값(median)은 50번째 백분위수라서 p50 으로 불린다.
  • 중앙값이 1.5ms 라고 하면, 절반은 1.5ms 보다 느렸고, 절반은 1.5ms 보다 빨랐다고 알 수 있다.

p. 15

  • 예를 들어 Amazon은 내부 서비스에 대한 응답 시간 요구 사항을 99.9% 라고 설명한다. 이말은 1,000건의 요청 중 1건에만 영향을 미친다는 뜻이다. 이는 요청이 가장 느린 고객이 종종 계정에 가장 많은 데이터를 보유하고, 구매횟수가 많은 고객이기 때문이다. 즉 가장 가치있는 고객들이다.[19].
  • 반면에 99.99번째 백분위수(요청 10,000건 중 가장 느린 1건)를 최적화하는 것은 비용이 너무 많이 들고 아마존의 목적에 비해 충분한 이점을 얻지 못하는 것으로 간주되었다.
  • 응답 시간이 100밀리초 증가하면 매출이 1% 감소하고[20], 1초 느려지면 고객 만족 지표가 16% 감소한다는 보고도 있다.[21, 22]

p. 16

  • 응답 시간 데이터를 집계하는 올바른 방법은 히스토그램을 추가하는 것
  • 시간 해상도를 낮추거나 여러 머신의 데이터를 결합하기 위해 백분위수를 평균하는 것은 수학적으로 의미가 없다.

부하 다루기

Scalability > Approaches for Coping with Load

  • scale up(수직확장, 더 강력한 머신으로 이동)
  • scale out(수평확장, 여러 대의 작은 머신에 부하를 분산)
  • 탄력적 시스템(elastic system)은 부하를 예측할 수 없는 경우에 유용할 수 있다. 하지만 수동으로 확장하는 시스템이 더 간단하고 운영상의 돌발 상황이 적을 수 있다.


  • ’단일 노드로 된 상태 저장 데이터 시스템(stateful data system)’을 분산 설정으로 전환하면 많은 복잡성이 추가될 수 있다.
  • 일반적인 방법은 ‘확장 비용(scaling cost)’이나 ’고가용성 요건(high availability requirements)’ 들이 데이터베이스를 분산하는 것이 나은 시점이 올때까지 단일 노드에 데이터베이스를 유지(스케일업)하는 것.
  • 그러나 요즘 분산시스템을 구성하기가 더 편리해져서 향후, 대량의 데이터나 트래픽을 처리하지 않는 사용사례에서도 분산형 데이터 시스템이 기본이 될 수 있다.


  • 대규모로 운영되는 시스템의 아키텍처는 일반적으로 애플리케이션에 따라 매우 특수하다.
  • 그래서 모든 경우에 사용가능한 확장 가능한 아키텍처(비공식적으로 마법의 확장 소스라고 알려진)와 같은 것은 존재하지 않는다.
  • 특정 응용을 위해 잘 확장되는 구조는 어떤 작업이 일반적이고 어떤 작업이 드문지에 대한 가정, 즉 부하 매개변수(load parameters)를 기반으로 구축된다. 이러한 가정이 잘못된 것으로 판명되면, scaling 을 위한 노력은 가장 최악의 낭비가 되어 버린다.
  • 그래서 초기스타트업이나, 그렇게 흥행이 되지 않은 제품에서는 미래 부하량에 맞춰서 확장하는 것보단 제품 기능들을 빠르게 재시작(iterate)할 수 있는 것이 더 중요하다.

Maintainability

  • 소프트웨어 비용의 대부분은 지속적인 유지 보수에 발생
    • 버그 수정
    • 시스템 운영 유지
    • 장애 조사
    • 새로운 플랫폼에 맞게 조정
    • 새로운 사용 사례에 맞게 수정
    • 기술 부채 상환
    • 새로운 기능 추가
  • 유지보수시 고통을 최소화하도록 sw를 설계, 그래야 legacy sw 가 되지 않는다.
  • 이를 위한 3가지 설계원칙, 이 원칙을 염두에 두고 시스템을 고민해야 한다.
    • 운영성(Operability) : 운영팀이 쉽게 운영할 수 있도록
    • 단순성(Simplicity) : 새로운 엔지니어가 시스템을 쉽게 이해할 수 있도록
    • 진화가능성(Evolvability) : 확장성(extensibility), 수정가능성(modifiability), 가소성(plasticity) 라고도 부른다.

운영성

  • 좋은 운영성은 ‘일상적인 작업(routine task)을 쉽게 만드는 것’ 그래서 운영팀이 고부가가치 활동에 집중할 수 있도록 하는 것을 의미
  • 좋은 운영팀이 중요
  • ’좋은 운영팀’이란
    • 시스템 상태 모니터링 및 불량 상태 발생 시 신속한 서비스 복구
    • 시스템 장애 또는 성능 저하와 같은 문제의 원인을 추적합니다.
    • 보안 패치를 포함한 소프트웨어와 플랫폼을 최신 상태로 유지
    • 서로 다른 시스템이 서로에게 어떤 영향을 미치는지 파악하여 문제가 되는 변경이 손상을 일으키기 전에 피할 수 있도록 유지보수성 확보
    • 향후 문제를 예측하고 문제가 발생하기 전에 해결(예: 용량 계획)
    • 배포, 구성 관리 등을 위한 모범 사례 및 도구 설정
    • 애플리케이션을 한 플랫폼에서 다른 플랫폼으로 이동하는 것과 같은 복잡한 유지 관리 작업 수행
    • 구성이 변경될 때 시스템의 보안 유지
    • 운영을 예측 가능하게 하고 프로덕션 환경을 안정적으로 유지하는 데 도움이 되는 프로세스 정의
    • 개별 직원이 이동하는 경우에도 시스템에 대한 조직의 지식 유지

routine task 를 쉽게 만드는 방법 :

  • 우수한 모니터링을 통해 런타임 동작 및 시스템 내부에 대한 가시성 제공
  • 자동화 및 표준 도구와의 통합을 위한 우수한 지원 제공
  • 개별 머신에 대한 종속성 방지(시스템 전체가 중단 없이 계속 실행되는 동안 유지보수를 위해 머신을 중단할 수 있음)
  • 좋은 문서이해하기 쉬운 운영 모델 제공(“X를 하면 Y가 발생합니다”)
  • 좋은 기본 동작을 제공하되, 관리자가 필요할 때 기본값을 재정의할 수 있는 자유를 제공해야 합니다.
  • 적절한 경우 자가 복구 기능을 제공하되, 필요한 경우 관리자가 시스템 상태를 수동으로 제어할 수 있어야 합니다.
  • 예측 가능한 동작을 보여줌으로써 돌발 상황 최소화

단순성 Simplicity

  • 시스템을 단순하게 만드는 것은 accidential complexity 를 줄이는 것을 뜻하기도 한다.
  • accidentail complexity 는 소프트웨어가 해결하려고 하는 문제에서 나온 복잡성이 아니라, 단순히 구현에서 나온 복잡성이라고 정의
  • 이것을 줄이는 좋은 방법중 하나가 추상화(abstraction) 이다.


  • 복잡하고 이해하기 어려운 시스템은 시스템작업과 연관된 모든 사람의 작업속도를 떨어뜨린다.
  • 복잡하고 이해하기 어려운 시스템은 유지비용을 증가시킨다.
  • 복잡성 –> 유지보수 어려움 –> 예산, 일정의 초과
  • 복잡성의 다양한 증상
    • state space 의 폭발
    • 모듈의 긴밀한 결합(tight coupling)
    • 얽힌 종속성
    • 일관되지 않은 명명 및 용어
    • 성능문제를 해결하기 위한 hacks
    • 다른 곳의 이슈를 해결하기 위한 특수케이스
  • 변경시 버그가 발생할 위험이 더 크다.
    • 개발자가 시스템을 이해하기 어렵고, 이유를 찾기 어렵다.
    • 복잡한 시스템에선 숨겨진 가정, 의도하지 않은 결과, 예기치 않은 상호작용을 더 간과하기 쉽다.

진화가능성, 변경을 쉽게 Evolvability

  • 데이터 시스템을 수정하는 것과 변화하는 요구 사항에 맞게 조정하는 것을 쉽게 하려면 단순성 및 추상화가 중요하다.
  • 단순하고 이해하기 쉬운 시스템이 수정하기 더 쉽다.

Reference

  1. Designing Data Intensive Applications


[컴] Domain Driven Design(DDD)

 

Domain Driven Design(DDD)

왜 DDD 를 이야기 하는가?

위의 글, Introduction into Domain-Driven Design (DDD), 에 있는 인용문을 보면 DDD 가 말하고자 하는 것을 알 수 있다.

  • 코드에 현실세계를 반영하는 것. 우리의 시스템을 modeling 하는 더 나은 방법이다.

from : Building Microservices by Sam Newman

Eric Evan’s book DDD helped us understand the importance of representing the real world in our code. and showed us better ways to model our systems.

나의 정리

이해한 바를 정리하면, 우리가 우리의 시스템을 우리의 생각대로 그려나갈 수 있지만, 그 그림이 현실세계와 맞닿아 있을때 더 낫다. 그리고 그렇게 시스템을 디자인 하는 것이 DDD 이다.

See Also 4. 를 참고하면, 구체적으로 이 디자인이 어떤식으로 구현되는지를 알 수 있다.

하지만 See Also 5. 의 이야기처럼, DDD 의 기본개념이 뜻하는 바를 이해하는 것이 중요하다. 무조건 DDD에서 제안하는 pattern 이나 기술규칙을 사용하는 것이 DDD가 아니다.

from : Designing a DDD-oriented microservice - .NET | Microsoft Learn

때때로 이러한 DDD 기술 규칙과 패턴은 DDD 접근 방식(DDD aproaches)을 구현하는 데 있어 가파른 학습 곡선을 가진 장애물로 인식되기도 합니다. 하지만 중요한 부분은 패턴 자체가 아니라 코드를 비즈니스 문제에 맞게 구성하고 동일한 비즈니스 용어(유비쿼터스 언어)를 사용하는 것입니다. 또한 DDD 접근 방식(DDD aproaches)은 중요한 비즈니스 규칙이 있는 복잡한 마이크로서비스를 구현하는 경우에만 적용해야 합니다. CRUD 서비스와 같이 더 단순한 책임은 더 간단한 접근 방식으로 관리할 수 있습니다.

Sometimes these DDD technical rules and patterns are perceived as obstacles that have a steep learning curve for implementing DDD approaches. But the important part is not the patterns themselves, but organizing the code so it is aligned to the business problems, and using the same business terms (ubiquitous language). In addition, DDD approaches should be applied only if you are implementing complex microservices with significant business rules. Simpler responsibilities, like a CRUD service, can be managed with simpler approaches.

See Also

  1. What is Domain Driven Design (DDD)? - Stack Overflow
  2. What is Domain Driven Design? - Stack Overflow
  3. Domain Driven Design for Services Architecture | Thoughtworks
  4. Entities and Value Objects: Diving Deep into Domain-Driven Design
  5. Designing a DDD-oriented microservice - .NET | Microsoft Learn

Domain Driven Design(DDD)

왜 DDD 를 이야기 하는가?

위의 글, Introduction into Domain-Driven Design (DDD), 에 있는 인용문을 보면 DDD 가 말하고자 하는 것을 알 수 있다.

  • 코드에 현실세계를 반영하는 것. 우리의 시스템을 modeling 하는 더 나은 방법이다.

from : Building Microservices by Sam Newman

Eric Evan’s book DDD helped us understand the importance of representing the real world in our code. and showed us better ways to model our systems.

나의 정리

이해한 바를 정리하면, 우리가 우리의 시스템을 우리의 생각대로 그려나갈 수 있지만, 그 그림이 현실세계와 맞닿아 있을때 더 낫다. 그리고 그렇게 시스템을 디자인 하는 것이 DDD 이다. 

See Also 4. 를 참고하면, 구체적으로 이 디자인이 어떤식으로 구현되는지를 알 수 있다.

See Also

  1. What is Domain Driven Design (DDD)? - Stack Overflow
  2. What is Domain Driven Design? - Stack Overflow
  3. Domain Driven Design for Services Architecture | Thoughtworks
  4. Entities and Value Objects: Diving Deep into Domain-Driven Design

[컴] web 에서 B site 에서 A server 로 fetch 를 보내고, B로 redirect 를 받은 경우

cors 설정 / redirect fetch / fetch cors

web 에서 B site 에서 A server 로 fetch 를 보내고, B로 redirect 를 받은 경우

  • 다음 sequence diagram 은 만약 내가 site.a.com 에 접속한 상황에서 fetch 로 api.a.com 에 request 를 했다. 그 상황에서 api.a.com 가 redirect site.a.com 를 나에게 던져준 경우이다.
  • 이 경우 예상치 못하게 cors error 가 떴다.

CORS error 가 뜨는 이유

CORS error 가 뜨는 이유는 Origin 이 null 로 set 돼서 site.a.com 로 request 를 보내기 때문이다.

  • 여기를 보면, 처음 request 를 보낸 url에서 바뀐적이 있으면, redirect-tainted origin 으로 본다. 그렇기에 Origin 이 null 로 잡힌다.
  • 이러면 origin 이 ‘null’ 로 바뀌었다고 봐야 하기 때문에 site.a.com 에서 CORS 를 지원하기 위한 설정을 잡아줘야 한다.

다음처럼 api.a.com 으로 보내고 api.a.com 으로 redirect 되는 경우는 다음처럼 CORS error 가 안뜨고, 잘 동작한다.

api.a.com, api.a.com

CORS error 가 안뜨게 수정

  • 아래는 이때 CORS error 가 뜨지 않도록 header 를 맞춰준 예시이다.

조정한 내용:

  • Accee-Control-Allow-Origin: null

  • Access-Control-Allow-Credentials: true

  • Access-Control-Allow-HeadersAccess-Control-Request-Headers 에서 요청한 header를 넣어준다.

  • Access-Control-Allow-MethodsAccess-Control-Request-Method에 맞춰서 지정

  • api.a.com 으로 보내고 site.a.com 으로 redirect 되는 경우 : mermaid link

api.a.com, site.a.com

fetch 를 사용할 때 redirect :

CORS error 가 안뜨게 하려면

CORS error 가 안뜨게 하려면, 다음처럼 설정을 하면 된다.

client :

  • `credentials : ‘omit’
  • mode : 'no-cors'

server :

  • Access-Control-Allowed-Origin: *
  • Access-Control-Allow-Credentials: false

Fetch: Cross-Origin Requests

  • client 에서 Origin을 보내면, 그것을 server가 본다.
  • server 는 그에 따라 Access-Control-Allow-Origin 을 만들어서 response 에 보낸다.
  • browser 가 trusted mediator(신뢰할 수 있는 중재자) 역할을 한다. 즉, browser가 이제 client 에서 보냈던 origin 이랑 서버가 보낸 Access-Control-Allow-Origin 값을 비교해서 값이 적절하면, javascript 가 response 값을 access 할 수 있게 해준다.

javascript 가 기본적으로 접근할 수 있는 safe header 는 다음과 같다.

  • Cache-Control
  • Content-Language
  • Content-Length
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

js 가 다른 header 에 접근하고 싶어도 browser가 접근을 막는다. 다만 server 에서 Access-Control-Expose-Headers 에 추가된 header는 browser가 js 한테 열어준다.

이제는 browser가 안전하지 않은 요청(unsafe request)는 바로 날리지 않는다. preflight request 를 먼저 날리고 나서 날리게 된다.

  • Access-Control-Request-Method : 어떤 method 로 보내려는지 적는다.
  • Access-Control-Request-Headers : 보내려는 request 가 갖고 있는 header들을 적는다.
  • Origin : request 가 어디서 보내는 request 인지를 적게 된다.
200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
Content-Encoding: gzip
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Encoding,API-Key

서버단에서 redirect 시켜서 CORS 피하기

  • preflight 등의 성능을 방해하는 요소를 줄일 수 있다.
                              ┌─►  http://localhost:8081       
   http://localhost:8080      │     ┌─────────────┐            
                              │     │             │            
   http://localhost:8080/api──┘     │   WAS       │            
     ┌─────────────┐                │             │            
     │ web server  │                │             │            
     │     ┌───────┤                │             │            
     │     │       │                └─────────────┘            
     │     │ asset │                                           
     │     │       │                                           
     └─────┴───────┘                                           

[컴][웹] Http Header Origin 이 null 인 경우

spec , origin이 널 / null 인 경우 / why origin null

Http Header Origin 이 null 인 경우

origin 값은 opaque origin 또는 tuple origin 인데, tuple origin 이 정상적인 값으로 ‘http://daum.net’ 같은 값이고, opaque origin (이해하기 힘든 origin)은 null 이라고 보면 된다.

The serialization of ("https", "xn--maraa-rta.example", null, null) is "https://xn--maraa-rta.example".

아래 링크에서 origin 이 null 이 되는 모든 case 를 이야기 해준다.

fetch 를 A에 했는데, B 로 redirect 되는 경우 Origin 은 null

fetch 를 한 경우 다른 origin 으로 redirect 하면 null 이 된다.

See Also

  1. 쿠…sal: [컴] web 에서 B site 에서 A server 로 fetch 를 보내고, B로 redirect 를 받은 경우

[컴] 쿠팡, 판매상품의 재고가 0으로 표시되던 현상, redis 이슈

 에러 / 장애 / 캐시 / 레디스 이슈 / 레디스 버그 /

쿠팡, 판매상품의 재고가 0으로 표시되던 현상, redis 이슈

장애

  • 2019년 7월 24일 오전 7시경부터 쿠팡 판매 상품의 재고가‘0’으로 표시돼, 소비자는 관련 상품의 주문 및 구매할 수 없었다.

장애원인

쿠팡에서 직접적으로 밝히진 않았지만, 장애원인으로 지목되는 것은 다음 이슈이다.

간략하게 이야기하면 item 을 추가할 때 쓰는 dictAddRaw 함수의 버그이다. long long(64bit) 으로 사용해야 할 index를 int 로 사용해서 문제가 발생했다.

Reference

  1. 쿠팡 오류 원인은 오픈소스 ‘레디스 DB’ 때문 < 게임·인터넷 < 기사본문 - 디지털투데이 (DigitalToday), 2019-07-24
  2. 쿠팡 오류 원인은 오픈소스 ‘레디스 DB’ 때문 : 클리앙

[컴] Spring 의 securityWebFilterChain 에서 filter 가 추가되는 과정

스프링 / springboot / 스프링부트 / security

Spring 의 securityWebFilterChain 에서 filter 가 추가되는 과정

  • WebFilterChainProxy.filter()에서 WebFluxSecurityConfiguration.securityWebFilterChains 을 돌면서 실행한다.
  • WebFilterChainProxy@Bean WebFluxSecurityConfiguration.springSecurityWebFilterChainFilter 에 의해서 new 된다.
  • securityWebFilterChains@Autowire setSecurityWebFilterChains(List<SecurityWebFilterChain> securityWebFilterChains) 에 의해 new 된다.
  • WebFluxSecurityConfiguration@EnableWebFluxSecurity 에 의해 불려진다.
  • @EnableWebFluxSecuritySecurityConfig 에 설정돼 있다.
  • @Bean SecurityConfig.securityWebFilterChain 에서 filter를 정하고, ServerHttpSecurity.build() 를 호출하는데, 이 때 filter들이 실제로 추가된다.
    • formLogin 인 경우 login url 인 ‘/login’ 도 설정된다.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig
    @Bean
    SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            ...
            .authorizeExchange(exchange -> exchange
                    ...
                    .and()
                    .formLogin(formLogin -> formLogin
                            .authenticationSuccessHandler(authenticationSuccessHandler)
                            .authenticationFailureHandler(authenticationFailureHandler)
                    )
            );
        return http.build();    // ServerHttpSecurity.build
    }

public class ServerHttpSecurity
    private FormLoginSpec formLogin;
    public SecurityWebFilterChain build() {
        if (this.formLogin != null) {
            if (this.formLogin.authenticationManager == null) {
                this.formLogin.authenticationManager(this.authenticationManager);
            }
            if (this.formLogin.securityContextRepository != null) {
                this.formLogin.securityContextRepository(this.formLogin.securityContextRepository);
            }
            else if (this.securityContextRepository != null) {
                this.formLogin.securityContextRepository(this.securityContextRepository);
            }
            else {
                this.formLogin.securityContextRepository(new WebSessionServerSecurityContextRepository());
            }
            // add filter to the this.webFilters
            this.formLogin.configure(this);
        }
        ...
        AnnotationAwareOrderComparator.sort(this.webFilters);
        List<WebFilter> sortedWebFilters = new ArrayList<>();
        this.webFilters.forEach((f) -> {
            if (f instanceof OrderedWebFilter) {
                f = ((OrderedWebFilter) f).webFilter;
            }
            sortedWebFilters.add(f);
        });
        sortedWebFilters.add(0, new ServerWebExchangeReactorContextWebFilter());

        return new MatcherSecurityWebFilterChain(getSecurityMatcher(), sortedWebFilters);
    }

    public final class FormLoginSpec {
        protected void configure(ServerHttpSecurity http) {
            if (this.authenticationEntryPoint == null) {
                this.isEntryPointExplicit = false;
                loginPage("/login");
            }
            ...
        }
    }


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class,
        ReactiveOAuth2ClientImportSelector.class })
@Configuration
public @interface EnableWebFluxSecurity {

}

@Configuration(proxyBeanMethods = false)
class WebFluxSecurityConfiguration {

    private List<SecurityWebFilterChain> securityWebFilterChains;

    ...

    @Autowired(required = false)
    void setSecurityWebFilterChains(List<SecurityWebFilterChain> securityWebFilterChains) {
        // SecurityConfig.securityWebFilterChain()
        this.securityWebFilterChains = securityWebFilterChains;
    }

    @Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME)
    @Order(WEB_FILTER_CHAIN_FILTER_ORDER)
    WebFilterChainProxy springSecurityWebFilterChainFilter() {
        return new WebFilterChainProxy(getSecurityWebFilterChains());
    }

    private List<SecurityWebFilterChain> getSecurityWebFilterChains() {
        List<SecurityWebFilterChain> result = this.securityWebFilterChains;
        if (ObjectUtils.isEmpty(result)) {
            return Arrays.asList(springSecurityFilterChain());
        }
        return result;
    }


public class WebFilterChainProxy implements WebFilter {
    private final List<SecurityWebFilterChain> filters;

    public WebFilterChainProxy(List<SecurityWebFilterChain> filters) {
        this.filters = filters; // WebFluxSecurityConfiguration.securityWebFilterChains
    }
    ...
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return Flux.fromIterable(this.filters)
                .filterWhen((securityWebFilterChain) -> securityWebFilterChain.matches(exchange)).next()
                .switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
                .flatMap((securityWebFilterChain) -> securityWebFilterChain.getWebFilters().collectList())
                .map((filters) -> new FilteringWebHandler(chain::filter, filters)).map(DefaultWebFilterChain::new)
                .flatMap((securedChain) -> securedChain.filter(exchange));  // securedChain is `DefaultWebFilterChain`
    }
    ...
}
@AutoConfiguration(after = { WebFluxAutoConfiguration.class })
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnMissingBean(HttpHandler.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpHandlerAutoConfiguration {
    @Configuration(proxyBeanMethods = false)
    public static class AnnotationConfig {

        ...

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

    }

}

public final class WebHttpHandlerBuilder{
    public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
        ...
        // set this.filters
        List<WebFilter> webFilters = context
                .getBeanProvider(WebFilter.class)
                .orderedStream()
                .collect(Collectors.toList());
        builder.filters(filters -> filters.addAll(webFilters));
    }
    public HttpHandler build() {
        WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
        decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);

        HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
        ...
        // return adapted
        return (this.httpHandlerDecorator != null ? this.httpHandlerDecorator.apply(adapted) : adapted);
    }
}

public class FilteringWebHandler extends WebHandlerDecorator 
    public FilteringWebHandler(WebHandler handler, List<WebFilter> filters) {
        super(handler);
        this.chain = new DefaultWebFilterChain(handler, filters);
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        return this.chain.filter(exchange);
    }

public class DefaultWebFilterChain implements WebFilterChain
    public DefaultWebFilterChain(WebHandler handler, List<WebFilter> filters) {
        Assert.notNull(handler, "WebHandler is required");
        this.allFilters = Collections.unmodifiableList(filters);
        this.handler = handler;
        DefaultWebFilterChain chain = initChain(filters, handler);
        this.currentFilter = chain.currentFilter;
        this.chain = chain.chain;
    }

    @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));
    }
    private Mono<Void> invokeFilter(WebFilter current, DefaultWebFilterChain chain, ServerWebExchange exchange) {
        String currentName = current.getClass().getName();
        return current.filter(exchange, chain).checkpoint(currentName + " [DefaultWebFilterChain]");
    }

requst 를 처리시점에 호출되는 filter()

  1. HttpWebHandlerAdapter에서 getDelegate().handle(exchange)
  2. ExceptionHandlingWebHandler.handle() 에서 super.handle() 을 호출
  3. FilteringWebHandler.handle()을 호출하게 된다.
  4. FilteringWebHandler.handle()에서 DefaultWebFilterChain.filter(exchange) 를 호출
public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHandler 
    @Override
    public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
        ...
        ServerWebExchange exchange = createExchange(request, response);
        ...
        return getDelegate().handle(exchange)
                .doOnSuccess(aVoid -> logResponse(exchange))
                .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
                .then(Mono.defer(response::setComplete));
    }
public class ExceptionHandlingWebHandler extends WebHandlerDecorator 
    ...
    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        Mono<Void> completion;
        try {
            // WebHandlerDecorator.handle --> FilteringWebHandler.handle
            completion = super.handle(exchange);
        }
        catch (Throwable ex) {
            ...
        }

        for (WebExceptionHandler handler : this.exceptionHandlers) {
            completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
        }
        return completion;
    }

public class WebHandlerDecorator implements WebHandler

    private final WebHandler delegate;

    public WebHandlerDecorator(WebHandler delegate) {
        ...
        this.delegate = delegate;
    }
    ...

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        return this.delegate.handle(exchange);
    }

/login 호출

curl http://localhost:8888/login
  • DefaultWebFilterChain.filter
  • –> AuthenticationWebFilter.filter
  • --> AuthenticationWebFilter.authenticate()
  • --> authenticationManager.authenticate(token)
  • --> AbstractUserDetailsReactiveAuthenticationManager.authenticate