1.5. Functional Endpoints

편집일시: 2021-04-12 14:22 조회수: 211 댓글수: 0
[Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn) Spring WebFlux에는 WebFlux.fn가 포함되어 있다. WebFlux.fn는 기능을 사용하여 요청 라우팅하고 처리를 하고 불변성(immutability)을 갖도록 설계되어 있다. 이는 어노테이션 기반의 프로그래밍 모델에 대한 대안이며, 그 이외는 동일한 [리액티브 코어](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-reactive-spring-web) 기반에서 실행된다. ## 1.5.1 개요 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn-overview) WebFlux.fn에서 HTTP 요청은 `HandlerFunction`으로 처리된다. `ServerRequest` 을 받아 지연(비동기)되는 `ServerResponse `(즉, `Mono<ServerResponse>`)을 반환하는 함수이다. 요청 객체, 응답 객체 모두에 HTTP 요청과 응답에 JDK 8 친화적인 액세스를 제공하는 불변(immutable) 객체이다. `HandlerFunction`는 어노테이션 기반 프로그래밍 모델의 `@RequestMapping` 메소드의 본체(body)에 해당한다. 수신 요청은 `RouterFunction`을 사용하여 핸들러 함수에 전달된다. `ServerRequest` 를 받고 지연되는 `HandlerFunction` (즉, `Mono<HandlerFunction>`)을 반환하는 함수이다. 라우터 함수가 일치하면 핸들러 함수가 반환된다. 또는 빈어 있는 Mono가 반환된다. `RouterFunction`은 `@RequestMapping` 어노테이션과 동일하지만, 라우터 함수가 데이터뿐만 아니라 동작도 제공한다는 큰 차이가 있다. `RouterFunctions.route()`은 다음의 예와 같이 라우터의 작성을 용이하게 하는 라우터 빌더를 제공한다. Java ``` import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; import static org.springframework.web.reactive.function.server.RouterFunctions.route; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction<ServerResponse> route = route() .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) .POST("/person", handler::createPerson) .build(); public class PersonHandler { // ... public Mono<ServerResponse> listPeople(ServerRequest request) { // ... } public Mono<ServerResponse> createPerson(ServerRequest request) { // ... } public Mono<ServerResponse> getPerson(ServerRequest request) { // ... } } ``` Kotlin ``` val repository: PersonRepository = ... val handler = PersonHandler(repository) val route = coRouter { // (1) accept(APPLICATION_JSON).nest { GET("/person/{id}", handler::getPerson) GET("/person", handler::listPeople) } POST("/person", handler::createPerson) } class PersonHandler(private val repository: PersonRepository) { // ... suspend fun listPeople(request: ServerRequest): ServerResponse { // ... } suspend fun createPerson(request: ServerRequest): ServerResponse { // ... } suspend fun getPerson(request: ServerRequest): ServerResponse { // ... } } ``` - (1) `Coroutines` 라우터 DSL을 사용하여 라우터를 만든다. Reactive 대안도 `router { }`를 통해 사용할 수 있다. `RouterFunction`를 실행하는 하나의 방법은 `RouterFunction`를 `HttpHandler`로 변환하고, 내장된 [서버 어댑터](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-httphandler) 중 하나를 통해 설치하는 것이다. - `RouterFunctions.toHttpHandler(RouterFunction)` - `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)` 대부분의 응용 프로그램은 WebFlux Java 구성을 통해 실행할 수 있다. [서버의 실행](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-fn-running)을 참조해라. ## 1.5.2. HandlerFunction [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn-handler-functions) `ServerReques`와 `ServerResponse` 는 HTTP 요청와 응답에 JDK 8 친화적인 액세스를 제공하는 불변의 인터페이스이다. 요청과 응답 모두가 몸 스트림에 대한 [Reactive Streams](https://www.reactive-streams.org/) 역 배압을 제공한다. 요청 내용은 Reactor `Flux` 또는 `Mono`로 표시된다. 응답 본체는 `Flux`와 `Mono`을 포함하는 Reactive Streams `Publisher`에 표시된다. 자세한 내용은 [리액티브 라이브러리](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-reactive-libraries)를 참조해라. ### ServerRequest `ServerRequest` 는 HTTP 메소드, URI 헤더 쿼리 매개 변수에 대한 액세스를 제공하고 바디에 액세스는 `body` 메서드를 통해 제공된다. 다음 예제에서는 요청 본문을 `Mono<String>`으로 추출한다. Java ``` Mono<String> string = request.bodyToMono(String.class); ``` Kotlin ``` val string = request.awaitBody<String>() ``` 다음 예제에서는 `Flux<Person>` (또는 Kotlin의 `Flow<Person>`)에서 body를 추출한다. Person 객체는 JSON 또는 XML 등의 직렬화된 형식으로 디코딩된다. Java ``` Flux<Person> people = request.bodyToFlux(Person.class); ``` Kotlin ``` val people = request.bodyToFlow<Person>() ``` 위의 예제는 보다 일반적인 `ServerRequest.body(BodyExtractor)`를 사용하기 간편한 방법이며, `BodyExtractor` 기능 전략 인터페이스를 허용한다. 유틸리티 클래스 `BodyExtractors`는 다수의 인스턴스에 대한 액세스를 제공한다. 예를 들어, 위의 예제는 다음과 같이 쓸 수 있다. Java ``` Mono<String> string = request.body(BodyExtractors.toMono(String.class)); Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class)); ``` Kotlin ``` val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle() val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow() ``` 다음 예제는 폼 데이터에 액세스하는 방법을 보여준다. Java ``` Mono<MultiValueMap<String, String> map = request.formData(); ``` Kotlin ``` val map = request.awaitFormData() ``` 다음 예제는 맵으로 다중 데이터에 액세스하는 방법을 보여준다. Java ``` Mono<MultiValueMap<String, Part> map = request.multipartData(); ``` Kotlin ``` val map = request.awaitMultipartData() ``` 다음의 예는 스트리밍 방식으로 한 번에 하나씩 여러 부분에 액세스하는 방법을 보여준다. Java ``` Flux<Part> parts = request.body(BodyExtractors.toParts()); ``` Kotlin ``` val parts = request.body(BodyExtractors.toParts()).asFlow() ``` ### ServerResponse `ServerResponse` 는 HTTP 응답에 대한 액세스를 제공한다. 이것은 불변이므로 `build` 메소드를 사용하여 HTTP 응답을 만들 수 있다. 빌더를 사용하여 응답 상태를 설정하거나, 응답 헤더를 추가하거나 본문을 제공 할 수 있다. 다음 예제에서는 JSON 콘텐츠로 200 (OK) 응답을 만든다. Java ``` Mono<Person> person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class); ``` Kotlin ``` val person: Person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person) ``` 다음의 예는 `Location` 헤더를 가지고 본문이 없는 201 (CREATED) 응답을 만드는 방법을 보여준다. Java ``` URI location = ... ServerResponse.created(location).build(); ``` Kotlin ``` val location: URI = ... ServerResponse.created(location).build() ``` 사용되는 코덱에 따라 힌트 매개 변수를 전달하여 본문의 직렬화 또는 역 직렬화 방법을 지정할 수 있다. 예를 들어, [Jackson JSON View](https://www.baeldung.com/jackson-json-view-annotation) 를 지정할 수 있다. Java ``` ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...); ``` Kotlin ``` ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...) ``` ### 핸들러 클래스(Handler Classes) 다음의 예와 같이 핸들러 함수를 람다로 작성할 수 있다. Java ``` HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().bodyValue("Hello World"); ``` Kotlin ``` val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") } ``` 이는 편리하지만, 응용 프로그램에서 여러 함수가 필요하며, 여러 인라인 람다가 지저분해 질 수 있다. 관련 핸들러 함수를 어노테이션 기반 응용 프로그램의 `Controller`와 같은 역할을 가진 핸들러 클래스로 그룹화하면 편리한다. 예를 들어, 다음 클래스는 리액티브 `Person` 리포지토리와 관련된 처리를 한다. Java ``` import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.ServerResponse.ok; public class PersonHandler { private final PersonRepository repository; public PersonHandler(PersonRepository repository) { this.repository = repository; } public Mono<ServerResponse> listPeople(ServerRequest request) { // (1) Flux<Person> people = repository.allPeople(); return ok().contentType(APPLICATION_JSON).body(people, Person.class); } public Mono<ServerResponse> createPerson(ServerRequest request) { // (2) Mono<Person> person = request.bodyToMono(Person.class); return ok().build(repository.savePerson(person)); } public Mono<ServerResponse> getPerson(ServerRequest request) { // (3) int personId = Integer.valueOf(request.pathVariable("id")); return repository.getPerson(personId) .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person)) .switchIfEmpty(ServerResponse.notFound().build()); } } ``` - (1) listPeople 저장소에서 갬색된 모든 `Person` 객체를 JSON으로 반환하는 핸들러 함수이다. - (2) createPerson 는 요청 본문에 포함 된 새로운 `Person`를 저장하는 핸들러 함수이다. `PersonRepository.savePerson(Person)`는 `Mono<Void>`를 반환하는 것에 주의해라. 하늘의 Mono 는 요청에서 사람이 읽을 보관 된 때 완료 시그널을 발생한다. 따라서 `build(Publisher<Void>)` 메소드를 사용하여 완료 신호를 수신 할 때 (즉, `Person`이 저장될 때)에 응답을 보낸다. - (2) `getPerson`은 `id` 경로 변수로 식별되는 1명의 사람을 반환하는 핸들러 함수이다. `Person` 리포지토리에서 검색하여 조회가 되면 JSON 응답을 만든다. 찾을 수 없다면 `switchIfEmpty(Mono<T>)` 를 사용하여 404 미 검출 응답을 반환한다. Kotlin ``` class PersonHandler(private val repository: PersonRepository) { suspend fun listPeople(request: ServerRequest): ServerResponse { // (1) val people: Flow<Person> = repository.allPeople() return ok().contentType(APPLICATION_JSON).bodyAndAwait(people); } suspend fun createPerson(request: ServerRequest): ServerResponse { // (2) val person = request.awaitBody<Person>() repository.savePerson(person) return ok().buildAndAwait() } suspend fun getPerson(request: ServerRequest): ServerResponse { // (3) val personId = request.pathVariable("id").toInt() return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) } ?: ServerResponse.notFound().buildAndAwait() } } ``` - (1) `listPeople` 리포지토리에서 갬색된 모든 `Person` 객체를 JSON으로 반환 핸들러 함수이다. - (2) `createPerson` 는 요청 본문에 포함 된 새로운 `Person` 저장하는 핸들러 함수이다. `PersonRepository.savePerson(Person)` 반환 값이 없는 일시 중지 함수임을 유의해라. - (3) `getPerson`은 `id` 경로 변수로 식별되는 1명의 사람을 반환 핸들러 함수이다. `Person` 리포지토리에서 검색하여 조회가 되면 JSON 응답을 만든다. 없으면 404 미 검출 응답을 반환한다. ### 검증(Validation) 함수 엔드 포인트는 Spring [검증](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation) 기능을 사용하여 요청 본문에 검증을 적용 할 수 있다. 다음 예제는 `Person` 커스텀 정의 Spring [Validator](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation) 구현이 지정된 경우다. Java ``` public class PersonHandler { private final Validator validator = new PersonValidator(); // (1) // ... public Mono<ServerResponse> createPerson(ServerRequest request) { Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // (2) return ok().build(repository.savePerson(person)); } private void validate(Person person) { Errors errors = new BeanPropertyBindingResult(person, "person"); validator.validate(person, errors); if (errors.hasErrors()) { throw new ServerWebInputException(errors.toString()); // (3) } } } ``` Kotlin ``` class PersonHandler(private val repository: PersonRepository) { private val validator = PersonValidator() // (1) // ... suspend fun createPerson(request: ServerRequest): ServerResponse { val person = request.awaitBody<Person>() validate(person) // (2) repository.savePerson(person) return ok().buildAndAwait() } private fun validate(person: Person) { val errors: Errors = BeanPropertyBindingResult(person, "person"); validator.validate(person, errors); if (errors.hasErrors()) { throw ServerWebInputException(errors.toString()) // (3) } } } ``` - (1) `Validator` 인스턴스를 만든다. - (2) 검증을 적용한다. - (3) 400 응답에 대해 예외를 발생시킨다. 핸들러 `LocalValidatorFactoryBean`에 따라 글로벌 `Validator` 인스턴스를 만들고 주입하여 표준 Bean 검증 API (JSR-303)를 사용할 수도 있다. [Spring Validation](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation-beanvalidation) 을 참조해라. ## 1.5.3. `RouterFunction` [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn-router-functions) 라우터 함수를 사용하여 요청을 해당하는 `HandlerFunction`에 라우팅한다. 일반적으로 라우터 함수는 직접 만드는 것이 아니라, `RouterFunctions` 유틸리티 클래스의 메서드를 사용하여 만든다. `RouterFunctions.route()`(매개 변수 없음) 라우터 함수를 만들기 위한 흐르는듯한 빌더를 제공하지만,` RouterFunctions.route(RequestPredicate, HandlerFunction)` 라우터를 직접 만드는 방법을 제공한다. 일반적으로 `route()` 빌더를 사용하는 것이 좋다. 이것은 검출이 어려운 정적 임포트를 필요로 하지 않고, 일반적인 매핑 시나리오에 유용한 단축키를 제공하기 때문이다. 예를 들어, 라우터 함수 빌더는 GET 요청의 매핑을 작성하는 메소드 `GET(String, HandlerFunction)` 를 제공한다. POST 용 `POST(String, HandlerFunction)`. HTTP 메소드 기반 매핑 외에도 루트 빌더는 요청에 매핑 할 때 추가 술어를 도입하는 방법을 제공한다. HTTP 메소드에 대해 `RequestPredicate` 매개 변수로 사용하는 오버로드 된 변형이 있지만, 추가의 제약을 표현할 수 있다. ### 조건(Predicates) 자체 `RequestPredicate`를 만들 수 있지만, `RequestPredicates` 유틸리티 클래스는 요청 경로, HTTP, 메소드, 콘텐츠 형식 등에 따라 일반적으로 사용되는 구현을 제공한다. 다음 예제에서는 요청 조건를 사용하여 `Accept` 헤더에 따라 제약을 만든다. Java ``` RouterFunction<ServerResponse> route = RouterFunctions.route() .GET("/hello-world", accept(MediaType.TEXT_PLAIN), request -> ServerResponse.ok().bodyValue("Hello World")).build(); ``` Kotlin ``` val route = coRouter { GET("/hello-world", accept(TEXT_PLAIN)) { ServerResponse.ok().bodyValueAndAwait("Hello World") } } ``` 다음을 사용하여 여러 요청 조건를 함께 만들 수 있다. - `RequestPredicate.and(RequestPredicate)` - 모두 일치해야 한다. - `RequestPredicate.or(RequestPredicate)` - 둘 중 하나만 일치하면 된다. `RequestPredicates` 조건이 많은 구성되어 있다. 예를 들어, `RequestPredicates.GET(String)`는 `RequestPredicates.method(HttpMethod)`와 `RequestPredicates.path(String)`로 구성되어 있다. 위의 예제는 빌더가 `RequestPredicates.GET`를 내부적으로 사용하고, 그것을 `accept` 조건으로 구성하고 있기 때문에 두 요청 조건도 사용한다. ### 루트(Routes) 라우터의 기능은 순서대로 평가된다. 첫 번째 경로가 일치하지 않는 경우, 두 번째 루트가 평가된다. 일반적인 루트의 전에보다 구체적인 루트을 선언하는 것은 의미가 있다. 이것은 나중에 설명하도록 라우터 기능을 Spring Bean으로 등록하는 경우에도 중요한다. 이 동작은 "가장 구체적인" 컨트롤러 메소드가 자동으로 선택되는 어노테이션 기반의 프로그래밍 모델과 다르다는 점에 유의해라. 라우터 함수 빌더를 사용하는 경우, 정의된 모든 루트는 `build()`부터 반환되는 하나의 `RouterFunction` 구성된다. 여러 라우터 함수을 함께 구성하는 다른 방법도 있다. - `RouterFunctions.route()` 빌더의 `add(RouterFunction)` - `RouterFunction.and(RouterFunction)` - `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -  `RouterFunctions.route()`dhk 중첩된 `RouterFunction.and()`의 간결한 형태. 다음 예는 4개의 루트 구성을 보여준다. Java ``` import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction<ServerResponse> otherRoute = ... RouterFunction<ServerResponse> route = route() .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2) .POST("/person", handler::createPerson) (3) .add(otherRoute) (4) .build(); ``` Kotlin ``` import org.springframework.http.MediaType.APPLICATION_JSON val repository: PersonRepository = ... val handler = PersonHandler(repository); val otherRoute: RouterFunction<ServerResponse> = coRouter { } val route = coRouter { GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // (1) GET("/person", accept(APPLICATION_JSON), handler::listPeople) // (2) POST("/person", handler::createPerson) // (3) }.and(otherRoute) // (4) ``` - (1) JSON과 일치하는 `Accept` 헤더가 있는 `GET /person/{id}`는 `PersonHandler.getPerson`에 라우팅된다. - (2) JSON과 일치하는 `Accept` 헤더가 있는 `GET /person`는 `PersonHandler.listPeople`에 라우팅된다. - (3) 추가 조건없는 `POST /person`는 `PersonHandler.createPerson`에 매핑된다. - (4) `otherRoute`는 다른 곳에서 작성되어, 구축된 경로에 추가되는 라우터 기능이다. ### 중첩 된 루트(Nested Routes) 라우터 함수 그룹이 공유 조건(공유 경로 등)를 갖는 것은 일반적이다. 위의 예제에서는 공유 조건은 3가지 루트로 사용되는 `/person`에 일치하는 경로 조건이다. 어노테이션을 사용하는 경우, `/person`에 맵핑 형식 레벨의 `@RequestMapping` 어노테이션을 사용하여, 이 중복을 제거한다. WebFlux.fn에는 라우팅 조건은 라우터 함수 빌더의 `path` 방법을 통해 공유 할 수 있다. 예를 들어, 위 예제의 마지막 몇 줄은 중첩된 경로를 사용하여 다음과 같이 개선 할 수 있다. Java ``` RouterFunction<ServerResponse> route = route() .path("/person", builder -> builder (1) .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET(accept(APPLICATION_JSON), handler::listPeople) .POST("/person", handler::createPerson)) .build(); ``` - (1) `path`의 두 번째 매개 변수는 라우터 빌더를 사용하는 커슈머(consumer)임을 주의해라. Kotlin ``` val route = coRouter { "/person".nest { GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) GET(accept(APPLICATION_JSON), handler::listPeople) POST("/person", handler::createPerson) } } ``` 경로 기반의 중첩이 가장 일반적이지만, `Builder`에서 `nest` 메소드를 사용하여 모든 종류의 조건에 중첩 할 수 있다. 위는 공유는 `Accept-header` 조건의 형식으로 중복이 여전히 포함되어 있다. `nest` 메소드와 `accept`을 함께 사용하면, 더욱 개선할 수 있다. Java ``` RouterFunction<ServerResponse> route = route() .path("/person", b1 -> b1 .nest(accept(APPLICATION_JSON), b2 -> b2 .GET("/{id}", handler::getPerson) .GET(handler::listPeople)) .POST("/person", handler::createPerson)) .build(); ``` Kotlin ``` val route = coRouter { "/person".nest { accept(APPLICATION_JSON).nest { GET("/{id}", handler::getPerson) GET(handler::listPeople) POST("/person", handler::createPerson) } } } ``` ## 1.5.4. 서버의 실행 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn-running) HTTP 서버에서 라우터 기능을 어떻게 수행할까? 간단한 옵션은 다음 중 하나를 사용하여 라우터 함수를 `HttpHandler` 변환하는 것이다. - `RouterFunctions.toHttpHandler(RouterFunction)` - `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)` 그런 다음에는 서버별 절차에 대해 `HttpHandler`를 수행함으로써 반환된 [HttpHandler](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-httphandler)를 여러 서버 어댑터로 사용할 수 있다. Spring Boot에도 사용되는 일반적인 옵션은 [WebFlux 구성](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-config)을 통해 [`DispatcherHandler`](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-dispatcher-handler) 기반으로 설치로 실행하는 것이다. [WebFlux 구성](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-config)은 Spring 설정을 사용하여 요청을 처리하는데 필요한 구성 요소를 선언한다. WebFlux Java 구성은 함수 엔드 포인트를 지원하기 위해 다음의 인프라 구성 요소를 선언한다. - `RouterFunctionMapping`: Spring 설정에서 하나 이상의 `RouterFunction<?>` Bean을 감지하고, 정렬 붙일 수 있는 `RouterFunction.andOther`을 통해 [그들](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-ordered)을 결합하고 그 결과로 생성된 `RouterFunction`요청을 라우팅한다. - `HandlerFunctionAdapter`: `DispatcherHandler`이 요청에 맵핑된 `HandlerFunction` 호출을 가능하게 하는 간단한 어댑터. - `ServerResponseResultHandler`: `ServerResponse`의 `writeTo` 메소드를 호출하여, `HandlerFunction` 호출의 결과를 처리한다. 위의 구성 요소는 함수 엔드 포인트는 `DispatcherHandler` 요청 처리 라이프 사이클에 적합하고, 어노테이션이 선언된 컨트롤러(있는 경우)과 병행하여 (잠재적으로) 실행된다. 또한 Spring Boot WebFlux 스타터가 함수 엔드 포인트를 사용하는 방법도 있다. 다음의 예는 WebFlux Java 구성을 보여준다(실행 방법은 [DispatcherHandler](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-dispatcher-handler) 를 참조해라). Java ``` @Configuration @EnableWebFlux public class WebConfig implements WebFluxConfigurer { @Bean public RouterFunction<?> routerFunctionA() { // ... } @Bean public RouterFunction<?> routerFunctionB() { // ... } // ... @Override public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { // configure message conversion... } @Override public void addCorsMappings(CorsRegistry registry) { // configure CORS... } @Override public void configureViewResolvers(ViewResolverRegistry registry) { // configure view resolution for HTML rendering... } } ``` Kotlin ``` @Configuration @EnableWebFlux class WebConfig : WebFluxConfigurer { @Bean fun routerFunctionA(): RouterFunction<*> { // ... } @Bean fun routerFunctionB(): RouterFunction<*> { // ... } // ... override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) { // configure message conversion... } override fun addCorsMappings(registry: CorsRegistry) { // configure CORS... } override fun configureViewResolvers(registry: ViewResolverRegistry) { // configure view resolution for HTML rendering... } } ``` ## 1.5.5. 핸들러 함수의 필터링 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn-handler-filter-function) 라우팅 함수 빌더에서 `before`, `after` 또는 `filter` 메소드를 사용하여, 핸들러 함수를 필터링 할 수 있다. 어노테이션을 사용하면, `@ControllerAdvice`, `ServletFilter` 또는 둘다 모두를 사용하여 유사한 기능을 제공한다. 필터 빌더에 의해 작성된 모든 노선에 적용된다. 이것은 중첩된 루트에 정의된 필터가 "최상위" 루트에 적용되지 않는 것을 의미한다. 예를 들어, 다음 예를 살펴 보겠다. Java ``` RouterFunction<ServerResponse> route = route() .path("/person", b1 -> b1 .nest(accept(APPLICATION_JSON), b2 -> b2 .GET("/{id}", handler::getPerson) .GET(handler::listPeople) .before(request -> ServerRequest.from(request) // (1) .header("X-RequestHeader", "Value") .build())) .POST("/person", handler::createPerson)) .after((request, response) -> logResponse(response)) // (2) .build(); ``` Kotlin ``` val route = router { "/person".nest { GET("/{id}", handler::getPerson) GET("", handler::listPeople) before { // (1) ServerRequest.from(it) .header("X-RequestHeader", "Value").build() } POST("/person", handler::createPerson) after { _, response -> // (2) logResponse(response) } } } ``` - (1) 사용자 요청 헤더를 추가하는 `before` 필터는 2개의 GET 루트에만 적용된다. - (2) 응답을 기록하는 `after` 필터는 중첩된 것을 포함하여 모든 루트에 적용된다. 라우터 빌더의 `filter` 메소드는 `HandlerFilterFunction`를 인자로 받는다. 이는 `ServerRequest`와 `HandlerFunction`를 받아 `ServerResponse`를 반환하는 함수이다. 핸들러 함수 매개 변수는 체인의 다음의 요소를 나타낸다. 이것은 일반적으로 라우팅 핸들러이지만 여러가 적용되는 경우는 다른 필터 할 수 있다. 특정 경로가 허용되는지 여부를 판단 할 수 있는 `SecurityManager`가 있다고 가정하여, 루트에 간단한 보안 필터를 추가 할 수 있다. 다음의 예제는 그 방법을 보여준다. Java ``` SecurityManager securityManager = ... RouterFunction<ServerResponse> route = route() .path("/person", b1 -> b1 .nest(accept(APPLICATION_JSON), b2 -> b2 .GET("/{id}", handler::getPerson) .GET(handler::listPeople)) .POST("/person", handler::createPerson)) .filter((request, next) -> { if (securityManager.allowAccessTo(request.path())) { return next.handle(request); } else { return ServerResponse.status(UNAUTHORIZED).build(); } }) .build(); ``` Kotlin ``` val securityManager: SecurityManager = ... val route = router { ("/person" and accept(APPLICATION_JSON)).nest { GET("/{id}", handler::getPerson) GET("", handler::listPeople) POST("/person", handler::createPerson) filter { request, next -> if (securityManager.allowAccessTo(request.path())) { next(request) } else { status(UNAUTHORIZED).build(); } } } } ``` 위의 예는 `next.handle(ServerRequest)`의 호출이 옵션으로 있음을 보여준다. 액세스가 허용되는 경우에만 핸들러 함수를 실행시킨다. 라우터 함수 빌더에서 `filter` 메소드를 사용하는 것 외에도 `RouterFunction.filter(HandlerFilterFunction)`을 통해 기존의 라우터 기능에 필터를 적용 할 수 있다. > 함수 엔드 포인트 CORS 지원은 전용의 `CorsWebFilter`을 통해 제공된다.