HandlerFunction

2024. 10. 13. 20:33Spring Framework/Web on Servlet Stack

Spring Web MVC의 Functional Endpoints 모델인 WebMvc.fn에서 HandlerFunctionServerRequest, ServerResponse에 대한 자세한 설명을 제공하고, 이 기능이 어떻게 HTTP 요청을 처리하는지에 대한 구체적인 내용을 설명하겠습니다.

핵심 개념

1. HandlerFunction

  • HandlerFunction은 HTTP 요청을 처리하는 함수입니다. 이것은 애너테이션 기반의 @RequestMapping 메서드 본체와 동일한 역할을 하며, HTTP 요청을 받아 이를 처리하고 응답을 반환합니다.
  • 이 함수는 ServerRequest 객체를 받아 ServerResponse 객체를 반환하는 형태입니다. 즉, 요청을 처리하는 로직을 정의한 함수로, 클라이언트로부터의 요청을 받아 응답을 생성합니다.
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body("Hello World");

위 예시는 간단한 핸들러 함수로, Hello World라는 메시지를 반환하는 예입니다. 그러나 여러 개의 람다 함수가 많아지면 코드가 복잡해질 수 있으므로, 이를 Handler 클래스로 묶어서 관리할 수 있습니다.

2. ServerRequest

  • ServerRequest는 HTTP 요청에 대한 정보를 제공합니다. 이를 통해 HTTP 메서드, URI, 헤더, 쿼리 파라미터 등에 접근할 수 있습니다. HTTP 본문(Body)에 접근하기 위해서는 body() 메서드를 사용합니다.
    String stringBody = request.body(String.class);
    예시: 본문(body)을 List<Person>으로 추출하는 경우:예시: 쿼리 파라미터를 추출하는 경우:ServerRequest를 사용하여 HTTP 요청의 모든 구성 요소를 쉽게 추출할 수 있습니다.
  • MultiValueMap<String, String> params = request.params();
  • List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
  • 예시: 본문(body)을 String으로 추출하는 경우:

3. ServerResponse

  • ServerResponse는 HTTP 응답을 표현하는 객체입니다. ServerResponse는 불변성을 가지고 있어, 빌더 패턴을 사용하여 응답을 생성합니다.
  • 응답의 상태 코드, 헤더, 본문 등을 설정할 수 있으며, JSON 등의 다양한 포맷으로 응답을 보낼 수 있습니다.
    Person person = ...;
    ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
    예시: Location 헤더를 포함한 201 (Created) 응답을 생성하는 경우:이처럼 ServerResponse는 HTTP 응답을 구성하는 다양한 방법을 제공하여 클라이언트에게 적절한 HTTP 응답을 보낼 수 있습니다.
  • URI location = ...; ServerResponse.created(location).build();
  • 예시: JSON 응답을 생성하는 경우:

Functional Endpoints의 주요 기능

1. HandlerFunction과 Handler 클래스

  • HandlerFunction은 요청을 처리하는 단일 함수입니다. 그러나 실제 애플리케이션에서는 여러 함수가 필요하기 때문에 관련된 핸들러들을 한 클래스에 모아 관리할 수 있습니다. 이렇게 하면 애너테이션 기반의 @Controller와 비슷한 역할을 하게 됩니다.
    public class PersonHandler {
    
        private final PersonRepository repository;
    
        public PersonHandler(PersonRepository repository) {
            this.repository = repository;
        }
    
        // 모든 사람 목록을 반환
        public ServerResponse listPeople(ServerRequest request) {
            List<Person> people = repository.allPeople();
            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(people);
        }
    
        // 새로운 사람을 생성
        public ServerResponse createPerson(ServerRequest request) throws Exception {
            Person person = request.body(Person.class);
            repository.savePerson(person);
            return ServerResponse.ok().build();
        }
    
        // 특정 사람을 조회
        public ServerResponse getPerson(ServerRequest request) {
            int personId = Integer.parseInt(request.pathVariable("id"));
            Person person = repository.getPerson(personId);
            if (person != null) {
                return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
            } else {
                return ServerResponse.notFound().build();
            }
        }
    }
    • listPeople: 모든 사람 목록을 JSON으로 반환하는 핸들러 함수입니다.
    • createPerson: 요청 본문에 포함된 Person 객체를 데이터베이스에 저장하는 핸들러입니다.
    • getPerson: 주어진 ID로 특정 사람을 조회하며, 존재하지 않으면 404 Not Found 응답을 보냅니다.
  • 예시:

