Spring Web Reactive | 1. Spring WebFlux | 1.5. Functional Endpoints

Web MVC

Spring WebFlux에는 WebFlux.fn가 포함되어 있다. WebFlux.fn는 기능을 사용하여 요청 라우팅하고 처리를 하고 불변성(immutability)을 갖도록 설계되어 있다. 이는 어노테이션 기반의 프로그래밍 모델에 대한 대안이며, 그 이외는 동일한 리액티브 코어 기반에서 실행된다.

1.5.1 개요

Web MVC

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를 실행하는 하나의 방법은 RouterFunctionHttpHandler로 변환하고, 내장된 서버 어댑터 중 하나를 통해 설치하는 것이다.

  • RouterFunctions.toHttpHandler(RouterFunction)
  • RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)

대부분의 응용 프로그램은 WebFlux Java 구성을 통해 실행할 수 있다. 서버의 실행을 참조해라.

1.5.2. HandlerFunction

Web MVC

ServerRequesServerResponse 는 HTTP 요청와 응답에 JDK 8 친화적인 액세스를 제공하는 불변의 인터페이스이다. 요청과 응답 모두가 몸 스트림에 대한 Reactive Streams 역 배압을 제공한다. 요청 내용은 Reactor Flux 또는 Mono로 표시된다. 응답 본체는 FluxMono을 포함하는 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) getPersonid 경로 변수로 식별되는 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) getPersonid 경로 변수로 식별되는 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

Web MVC

라우터 함수를 사용하여 요청을 해당하는 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 /personPersonHandler.listPeople에 라우팅된다.
  • (3) 추가 조건없는 POST /personPersonHandler.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

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: ServerResponsewriteTo 메소드를 호출하여, 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. 핸들러 함수의 필터링

Web MVC

라우팅 함수 빌더에서 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를 인자로 받는다. 이는 ServerRequestHandlerFunction를 받아 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을 통해 제공된다.




최종 수정 : 2021-04-12