Spring Web Reactive | 1. Spring WebFlux | 1.5. Functional Endpoints
Spring WebFlux에는 WebFlux.fn가 포함되어 있다. WebFlux.fn는 기능을 사용하여 요청 라우팅하고 처리를 하고 불변성(immutability)을 갖도록 설계되어 있다. 이는 어노테이션 기반의 프로그래밍 모델에 대한 대안이며, 그 이외는 동일한 리액티브 코어 기반에서 실행된다.
1.5.1 개요
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
로 변환하고, 내장된 서버 어댑터 중 하나를 통해 설치하는 것이다.
RouterFunctions.toHttpHandler(RouterFunction)
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
대부분의 응용 프로그램은 WebFlux Java 구성을 통해 실행할 수 있다. 서버의 실행을 참조해라.
1.5.2. HandlerFunction
ServerReques
와 ServerResponse
는 HTTP 요청와 응답에 JDK 8 친화적인 액세스를 제공하는 불변의 인터페이스이다. 요청과 응답 모두가 몸 스트림에 대한 Reactive Streams 역 배압을 제공한다. 요청 내용은 Reactor Flux
또는 Mono
로 표시된다. 응답 본체는 Flux
와 Mono
을 포함하는 Reactive Streams Publisher
에 표시된다. 자세한 내용은 리액티브 라이브러리를 참조해라.
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 를 지정할 수 있다.
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 검증 기능을 사용하여 요청 본문에 검증을 적용 할 수 있다. 다음 예제는 Person
커스텀 정의 Spring Validator 구현이 지정된 경우다.
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 을 참조해라.
1.5.3. RouterFunction
라우터 함수를 사용하여 요청을 해당하는 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. 서버의 실행
HTTP 서버에서 라우터 기능을 어떻게 수행할까? 간단한 옵션은 다음 중 하나를 사용하여 라우터 함수를 HttpHandler
변환하는 것이다.
RouterFunctions.toHttpHandler(RouterFunction)
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
그런 다음에는 서버별 절차에 대해 HttpHandler
를 수행함으로써 반환된 HttpHandler를 여러 서버 어댑터로 사용할 수 있다.
Spring Boot에도 사용되는 일반적인 옵션은 WebFlux 구성을 통해 DispatcherHandler
기반으로 설치로 실행하는 것이다. WebFlux 구성은 Spring 설정을 사용하여 요청을 처리하는데 필요한 구성 요소를 선언한다. WebFlux Java 구성은 함수 엔드 포인트를 지원하기 위해 다음의 인프라 구성 요소를 선언한다.
RouterFunctionMapping
: Spring 설정에서 하나 이상의RouterFunction<?>
Bean을 감지하고, 정렬 붙일 수 있는RouterFunction.andOther
을 통해 그들을 결합하고 그 결과로 생성된RouterFunction
요청을 라우팅한다.HandlerFunctionAdapter
:DispatcherHandler
이 요청에 맵핑된HandlerFunction
호출을 가능하게 하는 간단한 어댑터.ServerResponseResultHandler
:ServerResponse
의writeTo
메소드를 호출하여,HandlerFunction
호출의 결과를 처리한다.
위의 구성 요소는 함수 엔드 포인트는 DispatcherHandler
요청 처리 라이프 사이클에 적합하고, 어노테이션이 선언된 컨트롤러(있는 경우)과 병행하여 (잠재적으로) 실행된다. 또한 Spring Boot WebFlux 스타터가 함수 엔드 포인트를 사용하는 방법도 있다.
다음의 예는 WebFlux Java 구성을 보여준다(실행 방법은 DispatcherHandler 를 참조해라).
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. 핸들러 함수의 필터링
라우팅 함수 빌더에서 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
을 통해 제공된다.