mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Extract some WebFlux functional snippets
See gh-36089
This commit is contained in:
@@ -235,87 +235,14 @@ val map = request.awaitMultipartData()
|
||||
|
||||
The following example shows how to access multipart data, one at a time, in streaming fashion:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
|
||||
allPartEvents.windowUntil(PartEvent::isLast)
|
||||
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
|
||||
if (signal.hasValue()) {
|
||||
PartEvent event = signal.get();
|
||||
if (event instanceof FormPartEvent formEvent) {
|
||||
String value = formEvent.value();
|
||||
// handle form field
|
||||
}
|
||||
else if (event instanceof FilePartEvent fileEvent) {
|
||||
String filename = fileEvent.filename();
|
||||
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
|
||||
// handle file upload
|
||||
}
|
||||
else {
|
||||
return Mono.error(new RuntimeException("Unexpected event: " + event));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return partEvents; // either complete or error signal
|
||||
}
|
||||
}));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val allPartEvents = request.bodyToFlux<PartEvent>()
|
||||
allPartEvents.windowUntil(PartEvent::isLast)
|
||||
.concatMap {
|
||||
it.switchOnFirst { signal, partEvents ->
|
||||
if (signal.hasValue()) {
|
||||
val event = signal.get()
|
||||
if (event is FormPartEvent) {
|
||||
val value: String = event.value()
|
||||
// handle form field
|
||||
} else if (event is FilePartEvent) {
|
||||
val filename: String = event.filename()
|
||||
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content)
|
||||
// handle file upload
|
||||
} else {
|
||||
return@switchOnFirst Mono.error(RuntimeException("Unexpected event: $event"))
|
||||
}
|
||||
} else {
|
||||
return@switchOnFirst partEvents // either complete or error signal
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./PartEventHandler[tag=snippet,indent=0]
|
||||
|
||||
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
|
||||
and also shows how to customize the `DataBinder`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val pet = request.bind(Pet::class.java) { dataBinder -> dataBinder.setAllowedFields("name") }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
include-code::./RequestHandler[tag=snippet,indent=0]
|
||||
|
||||
[[webflux-fn-response]]
|
||||
=== ServerResponse
|
||||
@@ -325,24 +252,7 @@ a `build` method to create it. You can use the builder to set the response statu
|
||||
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
|
||||
content:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val person: Mono<Person> = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person::class.java)
|
||||
----
|
||||
======
|
||||
include-code::./ResponseHandler[tag=snippet,indent=0]
|
||||
|
||||
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
|
||||
|
||||
@@ -353,7 +263,7 @@ Java::
|
||||
[source,java]
|
||||
----
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
return ServerResponse.created(location).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -361,7 +271,7 @@ Kotlin::
|
||||
[source,kotlin]
|
||||
----
|
||||
val location: URI = ...
|
||||
ServerResponse.created(location).build()
|
||||
return ServerResponse.created(location).build()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -374,14 +284,14 @@ Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
|
||||
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
|
||||
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -416,87 +326,7 @@ Therefore, it is useful to group related handler functions together into a handl
|
||||
has a similar role as `@Controller` in an annotation-based application.
|
||||
For example, the following class exposes a reactive `Person` repository:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
Note that `PersonRepository.savePerson(Person)` returns `Mono<Void>`: an empty `Mono` that emits
|
||||
a completion signal when the person has been read from the request and stored. So we use the
|
||||
`build(Publisher<Void>)` method to send a response when that completion signal is received (that is,
|
||||
when the `Person` has been saved).
|
||||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we use `switchIfEmpty(Mono<T>)` to return a 404 Not Found response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
Note that `PersonRepository.savePerson(Person)` is a suspending function with no return type.
|
||||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
======
|
||||
--
|
||||
include-code::./PersonHandler[tag=snippet,indent=0]
|
||||
|
||||
[[webflux-fn-handler-validation]]
|
||||
=== Validation
|
||||
@@ -505,66 +335,7 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado
|
||||
apply validation to the request body. For example, given a custom Spring
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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>
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
======
|
||||
include-code::./PersonHandler[tag=snippet,indent=0]
|
||||
|
||||
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
|
||||
a global `Validator` instance based on `LocalValidatorFactoryBean`.
|
||||
@@ -602,28 +373,7 @@ path, headers, xref:#api-version[API version], and more.
|
||||
|
||||
The following example uses an `Accept` header, request predicate:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
|
||||
request -> ServerResponse.ok().bodyValue("Hello World")).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val route = coRouter {
|
||||
GET("/hello-world", accept(MediaType.TEXT_PLAIN)) {
|
||||
ServerResponse.ok().bodyValueAndAwait("Hello World")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./RouterConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can compose multiple request predicates together by using:
|
||||
|
||||
@@ -658,61 +408,7 @@ There are also other ways to compose multiple router functions together:
|
||||
|
||||
The following example shows the composition of four routes:
|
||||
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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();
|
||||
----
|
||||
<1> pass:q[`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.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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> pass:q[`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.
|
||||
======
|
||||
include-code::./RouterConfiguration[tag=snippet,indent=0]
|
||||
|
||||
[[nested-routes]]
|
||||
=== Nested Routes
|
||||
@@ -1077,54 +773,7 @@ Now we can add a simple security filter to our route, assuming that we have a `S
|
||||
can determine whether a particular path is allowed.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val securityManager: SecurityManager = ...
|
||||
|
||||
val route = router {
|
||||
("/person" and accept(APPLICATION_JSON)).nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET(handler::listPeople)
|
||||
POST(handler::createPerson)
|
||||
filter { request, next ->
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
next(request)
|
||||
}
|
||||
else {
|
||||
status(UNAUTHORIZED).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./RouterConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
|
||||
We only let the handler function be run when access is allowed.
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerclasses;
|
||||
|
||||
public record Person(String name) { }
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerclasses;
|
||||
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
|
||||
// tag::snippet[]
|
||||
public class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
// listPeople is a handler function that returns all Person objects found
|
||||
// in the repository as JSON
|
||||
public Mono<ServerResponse> listPeople(ServerRequest request) {
|
||||
Flux<Person> people = repository.allPeople();
|
||||
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
|
||||
}
|
||||
|
||||
// createPerson is a handler function that stores a new Person contained
|
||||
// in the request body.
|
||||
// Note that PersonRepository.savePerson(Person) returns Mono<Void>: an empty
|
||||
// Mono that emits a completion signal when the person has been read from the
|
||||
// request and stored. So we use the build(Publisher<Void>) method to send a
|
||||
// response when that completion signal is received (that is, when the Person
|
||||
// has been saved)
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) {
|
||||
Mono<Person> person = request.bodyToMono(Person.class);
|
||||
return ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
// getPerson is a handler function that returns a single person, identified by
|
||||
// the id path variable. We retrieve that Person from the repository and create
|
||||
// a JSON response, if it is found. If it is not found, we use switchIfEmpty(Mono<T>)
|
||||
// to return a 404 Not Found response.
|
||||
public Mono<ServerResponse> getPerson(ServerRequest request) {
|
||||
int personId = Integer.valueOf(request.pathVariable("id"));
|
||||
return repository.getPerson(personId)
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerclasses;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface PersonRepository {
|
||||
|
||||
Flux<Person> allPeople();
|
||||
|
||||
Mono<Void> savePerson(Mono<Person> person);
|
||||
|
||||
Mono<Person> getPerson(int id);
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerfilterfunction;
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonHandler;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
|
||||
public class RouterConfiguration {
|
||||
|
||||
public RouterFunction<ServerResponse> route(PersonHandler handler) {
|
||||
// tag::snippet[]
|
||||
SecurityManager securityManager = getSecurityManager();
|
||||
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.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();
|
||||
// end::snippet[]
|
||||
return route;
|
||||
}
|
||||
|
||||
SecurityManager getSecurityManager() {
|
||||
return path -> false;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerfilterfunction;
|
||||
|
||||
public interface SecurityManager {
|
||||
|
||||
boolean allowAccessTo(String path);
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlervalidation;
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.Person;
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonRepository;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
|
||||
// tag::snippet[]
|
||||
public class PersonHandler {
|
||||
|
||||
// Create Validator instance
|
||||
private final Validator validator = new PersonValidator();
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) {
|
||||
// Apply validation
|
||||
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate);
|
||||
return ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlervalidation;
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.Person;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
public class PersonValidator implements Validator {
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return Person.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
// Validation logic
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnpredicates;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
|
||||
public class RouterConfiguration {
|
||||
|
||||
public RouterFunction<ServerResponse> route() {
|
||||
// tag::snippet[]
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
|
||||
request -> ServerResponse.ok().bodyValue("Hello World")).build();
|
||||
// end::snippet[]
|
||||
return route;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnrequest;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.codec.multipart.FilePartEvent;
|
||||
import org.springframework.http.codec.multipart.FormPartEvent;
|
||||
import org.springframework.http.codec.multipart.PartEvent;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class PartEventHandler {
|
||||
|
||||
public void handle(ServerRequest request) {
|
||||
// tag::snippet[]
|
||||
request.bodyToFlux(PartEvent.class).windowUntil(PartEvent::isLast)
|
||||
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
|
||||
if (signal.hasValue()) {
|
||||
PartEvent event = signal.get();
|
||||
if (event instanceof FormPartEvent formEvent) {
|
||||
String value = formEvent.value();
|
||||
// handle form field
|
||||
}
|
||||
else if (event instanceof FilePartEvent fileEvent) {
|
||||
String filename = fileEvent.filename();
|
||||
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
|
||||
// handle file upload
|
||||
}
|
||||
else {
|
||||
return Mono.error(new RuntimeException("Unexpected event: " + event));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return partEvents; // either complete or error signal
|
||||
}
|
||||
return Mono.empty();
|
||||
}));
|
||||
// end::snippet[]
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnrequest;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
|
||||
public class RequestHandler {
|
||||
|
||||
public void bind(ServerRequest request) {
|
||||
// tag::snippet[]
|
||||
Mono<Pet> pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
// end::snippet[]
|
||||
}
|
||||
|
||||
record Pet(String name) { }
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnresponse;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class ResponseHandler {
|
||||
|
||||
public Mono<ServerResponse> createResponse() {
|
||||
// tag::snippet[]
|
||||
Mono<Person> person = getPerson();
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
// end::snippet[]
|
||||
}
|
||||
|
||||
private Mono<Person> getPerson() {
|
||||
return Mono.just(new Person("foo"));
|
||||
}
|
||||
|
||||
record Person(String name) { }
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnroutes;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.Person;
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonHandler;
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonRepository;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
public class RouterConfiguration {
|
||||
|
||||
public RouterFunction<ServerResponse> routes() {
|
||||
// tag::snippet[]
|
||||
PersonRepository repository = getPersonRepository();
|
||||
PersonHandler handler = new PersonHandler(repository);
|
||||
|
||||
RouterFunction<ServerResponse> otherRoute = getOtherRoute();
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
// GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
|
||||
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
|
||||
// GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
|
||||
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
|
||||
// POST /person with no additional predicates is mapped to PersonHandler.createPerson
|
||||
.POST("/person", handler::createPerson)
|
||||
// otherRoute is a router function that is created elsewhere and added to the route built
|
||||
.add(otherRoute)
|
||||
.build();
|
||||
// end::snippet[]
|
||||
return route;
|
||||
}
|
||||
|
||||
PersonRepository getPersonRepository() {
|
||||
return new PersonRepository() {
|
||||
@Override
|
||||
public Flux<Person> allPeople() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> savePerson(Mono<Person> person) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Person> getPerson(int id) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
RouterFunction<ServerResponse> getOtherRoute() {
|
||||
return RouterFunctions.route().build();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerclasses
|
||||
|
||||
data class Person(val name: String)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerclasses
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
import org.springframework.web.reactive.function.server.ServerRequest
|
||||
import org.springframework.web.reactive.function.server.ServerResponse
|
||||
import org.springframework.web.reactive.function.server.awaitBody
|
||||
import org.springframework.web.reactive.function.server.bodyAndAwait
|
||||
import org.springframework.web.reactive.function.server.bodyValueAndAwait
|
||||
import org.springframework.web.reactive.function.server.buildAndAwait
|
||||
|
||||
// tag::snippet[]
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
// listPeople is a handler function that returns all Person objects found
|
||||
// in the repository as JSON
|
||||
suspend fun listPeople(request: ServerRequest): ServerResponse {
|
||||
val people: Flow<Person> = repository.allPeople()
|
||||
return ServerResponse.ok().contentType(APPLICATION_JSON).bodyAndAwait(people)
|
||||
}
|
||||
|
||||
// createPerson is a handler function that stores a new Person contained
|
||||
// in the request body.
|
||||
// Note that PersonRepository.savePerson(Person) returns Mono<Void>: an empty
|
||||
// Mono that emits a completion signal when the person has been read from the
|
||||
// request and stored. So we use the build(Publisher<Void>) method to send a
|
||||
// response when that completion signal is received (that is, when the Person
|
||||
// has been saved)
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse {
|
||||
val person = request.awaitBody<Person>()
|
||||
repository.savePerson(person)
|
||||
return ServerResponse.ok().buildAndAwait()
|
||||
}
|
||||
|
||||
// getPerson is a handler function that returns a single person, identified by
|
||||
// the id path variable. We retrieve that Person from the repository and create
|
||||
// a JSON response, if it is found. If it is not found, we use switchIfEmpty(Mono<T>)
|
||||
// to return a 404 Not Found response.
|
||||
suspend fun getPerson(request: ServerRequest): ServerResponse {
|
||||
val personId = request.pathVariable("id").toInt()
|
||||
return repository.getPerson(personId)?.let { ServerResponse.ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
|
||||
?: ServerResponse.notFound().buildAndAwait()
|
||||
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerclasses
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PersonRepository {
|
||||
|
||||
fun allPeople(): Flow<Person>
|
||||
|
||||
suspend fun savePerson(person: Person)
|
||||
|
||||
suspend fun getPerson(id: Int): Person?
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerfilterfunction
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonHandler
|
||||
import org.springframework.http.HttpStatus.UNAUTHORIZED
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
import org.springframework.web.reactive.function.server.RouterFunction
|
||||
import org.springframework.web.reactive.function.server.ServerResponse
|
||||
import org.springframework.web.reactive.function.server.buildAndAwait
|
||||
import org.springframework.web.reactive.function.server.coRouter
|
||||
|
||||
class RouterConfiguration {
|
||||
|
||||
fun route(handler: PersonHandler): RouterFunction<ServerResponse> {
|
||||
// tag::snippet[]
|
||||
val securityManager: SecurityManager = getSecurityManager()
|
||||
|
||||
val route = coRouter {
|
||||
("/person" and accept(APPLICATION_JSON)).nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET("/", handler::listPeople)
|
||||
POST("/", handler::createPerson)
|
||||
filter { request, next ->
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
next(request)
|
||||
}
|
||||
else {
|
||||
ServerResponse.status(UNAUTHORIZED).buildAndAwait()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
return route
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getSecurityManager() = object : SecurityManager {
|
||||
override fun allowAccessTo(path: String): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlerfilterfunction
|
||||
|
||||
interface SecurityManager {
|
||||
|
||||
fun allowAccessTo(path: String): Boolean
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlervalidation
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.Person
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonRepository
|
||||
import org.springframework.validation.BeanPropertyBindingResult
|
||||
import org.springframework.validation.Errors
|
||||
import org.springframework.web.reactive.function.server.ServerRequest
|
||||
import org.springframework.web.reactive.function.server.ServerResponse
|
||||
import org.springframework.web.reactive.function.server.awaitBody
|
||||
import org.springframework.web.reactive.function.server.buildAndAwait
|
||||
import org.springframework.web.server.ServerWebInputException
|
||||
|
||||
// tag::snippet[]
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
// Create Validator instance
|
||||
private val validator = PersonValidator()
|
||||
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse {
|
||||
val person = request.awaitBody<Person>()
|
||||
// Apply validation
|
||||
validate(person)
|
||||
repository.savePerson(person)
|
||||
return ServerResponse.ok().buildAndAwait()
|
||||
}
|
||||
|
||||
private fun validate(person: Person) {
|
||||
val errors: Errors = BeanPropertyBindingResult(person, "person")
|
||||
validator.validate(person, errors)
|
||||
if (errors.hasErrors()) {
|
||||
// Raise exception for a 400 response
|
||||
throw ServerWebInputException(errors.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnhandlervalidation
|
||||
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.Person
|
||||
import org.springframework.validation.Errors
|
||||
import org.springframework.validation.Validator
|
||||
|
||||
class PersonValidator : Validator {
|
||||
|
||||
override fun supports(clazz: Class<*>): Boolean {
|
||||
return Person::class.java.isAssignableFrom(clazz)
|
||||
}
|
||||
|
||||
override fun validate(target: Any, errors: Errors) {
|
||||
// Validation logic
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnpredicates
|
||||
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.reactive.function.server.RouterFunction
|
||||
import org.springframework.web.reactive.function.server.ServerResponse
|
||||
import org.springframework.web.reactive.function.server.bodyValueAndAwait
|
||||
import org.springframework.web.reactive.function.server.coRouter
|
||||
|
||||
class RouterConfiguration {
|
||||
|
||||
fun route(): RouterFunction<ServerResponse> {
|
||||
// tag::snippet[]
|
||||
val route = coRouter {
|
||||
GET("/hello-world", accept(MediaType.TEXT_PLAIN)) {
|
||||
ServerResponse.ok().bodyValueAndAwait("Hello World")
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
return route
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnrequest
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer
|
||||
import org.springframework.http.codec.multipart.FilePartEvent
|
||||
import org.springframework.http.codec.multipart.FormPartEvent
|
||||
import org.springframework.http.codec.multipart.PartEvent
|
||||
import org.springframework.web.reactive.function.server.ServerRequest
|
||||
import org.springframework.web.reactive.function.server.bodyToFlux
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class PartEventHandler {
|
||||
|
||||
fun handle(request: ServerRequest) {
|
||||
// tag::snippet[]
|
||||
request.bodyToFlux<PartEvent>().windowUntil(PartEvent::isLast)
|
||||
.concatMap {
|
||||
it.switchOnFirst { signal, partEvents ->
|
||||
if (signal.hasValue()) {
|
||||
val event = signal.get()
|
||||
if (event is FormPartEvent) {
|
||||
val value: String = event.value()
|
||||
// handle form field
|
||||
} else if (event is FilePartEvent) {
|
||||
val filename: String = event.filename()
|
||||
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content)
|
||||
// handle file upload
|
||||
} else {
|
||||
return@switchOnFirst Mono.error(RuntimeException("Unexpected event: $event"))
|
||||
}
|
||||
} else {
|
||||
return@switchOnFirst partEvents // either complete or error signal
|
||||
}
|
||||
Mono.empty()
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnrequest
|
||||
|
||||
import org.springframework.web.reactive.function.server.ServerRequest
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class RequestHandler {
|
||||
|
||||
fun bind(request: ServerRequest) {
|
||||
// tag::snippet[]
|
||||
val pet: Mono<Pet> = request.bind(Pet::class.java) { dataBinder -> dataBinder.setAllowedFields("name") }
|
||||
// end::snippet[]
|
||||
}
|
||||
|
||||
data class Pet(val name: String)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnresponse
|
||||
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.reactive.function.server.ServerResponse
|
||||
import org.springframework.web.reactive.function.server.bodyValueWithTypeAndAwait
|
||||
|
||||
class ResponseHandler {
|
||||
|
||||
suspend fun createResponse(): ServerResponse {
|
||||
// tag::snippet[]
|
||||
val person: Person = getPerson()
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValueWithTypeAndAwait<Person>(person)
|
||||
// end::snippet[]
|
||||
}
|
||||
|
||||
fun getPerson() = Person("foo")
|
||||
|
||||
data class Person(val name: String)
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webfluxfnroutes
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.Person
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonHandler
|
||||
import org.springframework.docs.web.webfluxfnhandlerclasses.PersonRepository
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
import org.springframework.web.reactive.function.server.RouterFunction
|
||||
import org.springframework.web.reactive.function.server.ServerResponse
|
||||
import org.springframework.web.reactive.function.server.coRouter
|
||||
|
||||
class RouterConfiguration {
|
||||
|
||||
fun routes(): RouterFunction<ServerResponse> {
|
||||
// tag::snippet[]
|
||||
val repository: PersonRepository = getPersonRepository()
|
||||
val handler = PersonHandler(repository)
|
||||
|
||||
val otherRoute: RouterFunction<ServerResponse> = getOtherRoute()
|
||||
|
||||
val route = coRouter {
|
||||
// GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
|
||||
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
|
||||
// GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
|
||||
GET("/person", accept(APPLICATION_JSON), handler::listPeople)
|
||||
// POST /person with no additional predicates is mapped to PersonHandler.createPerson
|
||||
POST("/person", handler::createPerson)
|
||||
// otherRoute is a router function that is created elsewhere and added to the route built
|
||||
}.and(otherRoute)
|
||||
// end::snippet[]
|
||||
return route
|
||||
}
|
||||
}
|
||||
|
||||
fun getOtherRoute() = coRouter { }
|
||||
|
||||
fun getPersonRepository() = object: PersonRepository {
|
||||
override fun allPeople(): Flow<Person> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun savePerson(person: Person) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getPerson(id: Int): Person? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user