Browse Source

Extract some WebFlux functional snippets

See gh-36089
pull/36095/head
Sébastien Deleuze 4 weeks ago
parent
commit
6a28f06b2e
  1. 375
      framework-docs/modules/ROOT/pages/web/webflux-functional.adoc
  2. 19
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerclasses/Person.java
  3. 67
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerclasses/PersonHandler.java
  4. 29
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerclasses/PersonRepository.java
  5. 55
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerfilterfunction/RouterConfiguration.java
  6. 22
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerfilterfunction/SecurityManager.java
  7. 58
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlervalidation/PersonHandler.java
  8. 34
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlervalidation/PersonValidator.java
  9. 36
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnpredicates/RouterConfiguration.java
  10. 55
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnrequest/PartEventHandler.java
  11. 33
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnrequest/RequestHandler.java
  12. 37
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnresponse/ResponseHandler.java
  13. 78
      framework-docs/src/main/java/org/springframework/docs/web/webfluxfnroutes/RouterConfiguration.java
  14. 19
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerclasses/Person.kt
  15. 62
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerclasses/PersonHandler.kt
  16. 28
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerclasses/PersonRepository.kt
  17. 58
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerfilterfunction/RouterConfiguration.kt
  18. 22
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerfilterfunction/SecurityManager.kt
  19. 53
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlervalidation/PersonHandler.kt
  20. 32
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlervalidation/PersonValidator.kt
  21. 37
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnpredicates/RouterConfiguration.kt
  22. 55
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnrequest/PartEventHandler.kt
  23. 31
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnrequest/RequestHandler.kt
  24. 35
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnresponse/ResponseHandler.kt
  25. 65
      framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnroutes/RouterConfiguration.kt

375
framework-docs/modules/ROOT/pages/web/webflux-functional.adoc

@ -235,87 +235,14 @@ val map = request.awaitMultipartData() @@ -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 @@ -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:: @@ -353,7 +263,7 @@ Java::
[source,java]
----
URI location = ...
ServerResponse.created(location).build();
return ServerResponse.created(location).build();
----
Kotlin::
@ -361,7 +271,7 @@ 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:: @@ -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 @@ -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 @@ -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. @@ -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: @@ -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 @@ -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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerclasses/Person.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerclasses/PersonHandler.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerclasses/PersonRepository.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerfilterfunction/RouterConfiguration.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlerfilterfunction/SecurityManager.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlervalidation/PersonHandler.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnhandlervalidation/PersonValidator.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnpredicates/RouterConfiguration.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnrequest/PartEventHandler.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnrequest/RequestHandler.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnresponse/ResponseHandler.java

@ -0,0 +1,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
framework-docs/src/main/java/org/springframework/docs/web/webfluxfnroutes/RouterConfiguration.java

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerclasses/Person.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerclasses/PersonHandler.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerclasses/PersonRepository.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerfilterfunction/RouterConfiguration.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlerfilterfunction/SecurityManager.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlervalidation/PersonHandler.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnhandlervalidation/PersonValidator.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnpredicates/RouterConfiguration.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnrequest/PartEventHandler.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnrequest/RequestHandler.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnresponse/ResponseHandler.kt

@ -0,0 +1,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
framework-docs/src/main/kotlin/org/springframework/docs/web/webfluxfnroutes/RouterConfiguration.kt

@ -0,0 +1,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")
}
}
Loading…
Cancel
Save