2. 비동기 응답 (Async Responses)

  • 비동기 처리 방식으로 응답을 반환할 수 있습니다. 예를 들어, CompletableFuturePublisher를 사용하여 비동기 데이터를 처리하고 응답할 수 있습니다.
    Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
    ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
    이 예시는 Mono<Person> 타입의 비동기 데이터를 처리하여 응답을 생성하는 방법을 보여줍니다.
  • 예시: 비동기 응답을 생성하는 경우:

3. Server-Sent Events (SSE)

  • SSE (Server-Sent Events)는 서버에서 클라이언트로 지속적인 데이터를 푸시하는 기술입니다. Spring Web MVC에서는 ServerResponse.sse() 메서드를 통해 SSE를 지원합니다.
    public RouterFunction<ServerResponse> sse() {
        return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
                    // SSE 처리 로직
                }));
    }
    
    // 다른 쓰레드에서 이벤트 전송
    sseBuilder.send("Hello world");
    • sseBuilder.send()를 사용해 문자열이나 객체를 클라이언트로 전송할 수 있습니다.
    • JSON 형식으로도 데이터를 보낼 수 있으며, 이벤트 ID 및 타입도 설정할 수 있습니다.
  • 예시: SSE 응답을 생성하는 경우:

4. 유효성 검사 (Validation)

  • 요청 본문에 포함된 데이터를 유효성 검사할 수 있습니다. Spring의 Validator를 사용하거나 JSR-303 표준 유효성 검사를 적용할 수 있습니다.
    public class PersonHandler {
    
        private final Validator validator = new PersonValidator();
    
        public ServerResponse createPerson(ServerRequest request) {
            Person person = request.body(Person.class);
            validate(person);  // 유효성 검사 적용
            repository.savePerson(person);
            return ServerResponse.ok().build();
        }
    
        private void validate(Person person) {
            Errors errors = new BeanPropertyBindingResult(person, "person");
            validator.validate(person, errors);
            if (errors.hasErrors()) {
                throw new ServerWebInputException(errors.toString());
            }
        }
    }
    • validate() 메서드를 사용해 Person 객체에 대한 유효성 검사를 수행하고, 오류가 있으면 400 Bad Request 응답을 발생시킵니다.
  • 예시: 유효성 검사 적용

Functional Endpoint의 라우팅

RouterFunction은 요청을 적절한 핸들러로 라우팅하는 역할을 합니다. RouterFunction은 주로 라우터 빌더를 통해 정의되며, 빌드된 라우터는 Spring Web MVC에서 자동으로 감지되어 사용됩니다.

import static org.springframework.web.servlet.function.RouterFunctions.route;
import static org.springframework.http.MediaType.APPLICATION_JSON;

RouterFunction<ServerResponse> personRoutes = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();

이 라우팅 설정은 HTTP 메서드와 경로에 따라 적절한 핸들러를 호출하는 구조입니다.

 

Spring Web MVC의 Functional Endpoints는 기존의 애너테이션 기반 방식과는 다른 함수형 스타일의 프로그래밍을 지원하며, 더 간결하고 명확한 요청 처리 방식입니다. HandlerFunctionRouterFunction을 사용하여 HTTP 요청을 처리할 수 있으며, 비동기 응답, SSE, 유효성 검사 등의 기능도 지원합니다. 이 모델은 간결한 코드 작성과 유지보수를 용이하게 해주는 동시에, 함수형 스타일을 통해 더 유연한 라우팅과 요청 처리를 제공합니다.