====== Reactive Core ======
===== 설명 =====
스프링-웹 모듈에는 반응형 웹 애플리케이션을 위한 다음과 같은 기본 지원이 포함되어 있다.
* 서버 요청 처리에는 두 가지 수준의 지원한다.
* HttpHandler: 논블로킹 I/O 및 리액티브 스트림 Back Pressure을 사용하는 HTTP 요청을 처리하며, Reactor Netty, Undertow, Tomcat, Jetty 및 모든 Servlet 3.1+ 컨테이너용 어댑터와 함께 사용한다.
* WebHandler API: 요청 처리를 위한 약간 더 높은 수준의 범용 웹 API로, 주석이 달린 컨트롤러 및 기능적 엔드포인트와 같은 구체적인 프로그래밍 모델로 작성되어 있다.
* 클라이언트 사이드에서는 논블로킹 I/O 및 리액티브 스트림 Back Pressure으로 HTTP 요청을 수행하는 기본 ClientHttpConnector 계약과 함께 Reactor Netty, 리액티브 Jetty HttpClient 및 Apache HttpComponents용 어댑터가 있다. 애플리케이션에 사용되는 상위 수준의 WebClient는 이를 기반으로 구축된다.
* 클라이언트와 서버 모두 코덱으로 HTTP 요청 및 응답 콘텐츠의 직렬화 및 역직렬화를 한다.
==== HttpHandler ====
HttpHandler는 요청과 응답을 처리하는 메소드를 하나만 가지고 있다. 의도적으로 최소한의 기능을 제공하며, 주된 목적은 다양한 HTTP 서버 API에 대한 최소한의 추상화이다.\\
지원하는 서버의 API는 아래 표와 같다.
^ 서버 이름 ^ 사용하는 Servlet API ^ 리액티브 스트림 지원 ^
|Netty|Netty API|Reactor Netty|
|Undertow|Undertow API API|spring-web: Undertow to 리액티브 스트림 브릿지|
|Tomcat|서블릿 3.1 논블로킹 I/O; ByteBuffers로 byte[]를 읽고 쓰는 Tomcat API|spring-web: 서블릿 3.1 논블로킹 I/O to 리액티브 스트림 브릿지|
|Jetty|서블릿 3.1 논블로킹 I/O; ByteBuffers로 byte[]를 읽고 쓰는 Jetty API|spring-web: 서블릿 3.1 논블로킹 I/O to 리액티브 스트림 브릿지|
|서블릿 3.1 컨테이너|서블릿 3.1 논블로킹 I/O|spring-web: 서블릿 3.1 논블로킹 I/O to 리액티브 스트림 브릿지|
\\
서버 Dependency는 아래 표와 같다(지원 버전 참고)
^ 서버 이름 ^ Group ID ^ Artifact Name ^
|Reactor Netty|io.projectreactor.netty|reactor-netty|
|Undertow|io.undertow|undertow-core|
|Tomcat|org.apache.tomcat.embed|tomcat-embed-core|
|Jetty|org.eclipse.jetty|jetty-server, jetty-servlet|
\\
다음은 각 서버의 API 어댑터를 활용하는 HttpHandler이다.
=== 리액터 Netty ===
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
=== Undertow ===
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
=== Tomcat ===
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);
Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
=== Jetty ===
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);
Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();
ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
=== 서블릿 3.1+ 컨테이너 ===
서블릿 3.1+ 컨테이너에 WAR를 배포하려면 WAR에 AbstractReactiveWebInitializer를 확장하여 추가하면 된다. 이 클래스는 HttpHandler, ServletHttpHandlerAdapter를 감싸고 있으며, 이 핸들러를 서블릿으로 등록한다.
==== WebHandler API ====
org.springframework.web.server 패키지를 보면 HttpHandler가 WebHandler와 여러 WebExceptionHandler, WebFilter로 체인을 형성해 요청을 처리하는 범용 웹 API를 제공한다.\\
WebHttpHandlerBuilder에 컴포넌트를 등록하거나 스프링 ApplicationContext 위치만 알려주면 자동으로 컴포넌트 체인에 추가한다.\\
HttpHandler는 서로 다른 HTTP 서버를 쓰기 위한 추상화가 전부인 반면, WebHandler API는 아래와 같이 웹 애플리케이션에서 흔히 쓰는 광범위한 기능을 제공한다.
* User session과 Session attributes
* Request attributes.
* Locale, Principal 리졸브
* form 데이터 파싱, 캐싱 조회
* multipart 데이터 추상화
* 기타 등등
=== Form Data ===
ServerWebExchange는 form 데이터에 접근할 수 있는 메소드를 제공한다.
Mono> getFormData();
DefaultServerWebExchange는 설정에 있는 HttpMessageReader를 사용해 form 데이터를 MultiValueMap으로 파싱한다. 디폴트로 사용하는 리더는 ServerCodecConfigurer 빈에 있는 FormHttpMessageReader이다.
=== Multipart Data ===
ServerWebExchange는 multipart 데이터에 접근할 수 있는 메소드를 제공한다.
Mono> getMultipartData();
DefaultServerWebExchange는 설정에 있는 HttpMessageReader>를 사용해 multipart/form-data 컨텐츠를 MultiValueMap으로 파싱한다. 현재로써는 Synchronoss NIO Multipart가 유일하게 지원하는 서브파티 라이브러리이며, 논블로킹으로 multipart 요청을 파싱하는 유일한 라이브러리이다. ServerCodecConfigurer 빈으로 활성화할 수 있다.\\
스트리밍 방식으로 multipart 데이터를 파싱하려면 HttpMessageReader가 리턴하는 Flux를 사용한다. 예를 들어 컨트롤러에서 @RequestPart를 선언하면 Map처럼 이름으로 각 파트에 접근하겠다는 뜻이므로 multipart 데이터를 한번에 파싱해야 한다. 반대로 Flux 타입에 @RequestPart를 사용허면 컨텐츠를 디코딩할 때 MultiValueMap에 수집하지 않는다.
=== Forwarded Headers ===
Load Balancers와 같이 프톡시를 경유한 요청은 호스트, 포트, URL 스킴ㅇ이 변경될 수 있어서 클라이언트 입장에서는 원래의 URL 정보를 알아내기 어렵다.\\
RFC 7239 정의에 따르면 Forwarded HTTP 헤더는 프록시가 원래 요청에 대한 정보를 추가하는 헤다이다. X-Forwarded-Host, X-Forwarded-Port, X-Forwarded-Proto, X-Forwarded-Ssl, and X-Forwarded-Prefix 같은 비표준 헤더도 존재한다.\\
ForwardedHeaderTransformer는 forwarded 헤더를 보고 요청의 호스트, 포트, 스킴을 바꿔준 다음, 헤더를 제거하는 컴포넌트이다. ForwardedHeaderTransformer라는 이름으로 빈을 정의하면 자동으로 체인에 추가된다.\\
forwarded 헤더는 보안에 신경써야 할 요소가 있는데 프록시가 헤더를 추가한건지, 클라이언트가 악의적으로 추가한 것인지 애플리케이션에서는 알 수 없기 때문이다.\\
따라서 외부에서 들어오는 신뢰할 수 없는 프록시 요청을 제거할 때 ForwardedHeaderTransformer를 removeOnly=true로 설정하여 헤더 정보를 사용하지 않고 제거할 수 있다.
5.1 버전부터 ForwardedHeaderFilter는 제거대상(deprecated)이 되어 ForwardedHeaderTransformer롤 대신한다. 따라서 exchange(http 요청/응답과 세션 정보 등의 컨테이너)를 만들기 전에 forwarded 헤더를 처리할 수 있다. 필터를 사용하더라도 이 필터는 전체 필터 리스트에서 제외되며 그 대신 ForwardedHeaderTransformer를 사용한다.
==== Filters ====
WebHandler API에서는 WebFilter를 사용하면 다른 필터 체인과 WebHandler 전후에 요청을 가로채서 원하는 로직을 넣을 수 있다. WebFilter를 등록하려면 스프링 빈으로 만들어 빈 위에 @Order를 선언아거나 Ordered를 구현해 순서를 정해도 되고, WebFlux Config를 사용해도 된다.
=== CORS ===
CORS는 컨트롤러에 어노테이션을 선언하는 것으로 잘 동작한다. Spring Security와 사용하면 CorsFilter를 사용해서 Spring Security 필터 체인보다 먼저 처리하도록 해야 한다.
==== Exceptions ====
WebHandler API에서는 WebFilter 체인과 WebHandler에서 발생한 예외를 WebExceptionHandler로 처리한다. WebExceptionHandler를 등록하려면 스프링 빈으로 빈 위에 @Order를 선언아거나 Ordered를 구현해 순서를 정해도 되고, WebFlux Config를 사용해도 된다.\\
아래 표는 바로 사용할 수 있는 WebExceptionHandler 구현체이다.
^ Exception Handler ^ Description ^
|ResponseStatusExceptionHandler|Http status code를 지정할 수 있는 ResponseStatusException을 처리한다.|
|WebFluxResponseStatusExceptionHandler|ResponseStatusExceptionHandler를 확장하는 것으로 다른 exception 타입도 @ResponseStatus를 선언해서 HTTP status code를 정할 수 있다.\\ 이 핸들러는 WebFlux Config 안에 선언되어 있다.|
===== 참고자료 =====
* [[https://docs.spring.io/spring-framework/docs/5.3.27/reference/html/web-reactive.html#webflux-reactive-spring-web|TThe Spring Framework - Web Reactive - Reactive Core]]