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)
.header("X-RequestHeader", "Value")
.build()))
.POST(handler::createPerson))
.after((request, response) -> logResponse(response))
.build();
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#webmvc-fn
Web on Servlet Stack
Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonl
docs.spring.io
틀린 해석이나 내용이 있다면 알려주세요 😃
이 많은 문서를 언제 다 보고 있지 했는데
하루에 한 챕터는 읽자 하는 마음으로 하다 보니 금방인 것 같다
계속 계속 열심히 꾸준히 해봐야겠다
1.4. Functional Endpoints
Spring Web MVC에는 요청을 라우팅하고 처리하는 데 사용되는 함수와 계약이 불변성을 위해 설계된 경량 기능 프로그래밍 모델인 WebMvc.fn이 포함되어 있다.
주석 기반 프로그래밍 모델의 대안이지만 동일한 DispatcherServlet에서 실행된다.
1.4.1. Overview
WebMvc.fn에서 HTTP 요청은 HandlerFunction으로 처리된다
이 함수는 ServerRequest를 받고 ServerResponse로 반환한다
요청 및 응답 object 모두 HTTP 요청 및 응답에 대한 액세스를 제공하는 불변한 약속을 가지고 있다
HandlerFunction은 애노테이션 기반 프로그래밍 모델에서 @RequestMapping 메서드 내용과 동일하다
내가 정리한 RequestMapping
https://delightpip.tistory.com/32
[Spring framework Web MVC docs] 1.3.2 Request Mapping
틀린 해석이 있다면 알려주세요 spring document 공식문서 보고 공부하는 중 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-requestmapping @Controller @RestController 기본 클래스를 확장하거
delightpip.tistory.com
들어오는 request는 RouterFunction이 있는 처리 함수(ServerRequest를 받고 선택적 HandlerFunction, Optional<HandlerFunction> 을 반환하는 함수)로 라우팅된다
라우터기능이 일치하면 핸들러 기능이 반환되며, 그게 아니라면 empty Optional 이다
RouterFunction은 @RequestMapping 주석과 동일하지만 라우터기능이 데이터 뿐아니라 동작도 제공하는 차이가 있다
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 ServerResponse listPeople(ServerRequest request) {
// ...
}
public ServerResponse createPerson(ServerRequest request) {
// ...
}
public ServerResponse getPerson(ServerRequest request) {
// ...
}
}
저 route() << 에서 만들어진다
If you register the RouterFunction as a bean, for instance by exposing it in a @Configuration class, it will be auto-detected by the servlet, as explained in Running a Server.
Web on Servlet Stack
Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonl
docs.spring.io
1.4.2. HandlerFunction
ServerRequest 및 ServerResponse는
headers, body, method, status code를 포함하여 HTTP 요청 및 응답에 대한 JDK 8 친화적 액세스를 제공하는 변경할 수 없는 인터페이스이다
ServerRequest
ServerRequest 는 HTTP method, URI, headers, query parameters 들에 대한 액세스를 제공하고
body 에 대한 액세스를 body method를 통해 제공한다
// body 추출
String string = request.body(String.class);
// JSON, XML 직렬화 디코딩 body 추출
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
// 매개변수 액세스
MultiValueMap<String, String> params = request.params();
ServerResponse
Response 는 응답에 대한 액세스를 제공 후 변경할 수 없기 때문에, build 메서드를 사용하여 생성한다
응답상태 설정, 응답헤더 추가 및 body 를 추가할 수 있다
//ok = status 200
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
//Location header만 있는 201 CREATED 작성
URI location = ...
ServerResponse.created(location).build();
// CompletableFuture, Publisher 또는 ReactiveAdapterRegistry에서 지원하는 다른 유형의 형식으로 비동기 결과를 본문으로 사용
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
// status, header, body 포함 모두 비동기 유형을 동반하는 경우
// accepts CompletableFuture<ServerResponse>, Publisher<ServerResponse>, or any other asynchronous type supported by the ReactiveAdapterRegistry
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
.map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);
Server-Sent Events can be provided via the static sse method on ServerResponse. The builder provided by that method allows you to send Strings, or other objects as JSON.
https://html.spec.whatwg.org/multipage/
HTML Standard
HTML Living Standard — Last Updated 6 February 2023
html.spec.whatwg.org
public RouterFunction<ServerResponse> sse() {
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
// Save the sseBuilder object somewhere..
}));
}
// In some other thread, sending a String
sseBuilder.send("Hello world");
// Or an object, which will be transformed into JSON
Person person = ...
sseBuilder.send(person);
// Customize the event by using the other methods
sseBuilder.id("42")
.event("sse event")
.data(person);
// and done at some point
sseBuilder.complete();
Handler Classes
람다 작성
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body("Hello World");
편리한 방법이지만 실질적으로 여러 함수, 여러 인라인 때문에 지저분해보일 수 있다
Therefore, it is useful to group related handler functions together into a handler class, which has a similar role as @Controller in an annotation-based application.
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public ServerResponse listPeople(ServerRequest request) {
// listPeoplePerson 의 객체를 json 으로 반환
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
public ServerResponse createPerson(ServerRequest request) throws Exception {
// request body 에 포함되어있는 createPersonPerson 을 가져와서 저장
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
public ServerResponse getPerson(ServerRequest request) {
// 변수로 지정된 getPersonid 경로의 데이터를 찾아서 반환
// notFound() == status 404 Not Found
int personId = Integer.parseInt(request.pathVariable("id"));
Person person = repository.getPerson(personId);
if (person != null) {
return ok().contentType(APPLICATION_JSON).body(person);
}
else {
return ServerResponse.notFound().build();
}
}
}
Validation
function endpoint 에서 validation을 사용여 request body에서 받은 정보에 대한 유효성 검사를 할 수 있다
public class PersonHandler {
private final Validator validator = new PersonValidator();
// -> Validator instance create
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
validate(person); // Apply validation
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
// Raise exception for a 400 response
throw new ServerWebInputException(errors.toString());
}
}
}
1.4.3.RouterFunction
Router 기능은 request를 해당 HandlerFUnction으로 라우팅시킨다
일반적으로 함수를 직접 작성하지는 않고 RouterFunctions Utility class의 method를 사용하여 생성한다
RouterFunctions.route()(매개 변수 없음)는 라우터 기능을 생성하기 위한 좋은 빌더를 제공하고있고,
RouterFunctions.route(RequestPredicate, HandlerFunction)는 라우터를 생성하는 직접적인 방법을 제공하고 있다
일반적으로 route() 빌더를 사용하는 것이 좋다.
이는 찾기 hard0to-discover static imports를 요구하지 않고 일반적인 매핑 시나리오에 대한 편리한 바로 가기를 제공한다.
라우터 기능 빌더는 GET 요청에 대한 매핑을 생성하기 위해 GET(String, HandlerFunction) 메서드를 제공하고 있다. (POST(String, HandlerFunction)는 POST용)
HTTP 메서드 기반 매핑 외에도 경로 빌더는 요청에 매핑할 때 추가 조건자를 도입하는 방법을 제공한다.
각 HTTP 메서드에는 RequestPredicate를 매개 변수로 사용하는 overloaded된 variant이 있으며 이를 통해 추가 제약 조건을 표현할 수 있다.
Predicates
고유 RequestPredicate를 작성할 수 있지만 RequestPredicates Utility class는 요청 경로, HTTP method, content-type 에 따라 일반적으로 사용되는 구현을 제공한다
uses a request predicate to create a constraint based on the Accept header
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
- RequestPredicate.and(RequestPredicate) — 둘 다 일치해야함
- RequestPredicate.or(RequestPredicate) — 둘 중 하나 일치
RequestPredicates.GET(String)은
RequestPredicates.method(HttpMethod) 및 RequestPredicates.path(String)로 구성된다
빌더가 내부적으로 RequestPredicates.GET을 사용하고
이를 수락 조건자로 구성하므로 위에 표시된 예에서도 두 개의 request predicate를 사용한다
Routes
Route 는 순서대로 처리된다
첫번째 경로에서 일치하지않으면 다음 경로를 찾는다
일반적인 경로보다 구체적인 경로를 먼저 선언해야한다.
Router 기능을 Spring Bean에 등록할때도 가장 구체적인 Controller method가 자동으로 선택되는 주석 기반프로그래밍 모델과 다르다. router function builder를 사용할 때 정의된 모든 경로는 build()에서 반환되는 하나의 RouterFunction으로 구성된다
- add(RouterFunction) on the RouterFunctions.route() builder
- RouterFunction.and(RouterFunction)
- RouterFunction.andRoute(RequestPredicate, HandlerFunction) — shortcut for RouterFunction.and() with nested RouterFunctions.route().
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();
1 | GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson |
2 | GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople |
3 | POST /person with no additional predicates is mapped to PersonHandler.createPerson, and |
4 | otherRoute is a router function that is created elsewhere, and added to the route built. |
Nested Routes
중첩되는 Route 는 @RequestMapping 주석을 사용하여 중복을 제거한다
WebMvc.fn 에서 router function builder의 path predicates를 통해 path predicates를 공유할 수 있다
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST(handler::createPerson))
.build();
.path("/person", onsumer that takes the router builder)
path-base nesting 이 일반적이지만
모든 종류의 predicate에 nest 할 수 있다
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
1.4.4. Running a Server
DispathcherHandler 를 통해 라우터 기능을 실행한다.
DispatcherHandler ->
https://delightpip.tistory.com/47
[Spring framework Web MVC docs] 1.1.1.Context Hierarchy ~ 1.1.5. Processing
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-context-hierarchy Web on Servlet Stack Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the ver
delightpip.tistory.com
MVC Java 구성은 이 endpoint 지원을 위해 두가지를 지원한다
- RouterFunctionMapping: Detects one or more RouterFunction<?> beans in the Spring configuration, orders them, combines them through RouterFunction.andOther, and routes requests to the resulting composed RouterFunction.
- HandlerFunctionAdapter: Simple adapter that lets DispatcherHandler invoke a HandlerFunction that was mapped to a request.
The preceding components let functional endpoints fit within the DispatcherServlet request processing lifecycle and also (potentially) run side by side with annotated controllers, if any are declared. It is also how functional endpoints are enabled by the Spring Boot Web starter.
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
1.4.5. Filtering Handler Functions
routing function builder 에서 before, after 또는 filter method 를 사용하여 함수를 피렅링할 수 있다
@ControllerAdvice, ServletFilter 애노테이션을 사용하면 유사한 기능을 얻을 수 있고
필터는 builder 가 구축한 모든 경로에 적용된다
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)
.header("X-RequestHeader", "Value")
.build()))
.POST(handler::createPerson))
.after((request, response) -> logResponse(response))
.build();
1. .before << 위의 GET경로 두개에서만 요청헤더를 추가함
2. .after 은 nest 경로를 포함하여 모든 경로에서 적용
rounter builder 의 filter method 에서는 HandlerFilterFunction을 사용한다
ServeerRequest 및 HandlerFunction을 사용하고, ServerResponse를 반환한다
SecurityManager 경로 허용을 결정하고 보안 필터를 추가하는 예제
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
The preceding example demonstrates that invoking the next.handle(ServerRequest) is optional. We only let the handler function be run when access is allowed.
-> next.handle 호출이 선택사항이기 때문에 액세스 허용인 경우만 핸들러 기능이 실행됨
'Web > spring' 카테고리의 다른 글
[Spring framework Web MVC docs] 1.7. CORS (0) | 2023.02.14 |
---|---|
[Spring framework testing] 5. Spring TestContext Framework (3) (0) | 2023.02.13 |
[Spring framework Web MVC docs] 1.2. Filters (0) | 2023.02.11 |
[Spring framework Web MVC docs] 1.12. MVC Config (0) | 2023.02.11 |
[Spring framework Web MVC docs] 1.1.6.Path Matching ~ 1.1.7. Interception (0) | 2023.02.10 |