1.2. Reactive Core

편집일시: 2021-08-29 18:40 조회수: 511 댓글수: 0
`spring-web` 모듈에는 리액티브 Web 응용 프로그램에 대한 다음의 기본적인 지원이 포함되어 있다. - 서버 요청 처리에는 두 가지 레벨의 지원이 있다. - [`HttpHandler`](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-httphandler) : Reactor Netty, Undertow, Tomcat, Jetty와 같은 Servlet 3.1+ 컨테이너 용 어댑터와 함께 논블록킹 I/O 및 Reactive Streams 역압력를 사용하여 HTTP 요청을 처리하는 기본 규약이다. - [`WebHandler` API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api) : 요청 처리를 위한 약간 높은 수준의 범용 Web API. 그리고, 어노테이션이 선언된 컨트롤러와 함수 엔드 포인트 등의 구체적인 프로그래밍 모델이 구축된다. - 클라이언트는 non-blocking I/O와 Reactive Streams 역압력을 사용하여 HTTP 요청을 실행하기 위한 기본적인 `ClientHttpConnector` 계약과 [Reactor Netty](https://github.com/reactor/reactor-netty), 리액티브 [Jetty HttpClient](https://github.com/jetty-project/jetty-reactive-httpclient) 및 [Apache HttpComponents](https://hc.apache.org/)의 어댑터가 있다. 응용 프로그램에서 사용되는 높은 수준의 [WebClient](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client)에서 계약을 기반으로 구축되어 있다. - 클라이언트 및 서버의 경우 HTTP 요청 및 응답 내용을 serialize 및 deserialize 용 [코덱](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs). ## 1.2.1. HttpHandler [`HttpHandler`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/http/server/reactive/HttpHandler.html)는 요청과 응답을 처리하는 단일의 메서드를 가지는 단순한 규칙이 가지고 있다. 이것은 의도적으로 최소화 할 수 있으며, 그 주요 유일한 목적은 다양한 HTTP 서버 API를 최소한으로 추상화하는 것이다. 다음 표에는 지원되는 서버 API를 제공한다. | 서버 이름 | 사용되는 서버 API | Reactive Streams 지원 | |--|--|--| | Netty | Netty API | [Reactor Netty](https://github.com/reactor/reactor-netty) | | Undertow | Undertow API |spring-web : Undertow에서 Reactive Streams 브리지| | Tomcat | Servlet 3.1 non-blocking I/O. ByteBuffers와 byte []를 읽고 쓰는 Tomcat API | spring-web : Servlet 3.1 non-blocking I/O에서 Reactive Streams 브리지 | | Jetty | Servlet 3.1 non-blocking I/O. ByteBuffers vs byte []를 작성 Jetty API | spring-web : Servlet 3.1 non-blocking I/O에서 Reactive Streams 브리지 | | Servlet 3.1 컨테이너 | Servlet 3.1 non-blocking I/O | spring-web : Servlet 3.1 non-blocking I/O에서 Reactive Streams 브리지 | 다음 표에는 서버의 종속성을 표시한다. ([지원되는 버전](https://github.com/spring-projects/spring-framework/wiki) 참조) | 서버 이름 | 그룹 Id | Artifact 이름 | |--|--|--| | 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` 어댑터를 사용하는 것을 보여준다. ### Reactor Netty Java ``` HttpHandler handler = ... ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer.create().host(host).port(port).handle(adapter).bind().block(); ``` Kotin ``` val handler: HttpHandler = ... val adapter = ReactorHttpHandlerAdapter(handler) HttpServer.create().host(host).port(port).handle(adapter).bind().block() ``` ### Tomcat Java ``` 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(); ``` Kotin ``` val handler: HttpHandler = ... val servlet = TomcatHttpHandlerAdapter(handler) val server = Tomcat() val base = File(System.getProperty("java.io.tmpdir")) val rootContext = server.addContext("", base.absolutePath) Tomcat.addServlet(rootContext, "main", servlet) rootContext.addServletMappingDecoded("/", "main") server.host = host server.setPort(port) server.start() ``` ### Jetty Java ``` 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(); ``` Kotin ``` val handler: HttpHandler = ... val servlet = JettyHttpHandlerAdapter(handler) val server = Server() val contextHandler = ServletContextHandler(server, "") contextHandler.addServlet(ServletHolder(servlet), "/") contextHandler.start(); val connector = ServerConnector(server) connector.host = host connector.port = port server.addConnector(connector) server.start() ``` ### Servlet 3.1+ 컨테이너 Servlet 3.1+ 컨테이너에 WAR로 배포하려면 [AbstractReactiveWebInitializer](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/server/adapter/AbstractReactiveWebInitializer.html)을 확장하여 WAR에 포함 할 수 있다. 그 클래스는 `HttpHandler`를 `ServletHttpHandlerAdapter`으로 감싸서, 그것을 Servlet으로 등록한다. ## 1.2.2. WebHandler API `org.springframework.web.server` 패키지는 `HttpHandler` 을 기반으로 여러 [`WebExceptionHandler`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/server/WebExceptionHandler.html)와 여러 [`WebFilter`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/server/WebFilter.html), 단일[ WebHandler](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/server/WebHandler.html) 컨포넌트의 체인을 통해 요청을 처리하기 위한 범용 Web API를 제공한다. 체인 컨포넌트를 [자동으로 감지](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api-special-beans)하는 Spring `ApplicationContext`를 지정하거나 컨포넌트 빌더에 등록하는 것만으로 `WebHttpHandlerBuilder`와 결합 할 수 있다. `HttpHandler`에는 다양한 HTTP 서버의 사용을 추상화한다는 목적이 있지만, `WebHandler` API는 다음과 같은 Web 응용 프로그램에서 일반적으로 사용되는 광범위한 기능 세트를 제공하는 것을 목적으로 한다. - 특성을 가진 사용자 세션. - 요청 속성. - 요청의 `Locale` 또는 `Principal`를 해결됨. - 분석 및 캐시 된 폼 데이터에 액세스. - 다중 데이터 추상화. - 기타 등등.. ### 특별한 Bean 형 다음 표는 `WebHttpHandlerBuilder`가 Spring ApplicationContext에서 자동 검출 할 수있는 컨포넌트 또는 직접 등록 할 수 있는 컨포넌트의 목록이다. | Bean 이름 | Bean 형 | 카운트 | 설명| |--|--|--|--| | \<any\> | `WebExceptionHandler` | 0..N | `WebFilter` 인스턴스의 체인 및 대상의 `WebHandler`에서 예외 처리를 제공한다. 자세한 내용은 [예외](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-exception-handler)를 참조해라. | | \<any\> | `WebFilter` | 0..N | 필터 체인의 나머지와 대상의 `WebHandler `전후에 인터셉트 스타일 로직를 적용한다. 자세한 내용은 [필터](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-filters)를 참조해라. | | `webHandler` | `WebHandler` | 1 | 요청 핸들러. | | `webSessionManager` | `WebSessionManager` | 0..1 |`ServerWebExchange`의 방법을 통해 공개되는 ` WebSession` 인스턴스 매니저. 디폴트는 `DefaultWebSessionManager`. | | `serverCodecConfigurer` | `ServerCodecConfigurer` | 0..1 | 폼 데이터와 멀티 파트 데이터를 분석하기 위한 `HttpMessageReader` 인스턴스에 액세스. 이 데이터는 `ServerWebExchange`의 메서드를 통해 공개된다. 디폴트는 `ServerCodecConfigurer.create()`. | | `localeContextResolver` | `LocaleContextResolver` | 0..1 |` ServerWebExchange`의 방법을 통해 공개된다 LocaleContext의 확인자. 디폴트는 `AcceptHeaderLocaleContextResolver`.| | `forwardedHeaderTransformer` | `ForwardedHeaderTransformer` | 0..1 | 전송 된 유형 헤더를 처리하려면 추출하여 제거하거나 삭제하면 된다. 디폴트는 사용되지 않는다. | ### 폼 데이터(Form Data) `ServerWebExchange` 폼 데이터에 액세스하기 위해 다음과 같은 메서드을 제공한다. Java ``` Mono<MultiValueMap<String, String>> getFormData(); ``` Kotin ``` suspend fun getFormData(): MultiValueMap<String, String> ``` `DefaultServerWebExchange`는 구성된 `HttpMessageReader`를 사용하여 폼 데이터 (`application/x-www-form-urlencoded`)을 분석하고 `MultiValueMap`한다. 디폴트로 `FormHttpMessageReader`는 `ServerCodecConfigurer` Bean을 사용하도록 구성되어 있다 ([Web 핸들러 API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api) 참조). ### 멀티파트 데이터(Multipart Data) [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-multipart) ServerWebExchange는 다중 데이터에 액세스하기 위해 다음과 같은 메서드을 제공한다. Java ``` Mono<MultiValueMap<String, Part>> getMultipartData(); ``` Kotin ``` suspend fun getMultipartData(): MultiValueMap<String, Part> ``` `DefaultServerWebExchange`는 구성된` HttpMessageReader<MultiValueMap<String, Part>>`를 사용하여 `multipart/form-data` 컨텐츠를 `MultiValueMap`로 파싱한다. 디폴트는 `DefaultPartHttpMessageReader`이며, 서드파티 의존성은 없다. 또는 [Synchronoss NIO Multipart](https://github.com/synchronoss/nio-multipart) 라이브러리에 기반에 `SynchronossPartHttpMessageReader`을 사용할 수 있다. 모두 `ServerCodecConfigurer` Bean을 통해 구성된다([WebHandler API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api) 참조). 스트리밍 형식으로 멀티파트 데이터를 파싱하려면 `HttpMessageReader<Part>`에서 반환되는 `Flux<Part>`를 사용하면 된다. 예를 들어, 어노테이션이 선언된 컨트롤러에서 `@RequestPart` 사용하면 `Map`와 같이 이름으로 각 파트에 접근하는 것을 의미하기 때문에 멀티 파트 데이터를 완벽하게 파싱해야 한다. 대조적으로, `@RequestBody`를 사용하여 `MultiValueMap` 수집하지 않고 컨텐츠를 `Flux<Part>`로 디코딩 할 수 있다. ### Forwarded 헤더 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-forwarded-headers) 요청이 프록시(로드 밸런서 등)을 통할 때, 호스트, 포트, 스키마가 변경 될 수 있다. 따라서 클라이언트의 관점에서 올바른 호스트, 포트, 스키마를 가리키는 링크를 만들기 어렵다. [RFC 7239](https://tools.ietf.org/html/rfc7239)에 따르면 프록시가 원래의 요청에 대한 정보를 제공하는데 사용할 수 있는 Forwarded HTTP 헤더를 정의한다. `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Proto`, `X-Forwarded-Ssl`, `X-Forwarded-Prefix` 등 다른 비표준 헤더도 있다. `ForwardedHeaderTransformer`는 전송 된 헤더에 따라 요청 호스트, 포트, 스키마를 변경하고, 해당 헤더를 제거하는 컨포넌트이다. `forwardedHeaderTransformer`라는 Bean으로 선언하면 자동 감지되어 사용할 수 있다. 전송 된 헤더에는 의도 한대로 프록시는 헤더가 추가되었는지, 악의적인 클라이언트에 의해 헤더가 추가되었는지를 응용 프로그램이 인식 할 수 없기 때문에 보안 고려 사항이 있다. 이것이 외부에서 신뢰할 수 없는 전송 트래픽을 삭제하도록 신뢰의 경계에있 는 프록시를 구성해야 하는 이유이다. `ForwardedHeaderTransformer`을` removeOnly=true`로 구성 할 수 있다. 이 경우 헤더는 사용되지 않고 제거될 수 있다. > 5.1 버전에서는 교환이 생성되기 전에 전송 된 헤더를 더 빨리 처리 할 수 있도록 `ForwardedHeaderFilter`는 사용되지 않으며, `ForwardedHeaderTransforme`r로 대체되었다. 필터가 구성되어 있는 경우 필터 목록에서 제외되고 대신 `ForwardedHeaderTransformer`가 사용된다. > 5.1 버전에서는 `ForwardedHeaderFilter`는 더 이상 사용되지 않도록 deprecated가 선언 되었고, forwarded 헤더를 더 빨리 처리 할 수 있도록 `ForwardedHeaderTransformer`으로 대체되었다. 필터가 구성되어 있는 경우 필터 목록에서 제외되고 대신 `ForwardedHeaderTransformer`가 사용된다. ## 1.2.3. 필터(Filters) [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters) [`WebHandler` API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api)는 `WebFilter`를 사용하여 필터의 나머지 부분 체인 및 대상이 되는 `WebHandler` 전후에 인터셉션 로직을 적용 할 수 있다. [WebFlux 구성](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-config)을 사용하는 경우, `WebFilter` 등록은 Spring Bean으로 선언하거나, (옵션으로) Bean 선언에 `@Order`를 사용하거나 `Ordered`을 구현하여 우선 순위를 간단히 정할 수도 있다. ### CORS [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-cors) Spring WebFlux는 컨트롤러의 어노테이션을 통해 CORS 구성의 상세한 지원을 제공한다. 단, Spring Security에서 사용하는 경우는 Spring Security 체인 필터보다 먼저 정렬해야 하는 내장 `CorsFilter`을 사용하는 것이 좋다. 자세한 내용은 [CORS](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-cors) 및 webflux-cors.html 섹션을 참조해라. ## 1.2.4. 예외(Exceptions) [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-customer-servlet-container-error-page) [`WebHandler` API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api)는 `WebExceptionHandler`를 사용하여 `WebFilter` 인스턴스 체인 및 대상이 되는 `WebHandler`에 예외를 처리 할 수 있다. WebFlux 구성을 사용하는 경우, `WebExceptionHandler` 등록은 Spring Bean으로 선언하거나, (옵션으로) Bean 선언 `@Order`를 사용하거나 `Ordered`을 구현하여 우선 순위를 간단히 정할 수도 있다. 다음 표에서는 사용 가능한 `WebExceptionHandler` 구현을 설명하고 있다. | 예외 핸들러 | 설명| |--|--| | `ResponseStatusExceptionHandler` | 예외의 HTTP 상태 코드에 대한 응답을 설정하여 [`ResponseStatusException`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/server/ResponseStatusException.html) 예외 처리를 제공한다. | | `WebFluxResponseStatusExceptionHandler` | `@ResponseStatus` 어노테이션의 HTTP 상태 코드도 예외 결정할 수 있는 `ResponseStatusExceptionHandler` 확장. 이 핸들러는 WebFlux 구성에 선언할 수 있다. | ## 1.2.5 코덱(Codecs) [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-message-conversion) `spring-web `및 `spring-core` 모듈은 Reactive Streams 역 압력을 사용한 non-blocking I/O를 통해, 높은 수준의 객체 사이의 바이트 컨텐츠를 직렬화 및 역 직렬화하는 지원을 제공한다. 이 지원은 아래에서 설명한다. - [`Encoder`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/core/codec/Encoder.html)와 [ `Decoder`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/core/codec/Decoder.html)는 HTTP에 관련 없이 컨텐츠를 인코딩 및 디코딩한다. - [`HttpMessageReader`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/http/codec/HttpMessageReader.html)와 [ `HttpMessageWriter`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/http/codec/HttpMessageWriter.html)는 HTTP 메시지 컨텐츠를 인코딩 및 디코딩한다. - `Encoder`는 `EncoderHttpMessageWriter`를 `Decoder`는 `DecoderHttpMessageReader`를 감싸고 있어 Web 응용 프로그램의 사용에 적용 할 수 있다. - [`DataBuffer`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/core/io/buffer/DataBuffer.html)는 다양한 바이트 버퍼 표현 (Netty `ByteBuf`, `java.nio.ByteBuffer` 등)을 추상화하고 모든 코덱이 작동하는 것이다. 이 주제에 대한 자세한 내용은 "Spring 코어"섹션의 [데이터 버퍼 및 코덱](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#databuffers)을 참조해라. `spring-core` 모듈은 `byte[]`, `ByteBuffer`, `DataBuffer`, `Resource`, `String` 인코더 및 디코더의 구현을 제공한다. `spring-web` 모듈은 Jackson JSON, Jackson Smile, JAXB2, Protocol Buffers 다른 인코더와 디코더, 폼 데이터, 멀티 파트 컨텐츠, 서버 전송 이벤트 등의 Web 전용 HTTP 메시지 Reader와 Writer 구현을 제공한다 . `ClientCodecConfigurer` 및 `ServerCodecConfigurer`는 일반적으로 응용 프로그램에서 사용하는 코덱 구성 및 사용자 지정하는데 사용된다. [HTTP 메시지 코덱](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-config-message-codecs) 설정 섹션을 참조해라. ### Jackson JSON JSON과 바이너리 JSON ([Smile](https://github.com/FasterXML/smile-format-specification))는 Jackson 라이브러리가 존재하는 경우에 지원된다. `Jackson2Decoder`는 다음과 같이 작동한다. - Jackson의 비동기 논 블로킹 파서는 바이트 청크 스트림을 각각 JSON 객체를 나타내는 `TokenBuffer`에 통합하는데 사용된다. - 각 `TokenBuffer`는 Jackson의 `ObjectMapper`에 전달되어, 보다 더 높은 레벨의 객체를 만든다. - 단일 값 게시자 (예 : `Mono`)로 디코딩하는 경우 하나의 `TokenBuffer`가 있다. - 다중 값 퍼블리셔 (`Flux` 등)으로 디코딩하는 경우, 완전히 형성된 객체에 대해 충분한 바이트가 수신 되자마자 각 `TokenBuffer`이 `ObjectMapper`에 전달된다. 입력 내용은 JSON 배열 또는 NDJSON, JSON 행, JSON 텍스트 시퀀스 등의 [줄 바꿈(line-delimited) JSON](https://en.wikipedia.org/wiki/JSON_streaming) 형식으로 할 수 있다. `Jackson2Encoder`는 다음과 같이 작동한다. - 단일 값 게시자 (예 : `Mono`)의 경우, 간단히 `ObjectMapper`를 통해 직렬화한다. - `application/json`을 사용하는 다중 값 퍼블리셔의 경우는 디폴트로 `Flux#collectToList()`를 사용하여 값을 수집하고 결과의 컬렉션을 직렬화한다. - `application/x-ndjson`이나 `application/stream+x-jackson-smile` 등의 스트리밍 미디어 유형을 가진 여러 값 퍼블리셔의 경우는 [줄 바꿈(line-delimited) JSON](https://en.wikipedia.org/wiki/JSON_streaming) 형식을 사용하여, 값을 개별적으로 인코딩 쓰기 플러시(flush)한다. 다른 스트리밍 미디어 타입은 엔코더에 등록 될 수 있다. - SSE의 경우 `Jackson2Encoder`는 이벤트마다 불려 출력은 플러시되고 지체없이 전달된다. > 기본적으로 `Jackson2Encoder`과 `Jackson2Decoder` 모두 유형 `String`의 요소를 지원하지 않는다. 대신 기본으로 `CharSequenceEncoder` 의해 렌더링되는 직렬화 된 JSON 컨텐츠를 나타내는 문자열 또는 문자열의 시퀀스이다. `Flux<String>`에서 JSON 배열을 렌더링 할 필요가 있는 경우에는 `Flux#collectToList()`를 사용하여 `Mono<List<String>>`를 인코딩한다. ### 폼 데이터(Form Data) `FormHttpMessageReader`와 `FormHttpMessageWriter`는 `application/x-www-form-urlencoded` 컨텐츠의 디코딩 및 인코딩을 지원한다. 여러 곳에서 폼 컨텐츠에 자주 접근해 하는 서버에서는 `ServerWebExchange`는` FormHttpMessageReader`를 통해 컨텐츠를 분석하고 반복 사용하기 위해 결과를 캐시 전용 `getFormData()` 메서드를 제공한다. [`WebHandler` API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api) 섹션의 [폼 데이터](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-form-data)를 참조해라. `getFormData()`를 사용하면 원본 컨텐츠를 더 이상 요청 본문에서 읽을 수 없다. 따라서 응용 프로그램은 원시 요청 본문(request body)에서 읽는 대신 캐시 된 양식 데이터에 액세스하기 위해서는 `ServerWebExchange`를 통해야 한다. ### 멀티 파트(Multipart) `MultipartHttpMessageReader` 와 ` MultipartHttpMessageWriter`는"multipart/form-data" 컨텐츠의 디코딩과 인코딩을 지원한다. 다음은 `MultipartHttpMessageReader`와 다른 `HttpMessageReader`에 위임하여 `Flux<Part>`에 파싱하고 그 결과를 `MultiValueMap`으로 수집한다. 디폴트는 `DefaultPartHttpMessageReader`가 사용되지만, 이것은 `ServerCodecConfigurer`을 통해 변경할 수 있다. `DefaultPartHttpMessageReader`에 대한 자세한 내용은[ `DefaultPartHttpMessageReader`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/http/codec/multipart/DefaultPartHttpMessageReader.html)의 javadoc를 참조해라. 멀티 파트 폼 컨텐츠에 여러 위치에서 액세스 할 수 있는 서버 측에서 `ServerWebExchange`는 `MultipartHttpMessageReader`를 통해 컨텐츠를 분석하고 반복 사용하기 위해 결과를 캐시 전용 `getMultipartData()` 메서드를 제공한다. [`WebHandler` API](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-web-handler-api) 섹션의 [멀티파트 데이터](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-multipart)를 참조해라. `getMultipartData()`를 사용하면 원래의 원시 컨텐츠를 요청 본문에서 읽을 수 없다. 따라서 응용 프로그램은 map과 같이 반복적으로 파트에 접근하기 위해서 `getMultipartData()`을 일관되게 사용하거나, `Flux<Part>`에 한번의 액세스를 위해 `SynchronossPartHttpMessageReader`에 의존해야만 한다 . ### 제한(Limits) 입력 스트림의 일부 또는 전부를 버퍼링하는 `Decoder`와 `HttpMessageReader` 구현은 메모리에 버퍼링 할 최대 바이트 수의 제한을 설정할 수 있다. 입력이 모인 하나의 객체(예 : `@RequestBody byte[]`와 `x-www-form-urlencoded` 데이터 등의 컨트롤러 메서드)로 표현되기 때문에 버퍼링이 발생할 수 있다. 버퍼링 입력 스트림을 분할 할 때 스트리밍에서도 발생할 수 있다. 예를 들어, 구분 된 텍스트, JSON 객체의 스트림 등이다. 이러한 스트리밍의 경우 제한 스트림에서 하나의 객체와 연결된 바이트에 적용된다. 버퍼 크기를 설정하려면 특정 `Decoder` 또는 `HttpMessageReader`이 `maxInMemorySize` 속성을 공개하고 있는지 여부를 확인하고, 공개하고 있다면, Javadoc에 기본값 세부 정보가 포함되어 있는지 확인한다. 서버 측에서 `ServerCodecConfigurer`은 모든 코덱을 설정하기 위한 한군데의 위치를 제공한다. [HTTP 메시지 코덱](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-config-message-codecs)을 참조해라. 클라이언트에서는 모든 코덱의 제한을[ WebClient.Builder](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client-builder-maxinmemorysize)에서 변경할 수 있다. [멀티파트 파싱](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs-multipart)의 경우 `maxInMemorySize` 속성은 비 파일 파트파트의 크기를 제한한다. 파일 파트의 경우는 파트가 디스크에 기록되는 임계 값을 결정한다. 디스크에 기록되는 파일 파트의 경우는 파트 당 디스크 공간의 양을 제한하는 추가 `maxDiskUsagePerPart` 속성이 있다. 멀티 파트 요청의 파트 수를 제한하는 `maxParts` 속성도 있다. WebFlux에서 세 가지를 모두 구성하려면 구성된 `MultipartHttpMessageReader`에서 `ServerCodecConfigurer`의 인스턴스를 제공해야 한다. ### 스트리밍(Streaming) [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async-http-streaming) HTTP 응답 (예를 들어, `text/event-stream`, `application/x-ndjson`)에 스트리밍하는 경우, 연결이 끊어진 클라이언트를 가능한 빠르게 감지하기 위해 주기적으로 데이터를 전송하는 것이 중요하다 . 이러한 전송은 짧은 문자열이나 버어 있는 SSE 이벤트 또는 하트 비트로서 효과적으로 기능하는 기타 "no-op" 데이터 일 수 있다. #### `DataBuffer` `DataBuffer`는 WebFlux 바이트 버퍼의 표현이다. 이 레퍼런스 Spring 코어 부분은 [데이터 버퍼 및 코덱](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#databuffers) 섹션에서 자세히 설명하고 있다. 이해해야 할 중요한 점은 Netty와 같은 일부 서버에서는 바이트 버퍼가 풀링 된 참조를 계산 메모리 누수를 방지하기 위해 소비(consume)되고, 반드시 릴리즈를 해야 한다. WebFlux 응용 프로그램은 코덱에 따라 높은 수준의 객체 사이의 변환하지 않고 데이터 버퍼를 직접 소비하거나 생성하지 않는 한 또는 사용자 지정 코덱을 만드는 것을 선택하지 않는 한 일반적으로 그런 과제를 고려할 필요는 없다. 그런 경우는 [데이터 버퍼 및 코덱](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#databuffers) 정보, 특히 [`DataBuffer`의 사용](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#databuffers-using) 섹션을 확인해라. ## 1.2.6. 로깅 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-logging) Spring에서 `DEBUG` 레벨의 로그 WebFlux는 가볍고, 최소한으로, 사람이 보기 편하게 설계되어 있다. 이것은 특정 문제를 디버깅 할 경우에만 유용한 다른 정보와 비교하여 지속적으로 도움이 되는 정보의 중점을 두고 있다. `TRACE` 레벨의 로그는 일반적으로 `DEBUG`와 같은 원칙을 따른다 (예를 들어, 소방 호스(firehose)가 되어서는 안된다). 다만 문제의 디버깅에 사용될 수 있다. 또한 일부 로그 메시지는 `TRACE`와 `DEBUG`에서 상세한 레벨이 다를 수도 있다. 해당 로그는 로그를 사용한 경험에서 얻을 수 있다. 기재되어 있는 목적와 다른 것을 발견하면 알려 주시기 바란다. ### 로그 ID WebFlux는 단일 요청을 여러 스레드에서 실행할 수 있다. 스레드 ID는 특정 요청에 속하는 로그 메시지 상관 관계에 찾기 힘들다. 그래서 기본적으로 WebFlux 로그 메시지의 시작 부분에 요청 고유한 ID가 붙는다. 서버에서 로그 ID는 `ServerWebExchange` 특성 ([LOG_ID_ATTRIBUTE](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/server/ServerWebExchange.html#LOG_ID_ATTRIBUTE))에 저장되지만, 그 ID를 기반으로 완전히 포맷 된 접두사는 `ServerWebExchange#getLogPrefix()`에서 사용할 수 있다. `WebClient`에서 로그 ID는 `ClientRequest` 특성 ([LOG_ID_ATTRIBUTE](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/reactive/function/client/ClientRequest.html#LOG_ID_ATTRIBUTE))에 저장되지만 완전히 포맷 된 접두사는 `ClientRequest#logPrefix()`에서 구할 수 있다. ### 민감한 데이터 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-logging-sensitive-data) `DEBUG` 및 `TRACE` 로깅은 기밀 정보를 기록 할 수 있다. 그래서 폼 매개 변수와 헤더가 기본적으로 마스크되어 있으며, 이에 대한 로깅을 완전히 명시적으로 활성화해야 한다. 다음 예제는 서버 측 요청에 대해 이렇게 하는 방법을 보여준다. Java ``` @Configuration @EnableWebFlux class MyConfig implements WebFluxConfigurer { @Override public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { configurer.defaultCodecs().enableLoggingRequestDetails(true); } } ``` Kotlin ``` @Configuration @EnableWebFlux class MyConfig : WebFluxConfigurer { override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) { configurer.defaultCodecs().enableLoggingRequestDetails(true) } } ``` 다음 예제는 클라이언트 요청에 대해 이를 수행하는 방법 보여준다. Java ``` Consumer<ClientCodecConfigurer> consumer = configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true); WebClient webClient = WebClient.builder() .exchangeStrategies(strategies -> strategies.codecs(consumer)) .build(); ``` Kotlin ``` val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) } val webClient = WebClient.builder() .exchangeStrategies({ strategies -> strategies.codecs(consumer) }) .build() ``` ### Appenders SLF4J과 Log4J2 등의 로깅 라이브러리는 블로킹을 피하기 비동기 로거를 제공한다. 로깅을 위해 대기열에 넣을 수 없는 메시지를 삭제하는 것과 같은 자체 단점들이 있지만, 현재 리액티브, 논블로킹 응용 프로그램에서 사용할 수있는 최선의 선택이다. ### 사용자 정의 코덱 응용 프로그램은 추가의 미디어 타입을 지원하는 커스텀 코덱, 또는 기본 코덱에서 지원되지 않는 특정 동작을 등록 할 수 있다. 개발자에 의해 표현 된 일부 구성 옵션은 기본 코덱에 적용된다. 사용자 정의 코덱은 버퍼링 제한의 강제하거나 기밀 데이터 로깅 등이 설정에 맞게 조정할 기회를 얻고 싶은 경우가 있다. 다음 예제는 클라이언트 측의 요청에 대해 커스텀 코텍을 설정하는 방법을 보여준다. Java ``` WebClient webClient = WebClient.builder() .codecs(configurer -> { CustomDecoder decoder = new CustomDecoder(); configurer.customCodecs().registerWithDefaultConfig(decoder); }) .build(); ``` Kotlin ``` val webClient = WebClient.builder() .codecs({ configurer -> val decoder = CustomDecoder() configurer.customCodecs().registerWithDefaultConfig(decoder) }) .build() ```

이전 글 : 1.1. 개요
다음 글 : 1.3. DispatcherHandler