Migrate Structure

This commit is contained in:
Rob Winch
2023-04-19 10:23:59 -05:00
committed by rstoyanchev
parent a78836c18b
commit 3fe7c65218
80 changed files with 2231 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
[[web-integration]]
= Other Web Frameworks
This chapter details Spring's integration with third-party web frameworks.
One of the core value propositions of the Spring Framework is that of enabling
_choice_. In a general sense, Spring does not force you to use or buy into any
particular architecture, technology, or methodology (although it certainly recommends
some over others). This freedom to pick and choose the architecture, technology, or
methodology that is most relevant to a developer and their development team is
arguably most evident in the web area, where Spring provides its own web frameworks
(<<mvc, Spring MVC>> and <<web-reactive.adoc#webflux, Spring WebFlux>>) while, at the same time,
supporting integration with a number of popular third-party web frameworks.
[[web-integration-common]]
== Common Configuration
Before diving into the integration specifics of each supported web framework, let us
first take a look at common Spring configuration that is not specific to any one web
framework. (This section is equally applicable to Spring's own web framework variants.)
One of the concepts (for want of a better word) espoused by Spring's lightweight
application model is that of a layered architecture. Remember that in a "classic"
layered architecture, the web layer is but one of many layers. It serves as one of the
entry points into a server-side application, and it delegates to service objects
(facades) that are defined in a service layer to satisfy business-specific (and
presentation-technology agnostic) use cases. In Spring, these service objects, any other
business-specific objects, data-access objects, and others exist in a distinct "business
context", which contains no web or presentation layer objects (presentation objects,
such as Spring MVC controllers, are typically configured in a distinct "presentation
context"). This section details how you can configure a Spring container (a
`WebApplicationContext`) that contains all of the 'business beans' in your application.
Moving on to specifics, all you need to do is declare a
{api-spring-framework}/web/context/ContextLoaderListener.html[`ContextLoaderListener`]
in the standard Jakarta EE servlet `web.xml` file of your web application and add a
`contextConfigLocation` `<context-param/>` section (in the same file) that defines which
set of Spring XML configuration files to load.
Consider the following `<listener/>` configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
----
Further consider the following `<context-param/>` configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
----
If you do not specify the `contextConfigLocation` context parameter, the
`ContextLoaderListener` looks for a file called `/WEB-INF/applicationContext.xml` to
load. Once the context files are loaded, Spring creates a
{api-spring-framework}/web/context/WebApplicationContext.html[`WebApplicationContext`]
object based on the bean definitions and stores it in the `ServletContext` of the web
application.
All Java web frameworks are built on top of the Servlet API, so you can use the
following code snippet to get access to this "business context" `ApplicationContext`
created by the `ContextLoaderListener`.
The following example shows how to get the `WebApplicationContext`:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
----
The
{api-spring-framework}/web/context/support/WebApplicationContextUtils.html[`WebApplicationContextUtils`]
class is for convenience, so you need not remember the name of the `ServletContext`
attribute. Its `getWebApplicationContext()` method returns `null` if an object
does not exist under the `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`
key. Rather than risk getting `NullPointerExceptions` in your application, it is better
to use the `getRequiredWebApplicationContext()` method. This method throws an exception
when the `ApplicationContext` is missing.
Once you have a reference to the `WebApplicationContext`, you can retrieve beans by their
name or type. Most developers retrieve beans by name and then cast them to one of their
implemented interfaces.
Fortunately, most of the frameworks in this section have simpler ways of looking up beans.
Not only do they make it easy to get beans from a Spring container, but they also let you
use dependency injection on their controllers. Each web framework section has more detail
on its specific integration strategies.
[[jsf]]
== JSF
JavaServer Faces (JSF) is the JCP's standard component-based, event-driven web
user interface framework. It is an official part of the Jakarta EE umbrella but also
individually usable, e.g. through embedding Mojarra or MyFaces within Tomcat.
Please note that recent versions of JSF became closely tied to CDI infrastructure
in application servers, with some new JSF functionality only working in such an
environment. Spring's JSF support is not actively evolved anymore and primarily
exists for migration purposes when modernizing older JSF-based applications.
The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
[[jsf-springbeanfaceselresolver]]
=== Spring Bean Resolver
`SpringBeanFacesELResolver` is a JSF compliant `ELResolver` implementation,
integrating with the standard Unified EL as used by JSF and JSP. It delegates to
Spring's "business context" `WebApplicationContext` first and then to the
default resolver of the underlying JSF implementation.
Configuration-wise, you can define `SpringBeanFacesELResolver` in your JSF
`faces-context.xml` file, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<faces-config>
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
...
</application>
</faces-config>
----
[[jsf-facescontextutils]]
=== Using `FacesContextUtils`
A custom `ELResolver` works well when mapping your properties to beans in
`faces-config.xml`, but, at times, you may need to explicitly grab a bean.
The {api-spring-framework}/web/jsf/FacesContextUtils.html[`FacesContextUtils`]
class makes this easy. It is similar to `WebApplicationContextUtils`, except that
it takes a `FacesContext` parameter rather than a `ServletContext` parameter.
The following example shows how to use `FacesContextUtils`:
[source,java,indent=0,subs="verbatim,quotes"]
----
ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
----
[[struts]]
== Apache Struts
Invented by Craig McClanahan, https://struts.apache.org[Struts] is an open-source project
hosted by the Apache Software Foundation. Struts 1.x greatly simplified the
JSP/Servlet programming paradigm and won over many developers who were using proprietary
frameworks. It simplified the programming model; it was open source; and it had a large
community, which let the project grow and become popular among Java web developers.
As a successor to the original Struts 1.x, check out Struts 2.x or more recent versions
as well as the Struts-provided
https://struts.apache.org/plugins/spring/[Spring Plugin] for built-in Spring integration.
[[tapestry]]
== Apache Tapestry
https://tapestry.apache.org/[Tapestry] is a "Component oriented framework for creating
dynamic, robust, highly scalable web applications in Java."
While Spring has its own <<mvc, powerful web layer>>, there are a number of unique
advantages to building an enterprise Java application by using a combination of Tapestry
for the web user interface and the Spring container for the lower layers.
For more information, see Tapestry's dedicated
https://tapestry.apache.org/integrating-with-spring-framework.html[integration module for Spring].
[[web-integration-resources]]
== Further Resources
The following links go to further resources about the various web frameworks described in
this chapter.
* The https://www.oracle.com/java/technologies/javaserverfaces.html[JSF] homepage
* The https://struts.apache.org/[Struts] homepage
* The https://tapestry.apache.org/[Tapestry] homepage

View File

@@ -0,0 +1,95 @@
In the context of web applications, _data binding_ involves the binding of HTTP request
parameters (that is, form data or query parameters) to properties in a model object and
its nested objects.
Only `public` properties following the
https://www.oracle.com/java/technologies/javase/javabeans-spec.html[JavaBeans naming conventions]
are exposed for data binding — for example, `public String getFirstName()` and
`public void setFirstName(String)` methods for a `firstName` property.
TIP: The model object, and its nested object graph, is also sometimes referred to as a
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
By default, Spring permits binding to all public properties in the model object graph.
This means you need to carefully consider what public properties the model has, since a
client could target any public property path, even some that are not expected to be
targeted for a given use case.
For example, given an HTTP form data endpoint, a malicious client could supply values for
properties that exist in the model object graph but are not part of the HTML form
presented in the browser. This could lead to data being set on the model object and any
of its nested objects, that is not expected to be updated.
The recommended approach is to use a _dedicated model object_ that exposes only
properties that are relevant for the form submission. For example, on a form for changing
a user's email address, the model object should declare a minimum set of properties such
as in the following `ChangeEmailForm`.
[source,java,indent=0,subs="verbatim,quotes"]
----
public class ChangeEmailForm {
private String oldEmailAddress;
private String newEmailAddress;
public void setOldEmailAddress(String oldEmailAddress) {
this.oldEmailAddress = oldEmailAddress;
}
public String getOldEmailAddress() {
return this.oldEmailAddress;
}
public void setNewEmailAddress(String newEmailAddress) {
this.newEmailAddress = newEmailAddress;
}
public String getNewEmailAddress() {
return this.newEmailAddress;
}
}
----
If you cannot or do not want to use a _dedicated model object_ for each data
binding use case, you **must** limit the properties that are allowed for data binding.
Ideally, you can achieve this by registering _allowed field patterns_ via the
`setAllowedFields()` method on `WebDataBinder`.
For example, to register allowed field patterns in your application, you can implement an
`@InitBinder` method in a `@Controller` or `@ControllerAdvice` component as shown below:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Controller
public class ChangeEmailController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
}
// @RequestMapping methods, etc.
}
----
In addition to registering allowed patterns, it is also possible to register _disallowed
field patterns_ via the `setDisallowedFields()` method in `DataBinder` and its subclasses.
Please note, however, that an "allow list" is safer than a "deny list". Consequently,
`setAllowedFields()` should be favored over `setDisallowedFields()`.
Note that matching against allowed field patterns is case-sensitive; whereas, matching
against disallowed field patterns is case-insensitive. In addition, a field matching a
disallowed pattern will not be accepted even if it also happens to match a pattern in the
allowed list.
[WARNING]
====
It is extremely important to properly configure allowed and disallowed field patterns
when exposing your domain model directly for data binding purposes. Otherwise, it is a
big security risk.
Furthermore, it is strongly recommended that you do **not** use types from your domain
model such as JPA or Hibernate entities as the model object in data binding scenarios.
====

View File

@@ -0,0 +1,330 @@
[id={chapter}.web-uricomponents]
= UriComponents
[.small]#Spring MVC and Spring WebFlux#
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") // <1>
.queryParam("q", "{q}") // <2>
.encode() // <3>
.build(); // <4>
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
----
<1> Static factory method with a URI template.
<2> Add or replace URI components.
<3> Request to have the URI template and URI variables encoded.
<4> Build a `UriComponents`.
<5> Expand variables and obtain the `URI`.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") // <1>
.queryParam("q", "{q}") // <2>
.encode() // <3>
.build() // <4>
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
----
<1> Static factory method with a URI template.
<2> Add or replace URI components.
<3> Request to have the URI template and URI variables encoded.
<4> Build a `UriComponents`.
<5> Expand variables and obtain the `URI`.
The preceding example can be consolidated into one chain and shortened with `buildAndExpand`,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri()
----
You can shorten it further by going directly to a URI (which implies encoding),
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123")
----
You can shorten it further still with a full URI template, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123")
----
[id={chapter}.web-uribuilder]
= UriBuilder
[.small]#Spring MVC and Spring WebFlux#
<<{chapter}.web-uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
`UriBuilder`, in turn, with a `UriBuilderFactory`. Together, `UriBuilderFactory` and
`UriBuilder` provide a pluggable mechanism to build URIs from URI templates, based on
shared configuration, such as a base URL, encoding preferences, and other details.
You can configure `RestTemplate` and `WebClient` with a `UriBuilderFactory`
to customize the preparation of URIs. `DefaultUriBuilderFactory` is a default
implementation of `UriBuilderFactory` that uses `UriComponentsBuilder` internally and
exposes shared configuration options.
The following example shows how to configure a `RestTemplate`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory
----
The following example configures a `WebClient`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
val client = WebClient.builder().uriBuilderFactory(factory).build()
----
In addition, you can also use `DefaultUriBuilderFactory` directly. It is similar to using
`UriComponentsBuilder` but, instead of static factory methods, it is an actual instance
that holds configuration and preferences, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val baseUrl = "https://example.com"
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)
val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123")
----
[id={chapter}.web-uri-encoding]
= URI Encoding
[.small]#Spring MVC and Spring WebFlux#
`UriComponentsBuilder` exposes encoding options at two levels:
* {api-spring-framework}/web/util/UriComponentsBuilder.html#encode--[UriComponentsBuilder#encode()]:
Pre-encodes the URI template first and then strictly encodes URI variables when expanded.
* {api-spring-framework}/web/util/UriComponents.html#encode--[UriComponents#encode()]:
Encodes URI components _after_ URI variables are expanded.
Both options replace non-ASCII and illegal characters with escaped octets. However, the first option
also replaces characters with reserved meaning that appear in URI variables.
TIP: Consider ";", which is legal in a path but has reserved meaning. The first option replaces
";" with "%3B" in URI variables but not in the URI template. By contrast, the second option never
replaces ";", since it is a legal character in a path.
For most cases, the first option is likely to give the expected result, because it treats URI
variables as opaque data to be fully encoded, while the second option is useful if URI
variables do intentionally contain reserved characters. The second option is also useful
when not expanding URI variables at all since that will also encode anything that
incidentally looks like a URI variable.
The following example uses the first option:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri();
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri()
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
----
You can shorten the preceding example by going directly to the URI (which implies encoding),
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar")
----
You can shorten it further still with a full URI template, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar")
----
The `WebClient` and the `RestTemplate` expand and encode URI templates internally through
the `UriBuilderFactory` strategy. Both can be configured with a custom strategy,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}
// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
uriTemplateHandler = factory
}
// Customize the WebClient..
val client = WebClient.builder().uriBuilderFactory(factory).build()
----
The `DefaultUriBuilderFactory` implementation uses `UriComponentsBuilder` internally to
expand and encode URI templates. As a factory, it provides a single place to configure
the approach to encoding, based on one of the below encoding modes:
* `TEMPLATE_AND_VALUES`: Uses `UriComponentsBuilder#encode()`, corresponding to
the first option in the earlier list, to pre-encode the URI template and strictly encode URI variables when
expanded.
* `VALUES_ONLY`: Does not encode the URI template and, instead, applies strict encoding
to URI variables through `UriUtils#encodeUriVariables` prior to expanding them into the
template.
* `URI_COMPONENT`: Uses `UriComponents#encode()`, corresponding to the second option in the earlier list, to
encode URI component value _after_ URI variables are expanded.
* `NONE`: No encoding is applied.
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historic
reasons and for backwards compatibility. The `WebClient` relies on the default value
in `DefaultUriBuilderFactory`, which was changed from `EncodingMode.URI_COMPONENT` in
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.

View File

@@ -0,0 +1,367 @@
[[webflux-cors]]
= CORS
[.small]#<<web.adoc#mvc-cors, See equivalent in the Servlet stack>>#
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
describes how to do so.
[[webflux-cors-intro]]
== Introduction
[.small]#<<web.adoc#mvc-cors-intro, See equivalent in the Servlet stack>>#
For security reasons, browsers prohibit AJAX calls to resources outside the current origin.
For example, you could have your bank account in one tab and evil.com in another. Scripts
from evil.com should not be able to make AJAX requests to your bank API with your
credentials -- for example, withdrawing money from your account!
Cross-Origin Resource Sharing (CORS) is a https://www.w3.org/TR/cors/[W3C specification]
implemented by https://caniuse.com/#feat=cors[most browsers] that lets you specify
what kind of cross-domain requests are authorized, rather than using less secure and less
powerful workarounds based on IFRAME or JSONP.
[[webflux-cors-processing]]
== Processing
[.small]#<<web.adoc#mvc-cors-processing, See equivalent in the Servlet stack>>#
The CORS specification distinguishes between preflight, simple, and actual requests.
To learn how CORS works, you can read
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[this article], among
many others, or see the specification for more details.
Spring WebFlux `HandlerMapping` implementations provide built-in support for CORS. After successfully
mapping a request to a handler, a `HandlerMapping` checks the CORS configuration for the
given request and handler and takes further actions. Preflight requests are handled
directly, while simple and actual CORS requests are intercepted, validated, and have the
required CORS response headers set.
In order to enable cross-origin requests (that is, the `Origin` header is present and
differs from the host of the request), you need to have some explicitly declared CORS
configuration. If no matching CORS configuration is found, preflight requests are
rejected. No CORS headers are added to the responses of simple and actual CORS requests
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{api-spring-framework}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the WebFlux Java configuration to declare such mappings, which results in a single,
global map passed to all `HandlerMapping` implementations.
You can combine global CORS configuration at the `HandlerMapping` level with more
fine-grained, handler-level CORS configuration. For example, annotated controllers can use
class- or method-level `@CrossOrigin` annotations (other handlers can implement
`CorsConfigurationSource`).
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
{api-spring-framework}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
====
To learn more from the source or to make advanced customizations, see:
* `CorsConfiguration`
* `CorsProcessor` and `DefaultCorsProcessor`
* `AbstractHandlerMapping`
====
[[webflux-cors-controller]]
== `@CrossOrigin`
[.small]#<<web.adoc#mvc-cors-controller, See equivalent in the Servlet stack>>#
The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
annotation enables cross-origin requests on annotated controller methods, as the
following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
----
--
By default, `@CrossOrigin` allows:
* All origins.
* All headers.
* All HTTP methods to which the controller method is mapped.
`allowCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
should be used only where appropriate. When it is enabled either `allowOrigins` must be
set to one or more specific domain (but not the special value `"*"`) or alternatively
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
`@CrossOrigin` is supported at the class level, too, and inherited by all methods.
The following example specifies a certain domain and sets `maxAge` to an hour:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
----
--
You can use `@CrossOrigin` at both the class and the method level,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@CrossOrigin(maxAge = 3600) // <1>
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com") // <2>
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
----
<1> Using `@CrossOrigin` at the class level.
<2> Using `@CrossOrigin` at the method level.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@CrossOrigin(maxAge = 3600) // <1>
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin("https://domain2.com") // <2>
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
----
<1> Using `@CrossOrigin` at the class level.
<2> Using `@CrossOrigin` at the method level.
--
[[webflux-cors-global]]
== Global Configuration
[.small]#<<web.adoc#mvc-cors-global, See equivalent in the Servlet stack>>#
In addition to fine-grained, controller method-level configuration, you probably want to
define some global CORS configuration, too. You can set URL-based `CorsConfiguration`
mappings individually on any `HandlerMapping`. Most applications, however, use the
WebFlux Java configuration to do that.
By default global configuration enables the following:
* All origins.
* All headers.
* `GET`, `HEAD`, and `POST` methods.
`allowedCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
should be used only where appropriate. When it is enabled either `allowOrigins` must be
set to one or more specific domain (but not the special value `"*"`) or alternatively
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
To enable CORS in the WebFlux Java configuration, you can use the `CorsRegistry` callback,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600)
// Add more mappings...
}
}
----
[[webflux-cors-webfilter]]
== CORS `WebFilter`
[.small]#<<web.adoc#mvc-cors-filter, See equivalent in the Servlet stack>>#
You can apply CORS support through the built-in
{api-spring-framework}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
good fit with <<webflux-fn, functional endpoints>>.
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
CORS.
To configure the filter, you can declare a `CorsWebFilter` bean and pass a
`CorsConfigurationSource` to its constructor, as the following example shows:
[source,java,indent=0,subs="verbatim",role="primary"]
.Java
----
@Bean
CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
----
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
.Kotlin
----
@Bean
fun corsFilter(): CorsWebFilter {
val config = CorsConfiguration()
// Possibly...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource().apply {
registerCorsConfiguration("/**", config)
}
return CorsWebFilter(source)
}
----

View File

@@ -0,0 +1,905 @@
[[webflux-fn]]
= Functional Endpoints
[.small]#<<web.adoc#webmvc-fn, See equivalent in the Servlet stack>>#
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
are used to route and handle requests and contracts are designed for immutability.
It is an alternative to the annotation-based programming model but otherwise runs on
the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation.
[[webflux-fn-overview]]
== Overview
[.small]#<<web.adoc#webmvc-fn-overview, See equivalent in the Servlet stack>>#
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
`ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
Both the request and the response object have immutable contracts that offer JDK 8-friendly
access to the HTTP request and response.
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
annotation-based programming model.
Incoming requests are routed to a handler function with a `RouterFunction`: a function that
takes `ServerRequest` and returns a delayed `HandlerFunction` (i.e. `Mono<HandlerFunction>`).
When the router function matches, a handler function is returned; otherwise an empty Mono.
`RouterFunction` is the equivalent of a `@RequestMapping` annotation, but with the major
difference that router functions provide not just data, but also behavior.
`RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route() <1>
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public Mono<ServerResponse> listPeople(ServerRequest request) {
// ...
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
// ...
}
public Mono<ServerResponse> getPerson(ServerRequest request) {
// ...
}
}
----
<1> Create router using `route()`.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = coRouter { // <1>
accept(APPLICATION_JSON).nest {
GET("/person/{id}", handler::getPerson)
GET("/person", handler::listPeople)
}
POST("/person", handler::createPerson)
}
class PersonHandler(private val repository: PersonRepository) {
// ...
suspend fun listPeople(request: ServerRequest): ServerResponse {
// ...
}
suspend fun createPerson(request: ServerRequest): ServerResponse {
// ...
}
suspend fun getPerson(request: ServerRequest): ServerResponse {
// ...
}
}
----
<1> Create router using Coroutines router DSL; a Reactive alternative is also available via `router { }`.
One way to run a `RouterFunction` is to turn it into an `HttpHandler` and install it
through one of the built-in <<web-reactive.adoc#webflux-httphandler, server adapters>>:
* `RouterFunctions.toHttpHandler(RouterFunction)`
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
Most applications can run through the WebFlux Java configuration, see <<webflux-fn-running>>.
[[webflux-fn-handler-functions]]
== HandlerFunction
[.small]#<<web.adoc#webmvc-fn-handler-functions, See equivalent in the Servlet stack>>#
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
access to the HTTP request and response.
Both request and response provide https://www.reactive-streams.org[Reactive Streams] back pressure
against the body streams.
The request body is represented with a Reactor `Flux` or `Mono`.
The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`.
For more on that, see <<web-reactive.adoc#webflux-reactive-libraries, Reactive Libraries>>.
[[webflux-fn-request]]
=== ServerRequest
`ServerRequest` provides access to the HTTP method, URI, headers, and query parameters,
while access to the body is provided through the `body` methods.
The following example extracts the request body to a `Mono<String>`:
[source,java,role="primary"]
.Java
----
Mono<String> string = request.bodyToMono(String.class);
----
[source,kotlin,role="secondary"]
.Kotlin
----
val string = request.awaitBody<String>()
----
The following example extracts the body to a `Flux<Person>` (or a `Flow<Person>` in Kotlin),
where `Person` objects are decoded from some serialized form, such as JSON or XML:
[source,java,role="primary"]
.Java
----
Flux<Person> people = request.bodyToFlux(Person.class);
----
[source,kotlin,role="secondary"]
.Kotlin
----
val people = request.bodyToFlow<Person>()
----
The preceding examples are shortcuts that use the more general `ServerRequest.body(BodyExtractor)`,
which accepts the `BodyExtractor` functional strategy interface. The utility class
`BodyExtractors` provides access to a number of instances. For example, the preceding examples can
also be written as follows:
[source,java,role="primary"]
.Java
----
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
----
[source,kotlin,role="secondary"]
.Kotlin
----
val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()
----
The following example shows how to access form data:
[source,java,role="primary"]
.Java
----
Mono<MultiValueMap<String, String>> map = request.formData();
----
[source,kotlin,role="secondary"]
.Kotlin
----
val map = request.awaitFormData()
----
The following example shows how to access multipart data as a map:
[source,java,role="primary"]
.Java
----
Mono<MultiValueMap<String, Part>> map = request.multipartData();
----
[source,kotlin,role="secondary"]
.Kotlin
----
val map = request.awaitMultipartData()
----
The following example shows how to access multipart data, one at a time, in streaming fashion:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
allPartsEvents.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
}
}));
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val parts = request.bodyToFlux<PartEvent>()
allPartsEvents.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 Mono.error(RuntimeException("Unexpected event: " + event));
}
} else {
return partEvents; // either complete or error signal
}
}
}
}
----
Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
[[webflux-fn-response]]
=== ServerResponse
`ServerResponse` provides access to the HTTP response and, since it is immutable, you can use
a `build` method to create it. You can use the builder to set the response status, to add response
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
content:
[source,java,role="primary"]
.Java
----
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
----
[source,kotlin,role="secondary"]
.Kotlin
----
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
----
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
[source,java,role="primary"]
.Java
----
URI location = ...
ServerResponse.created(location).build();
----
[source,kotlin,role="secondary"]
.Kotlin
----
val location: URI = ...
ServerResponse.created(location).build()
----
Depending on the codec used, it is possible to pass hint parameters to customize how the
body is serialized or deserialized. For example, to specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON view]:
[source,java,role="primary"]
.Java
----
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
----
[source,kotlin,role="secondary"]
.Kotlin
----
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
----
[[webflux-fn-handler-classes]]
=== Handler Classes
We can write a handler function as a lambda, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
----
--
That is convenient, but in an application we need multiple functions, and multiple inline
lambda's can get messy.
Therefore, it is useful to group related handler functions together into a handler class, which
has a similar role as `@Controller` in an annotation-based application.
For example, the following class exposes a reactive `Person` repository:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> listPeople(ServerRequest request) { // <1>
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) { // <2>
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) { // <3>
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
----
<1> `listPeople` 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.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class PersonHandler(private val repository: PersonRepository) {
suspend fun listPeople(request: ServerRequest): ServerResponse { // <1>
val people: Flow<Person> = repository.allPeople()
return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
}
suspend fun createPerson(request: ServerRequest): ServerResponse { // <2>
val person = request.awaitBody<Person>()
repository.savePerson(person)
return ok().buildAndAwait()
}
suspend fun getPerson(request: ServerRequest): ServerResponse { // <3>
val personId = request.pathVariable("id").toInt()
return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
?: ServerResponse.notFound().buildAndAwait()
}
}
----
<1> `listPeople` 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.
--
[[webflux-fn-handler-validation]]
=== Validation
A functional endpoint can use Spring's <<core.adoc#validation, validation facilities>> to
apply validation to the request body. For example, given a custom Spring
<<core.adoc#validation, Validator>> implementation for a `Person`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public class PersonHandler {
private final Validator validator = new PersonValidator(); // <1>
// ...
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // <2>
return ok().build(repository.savePerson(person));
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); // <3>
}
}
}
----
<1> Create `Validator` instance.
<2> Apply validation.
<3> Raise exception for a 400 response.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class PersonHandler(private val repository: PersonRepository) {
private val validator = PersonValidator() // <1>
// ...
suspend fun createPerson(request: ServerRequest): ServerResponse {
val person = request.awaitBody<Person>()
validate(person) // <2>
repository.savePerson(person)
return ok().buildAndAwait()
}
private fun validate(person: Person) {
val errors: Errors = BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw ServerWebInputException(errors.toString()) // <3>
}
}
}
----
<1> Create `Validator` instance.
<2> Apply validation.
<3> Raise exception for a 400 response.
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
a global `Validator` instance based on `LocalValidatorFactoryBean`.
See <<core.adoc#validation-beanvalidation, Spring Validation>>.
[[webflux-fn-router-functions]]
== `RouterFunction`
[.small]#<<web.adoc#webmvc-fn-router-functions, See equivalent in the Servlet stack>>#
Router functions are used to route the requests to the corresponding `HandlerFunction`.
Typically, you do not write router functions yourself, but rather use a method on the
`RouterFunctions` utility class to create one.
`RouterFunctions.route()` (no parameters) provides you with a fluent builder for creating a router
function, whereas `RouterFunctions.route(RequestPredicate, HandlerFunction)` offers a direct way
to create a router.
Generally, it is recommended to use the `route()` builder, as it provides
convenient short-cuts for typical mapping scenarios without requiring hard-to-discover
static imports.
For instance, the router function builder offers the method `GET(String, HandlerFunction)` to create a mapping for GET requests; and `POST(String, HandlerFunction)` for POSTs.
Besides HTTP method-based mapping, the route builder offers a way to introduce additional
predicates when mapping to requests.
For each HTTP method there is an overloaded variant that takes a `RequestPredicate` as a
parameter, though which additional constraints can be expressed.
[[webflux-fn-predicates]]
=== Predicates
You can write your own `RequestPredicate`, but the `RequestPredicates` utility class
offers commonly used implementations, based on the request path, HTTP method, content-type,
and so on.
The following example uses a request predicate to create a constraint based on the `Accept`
header:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val route = coRouter {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
----
You can compose multiple request predicates together by using:
* `RequestPredicate.and(RequestPredicate)` -- both must match.
* `RequestPredicate.or(RequestPredicate)` -- either can match.
Many of the predicates from `RequestPredicates` are composed.
For example, `RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)`
and `RequestPredicates.path(String)`.
The example shown above also uses two request predicates, as the builder uses
`RequestPredicates.GET` internally, and composes that with the `accept` predicate.
[[webflux-fn-routes]]
=== Routes
Router functions are evaluated in order: if the first route does not match, the
second is evaluated, and so on.
Therefore, it makes sense to declare more specific routes before general ones.
This is also important when registering router functions as Spring beans, as will
be described later.
Note that this behavior is different from the annotation-based programming model, where the
"most specific" controller method is picked automatically.
When using the router function builder, all defined routes are composed into one
`RouterFunction` that is returned from `build()`.
There are also other ways to compose multiple router functions together:
* `add(RouterFunction)` on the `RouterFunctions.route()` builder
* `RouterFunction.and(RouterFunction)`
* `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -- shortcut for
`RouterFunction.and()` with nested `RouterFunctions.route()`.
The following example shows the composition of four routes:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
.POST("/person", handler::createPerson) // <3>
.add(otherRoute) // <4>
.build();
----
<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.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.http.MediaType.APPLICATION_JSON
val repository: PersonRepository = ...
val handler = PersonHandler(repository);
val otherRoute: RouterFunction<ServerResponse> = coRouter { }
val route = coRouter {
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
POST("/person", handler::createPerson) // <3>
}.and(otherRoute) // <4>
----
<1> 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.
=== Nested Routes
It is common for a group of router functions to have a shared predicate, for instance a
shared path. In the example above, the shared predicate would be a path predicate that
matches `/person`, used by three of the routes. When using annotations, you would remove
this duplication by using a type-level `@RequestMapping` annotation that maps to
`/person`. In WebFlux.fn, path predicates can be shared through the `path` method on the
router function builder. For instance, the last few lines of the example above can be
improved in the following way by using nested routes:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder // <1>
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST(handler::createPerson))
.build();
----
<1> Note that second parameter of `path` is a consumer that takes the router builder.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val route = coRouter { // <1>
"/person".nest {
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST(handler::createPerson)
}
}
----
<1> Create router using Coroutines router DSL; a Reactive alternative is also available via `router { }`.
Though path-based nesting is the most common, you can nest on any kind of predicate by using
the `nest` method on the builder.
The above still contains some duplication in the form of the shared `Accept`-header predicate.
We can further improve by using the `nest` method together with `accept`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val route = coRouter {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
POST(handler::createPerson)
}
}
}
----
[[webflux-fn-running]]
== Running a Server
[.small]#<<web.adoc#webmvc-fn-running, See equivalent in the Servlet stack>>#
How do you run a router function in an HTTP server? A simple option is to convert a router
function to an `HttpHandler` by using one of the following:
* `RouterFunctions.toHttpHandler(RouterFunction)`
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
You can then use the returned `HttpHandler` with a number of server adapters by following
<<web-reactive.adoc#webflux-httphandler, HttpHandler>> for server-specific instructions.
A more typical option, also used by Spring Boot, is to run with a
<<web-reactive.adoc#webflux-dispatcher-handler, `DispatcherHandler`>>-based setup through the
<<web-reactive.adoc#webflux-config>>, which uses Spring configuration to declare the
components required to process requests. The WebFlux Java configuration declares the following
infrastructure components to support functional endpoints:
* `RouterFunctionMapping`: Detects one or more `RouterFunction<?>` beans in the Spring
configuration, <<core.adoc#beans-factory-ordered, orders them>>, combines them through
`RouterFunction.andOther`, and routes requests to the resulting composed `RouterFunction`.
* `HandlerFunctionAdapter`: Simple adapter that lets `DispatcherHandler` invoke
a `HandlerFunction` that was mapped to a request.
* `ServerResponseResultHandler`: Handles the result from the invocation of a
`HandlerFunction` by invoking the `writeTo` method of the `ServerResponse`.
The preceding components let functional endpoints fit within the `DispatcherHandler` request
processing lifecycle and also (potentially) run side by side with annotated controllers, if
any are declared. It is also how functional endpoints are enabled by the Spring Boot WebFlux
starter.
The following example shows a WebFlux Java configuration (see
<<web-reactive.adoc#webflux-dispatcher-handler, DispatcherHandler>> for how to run it):
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// configure message conversion...
}
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure view resolution for HTML rendering...
}
}
----
[[webflux-fn-handler-filter-function]]
== Filtering Handler Functions
[.small]#<<web.adoc#webmvc-fn-handler-filter-function, See equivalent in the Servlet stack>>#
You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing
function builder.
With annotations, you can achieve similar functionality by using `@ControllerAdvice`, a `ServletFilter`, or both.
The filter will apply to all routes that are built by the builder.
This means that filters defined in nested routes do not apply to "top-level" routes.
For instance, consider the following example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople)
.before(request -> ServerRequest.from(request) // <1>
.header("X-RequestHeader", "Value")
.build()))
.POST(handler::createPerson))
.after((request, response) -> logResponse(response)) // <2>
.build();
----
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val route = router {
"/person".nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
before { // <1>
ServerRequest.from(it)
.header("X-RequestHeader", "Value").build()
}
POST(handler::createPerson)
after { _, response -> // <2>
logResponse(response)
}
}
}
----
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
The `filter` method on the router builder takes a `HandlerFilterFunction`: a
function that takes a `ServerRequest` and `HandlerFunction` and returns a `ServerResponse`.
The handler function parameter represents the next element in the chain.
This is typically the handler that is routed to, but it can also be another
filter if multiple are applied.
Now we can add a simple security filter to our route, assuming that we have a `SecurityManager` that
can determine whether a particular path is allowed.
The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
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();
}
}
}
}
----
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
We only let the handler function be run when access is allowed.
Besides using the `filter` method on the router function builder, it is possible to apply a
filter to an existing router function via `RouterFunction.filter(HandlerFilterFunction)`.
NOTE: CORS support for functional endpoints is provided through a dedicated
<<webflux-cors.adoc#webflux-cors-webfilter, `CorsWebFilter`>>.

View File

@@ -0,0 +1,408 @@
[[webflux-view]]
= View Technologies
[.small]#<<web.adoc#mvc-view, See equivalent in the Servlet stack>>#
The use of view technologies in Spring WebFlux is pluggable. Whether you decide to
use Thymeleaf, FreeMarker, or some other view technology is primarily a matter of a
configuration change. This chapter covers the view technologies integrated with Spring
WebFlux. We assume you are already familiar with <<webflux-viewresolution>>.
[[webflux-view-thymeleaf]]
== Thymeleaf
[.small]#<<web.adoc#mvc-view-thymeleaf, See equivalent in the Servlet stack>>#
Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML
templates that can be previewed in a browser by double-clicking, which is very
helpful for independent work on UI templates (for example, by a designer) without the need for a
running server. Thymeleaf offers an extensive set of features, and it is actively developed
and maintained. For a more complete introduction, see the
https://www.thymeleaf.org/[Thymeleaf] project home page.
The Thymeleaf integration with Spring WebFlux is managed by the Thymeleaf project. The
configuration involves a few bean declarations, such as
`SpringResourceTemplateResolver`, `SpringWebFluxTemplateEngine`, and
`ThymeleafReactiveViewResolver`. For more details, see
https://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] and the WebFlux integration
https://web.archive.org/web/20210623051330/http%3A//forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html[announcement].
[[webflux-view-freemarker]]
== FreeMarker
[.small]#<<web.adoc#mvc-view-freemarker, See equivalent in the Servlet stack>>#
https://freemarker.apache.org/[Apache FreeMarker] is a template engine for generating any
kind of text output from HTML to email and others. The Spring Framework has built-in
integration for using Spring WebFlux with FreeMarker templates.
[[webflux-view-freemarker-contextconfig]]
=== View Configuration
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig, See equivalent in the Servlet stack>>#
The following example shows how to configure FreeMarker as a view technology:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates/freemarker");
return configurer;
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
}
// Configure FreeMarker...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("classpath:/templates/freemarker")
}
}
----
Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer`,
shown in the preceding example. Given the preceding configuration, if your controller
returns the view name, `welcome`, the resolver looks for the
`classpath:/templates/freemarker/welcome.ftl` template.
[[webflux-views-freemarker]]
=== FreeMarker Configuration
[.small]#<<web.adoc#mvc-views-freemarker, See equivalent in the Servlet stack>>#
You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker
`Configuration` object (which is managed by Spring) by setting the appropriate bean
properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property requires
a `java.util.Properties` object, and the `freemarkerVariables` property requires a
`java.util.Map`. The following example shows how to use a `FreeMarkerConfigurer`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
// ...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
Map<String, Object> variables = new HashMap<>();
variables.put("xml_escape", new XmlEscape());
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates");
configurer.setFreemarkerVariables(variables);
return configurer;
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
// ...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("classpath:/templates")
setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
}
}
----
See the FreeMarker documentation for details of settings and variables as they apply to
the `Configuration` object.
[[webflux-view-freemarker-forms]]
=== Form Handling
[.small]#<<web.adoc#mvc-view-freemarker-forms, See equivalent in the Servlet stack>>#
Spring provides a tag library for use in JSPs that contains, among others, a
`<spring:bind/>` element. This element primarily lets forms display values from
form-backing objects and show the results of failed validations from a `Validator` in the
web or business tier. Spring also has support for the same functionality in FreeMarker,
with additional convenience macros for generating form input elements themselves.
[[webflux-view-bind-macros]]
==== The Bind Macros
[.small]#<<web.adoc#mvc-view-bind-macros, See equivalent in the Servlet stack>>#
A standard set of macros are maintained within the `spring-webflux.jar` file for
FreeMarker, so they are always available to a suitably configured application.
Some of the macros defined in the Spring templating libraries are considered internal
(private), but no such scoping exists in the macro definitions, making all macros visible
to calling code and user templates. The following sections concentrate only on the macros
you need to directly call from within your templates. If you wish to view the macro code
directly, the file is called `spring.ftl` and is in the
`org.springframework.web.reactive.result.view.freemarker` package.
For additional details on binding support, see <<web.adoc#mvc-view-simple-binding, Simple
Binding>> for Spring MVC.
[[webflux-views-form-macros]]
==== Form Macros
For details on Spring's form macro support for FreeMarker templates, consult the following
sections of the Spring MVC documentation.
* <<web.adoc#mvc-views-form-macros, Input Macros>>
* <<web.adoc#mvc-views-form-macros-input, Input Fields>>
* <<web.adoc#mvc-views-form-macros-select, Selection Fields>>
* <<web.adoc#mvc-views-form-macros-html-escaping, HTML Escaping>>
[[webflux-view-script]]
== Script Views
[.small]#<<web.adoc#mvc-view-script, See equivalent in the Servlet stack>>#
The Spring Framework has a built-in integration for using Spring WebFlux with any
templating library that can run on top of the
https://www.jcp.org/en/jsr/detail?id=223[JSR-223] Java scripting engine.
The following table shows the templating libraries that we have tested on different script engines:
[%header]
|===
|Scripting Library |Scripting Engine
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |https://kotlinlang.org/[Kotlin]
|===
TIP: The basic rule for integrating any other script engine is that it must implement the
`ScriptEngine` and `Invocable` interfaces.
[[webflux-view-script-dependencies]]
=== Requirements
[.small]#<<web.adoc#mvc-view-script-dependencies, See equivalent in the Servlet stack>>#
You need to have the script engine on your classpath, the details of which vary by script engine:
* The https://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
Java 8+. Using the latest update release available is highly recommended.
* https://www.jruby.org[JRuby] should be added as a dependency for Ruby support.
* https://www.jython.org[Jython] should be added as a dependency for Python support.
* `org.jetbrains.kotlin:kotlin-script-util` dependency and a `META-INF/services/javax.script.ScriptEngineFactory`
file containing a `org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory`
line should be added for Kotlin script support. See
https://github.com/sdeleuze/kotlin-script-templating[this example] for more detail.
You need to have the script templating library. One way to do that for JavaScript is
through https://www.webjars.org/[WebJars].
[[webflux-view-script-integrate]]
=== Script Templates
[.small]#<<web.adoc#mvc-view-script-integrate, See equivalent in the Servlet stack>>#
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
the script files to load, what function to call to render templates, and so on.
The following example uses Mustache templates and the Nashorn JavaScript engine:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("mustache.js")
renderObject = "Mustache"
renderFunction = "render"
}
}
----
The `render` function is called with the following parameters:
* `String template`: The template content
* `Map model`: The view model
* `RenderingContext renderingContext`: The
{api-spring-framework}/web/servlet/view/script/RenderingContext.html[`RenderingContext`]
that gives access to the application context, the locale, the template loader, and the
URL (since 5.0)
`Mustache.render()` is natively compatible with this signature, so you can call it directly.
If your templating technology requires some customization, you can provide a script that
implements a custom render function. For example, https://handlebarsjs.com[Handlerbars]
needs to compile templates before using them and requires a
https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
browser facilities not available in the server-side script engine.
The following example shows how to set a custom render function:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("polyfill.js", "handlebars.js", "render.js")
renderFunction = "render"
isSharedEngine = false
}
}
----
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
script engines with templating libraries not designed for concurrency, such as Handlebars or
React running on Nashorn. In that case, Java SE 8 update 60 is required, due to
https://bugs.openjdk.java.net/browse/JDK-8076099[this bug], but it is generally
recommended to use a recent Java SE patch release in any case.
`polyfill.js` defines only the `window` object needed by Handlebars to run properly,
as the following snippet shows:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
var window = {};
----
This basic `render.js` implementation compiles the template before using it. A production
ready implementation should also store and reused cached templates or pre-compiled templates.
This can be done on the script side, as well as any customization you need (managing
template engine configuration for example).
The following example shows how compile a template:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
----
Check out the Spring Framework unit tests,
{spring-framework-main-code}/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java], and
{spring-framework-main-code}/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script[resources],
for more configuration examples.
[[webflux-view-httpmessagewriter]]
== JSON and XML
[.small]#<<web.adoc#mvc-view-jackson, See equivalent in the Servlet stack>>#
For <<webflux-multiple-representations>> purposes, it is useful to be able to alternate
between rendering a model with an HTML template or as other formats (such as JSON or XML),
depending on the content type requested by the client. To support doing so, Spring WebFlux
provides the `HttpMessageWriterView`, which you can use to plug in any of the available
<<webflux-codecs>> from `spring-web`, such as `Jackson2JsonEncoder`, `Jackson2SmileEncoder`,
or `Jaxb2XmlEncoder`.
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver`
but is instead <<webflux-config-view-resolvers, configured>> as a default view. You can
configure one or more such default views, wrapping different `HttpMessageWriter` instances
or `Encoder` instances. The one that matches the requested content type is used at runtime.
In most cases, a model contains multiple attributes. To determine which one to serialize,
you can configure `HttpMessageWriterView` with the name of the model attribute to use for
rendering. If the model contains only one attribute, that one is used.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,461 @@
[[webflux-websocket]]
= WebSockets
[.small]#<<web.adoc#websocket, See equivalent in the Servlet stack>>#
This part of the reference documentation covers support for reactive-stack WebSocket
messaging.
include::websocket-intro.adoc[leveloffset=+1]
[[webflux-websocket-server]]
== WebSocket API
[.small]#<<web.adoc#websocket-server, See equivalent in the Servlet stack>>#
The Spring Framework provides a WebSocket API that you can use to write client- and
server-side applications that handle WebSocket messages.
[[webflux-websocket-server-handler]]
=== Server
[.small]#<<web.adoc#websocket-server-handler, See equivalent in the Servlet stack>>#
To create a WebSocket server, you can first create a `WebSocketHandler`.
The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.reactive.socket.WebSocketHandler
import org.springframework.web.reactive.socket.WebSocketSession
class MyWebSocketHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
// ...
}
}
----
Then you can map it to a URL:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
class WebConfig {
@Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/path", new MyWebSocketHandler());
int order = -1; // before annotated controllers
return new SimpleUrlHandlerMapping(map, order);
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
class WebConfig {
@Bean
fun handlerMapping(): HandlerMapping {
val map = mapOf("/path" to MyWebSocketHandler())
val order = -1 // before annotated controllers
return SimpleUrlHandlerMapping(map, order)
}
}
----
If using the <<web-reactive.adoc#webflux-config, WebFlux Config>> there is nothing
further to do, or otherwise if not using the WebFlux config you'll need to declare a
`WebSocketHandlerAdapter` as shown below:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
class WebConfig {
// ...
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
class WebConfig {
// ...
@Bean
fun handlerAdapter() = WebSocketHandlerAdapter()
}
----
[[webflux-websockethandler]]
=== `WebSocketHandler`
The `handle` method of `WebSocketHandler` takes `WebSocketSession` and returns `Mono<Void>`
to indicate when application handling of the session is complete. The session is handled
through two streams, one for inbound and one for outbound messages. The following table
describes the two methods that handle the streams:
[options="header"]
|===
| `WebSocketSession` method | Description
| `Flux<WebSocketMessage> receive()`
| Provides access to the inbound message stream and completes when the connection is closed.
| `Mono<Void> send(Publisher<WebSocketMessage>)`
| Takes a source for outgoing messages, writes the messages, and returns a `Mono<Void>` that
completes when the source completes and writing is done.
|===
A `WebSocketHandler` must compose the inbound and outbound streams into a unified flow and
return a `Mono<Void>` that reflects the completion of that flow. Depending on application
requirements, the unified flow completes when:
* Either the inbound or the outbound message stream completes.
* The inbound stream completes (that is, the connection closed), while the outbound stream is infinite.
* At a chosen point, through the `close` method of `WebSocketSession`.
When inbound and outbound message streams are composed together, there is no need to
check if the connection is open, since Reactive Streams signals end activity.
The inbound stream receives a completion or error signal, and the outbound stream
receives a cancellation signal.
The most basic implementation of a handler is one that handles the inbound stream. The
following example shows such an implementation:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
return session.receive() // <1>
.doOnNext(message -> {
// ... // <2>
})
.concatMap(message -> {
// ... // <3>
})
.then(); // <4>
}
}
----
<1> Access the stream of inbound messages.
<2> Do something with each message.
<3> Perform nested asynchronous operations that use the message content.
<4> Return a `Mono<Void>` that completes when receiving completes.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
return session.receive() // <1>
.doOnNext {
// ... // <2>
}
.concatMap {
// ... // <3>
}
.then() // <4>
}
}
----
<1> Access the stream of inbound messages.
<2> Do something with each message.
<3> Perform nested asynchronous operations that use the message content.
<4> Return a `Mono<Void>` that completes when receiving completes.
TIP: For nested, asynchronous operations, you may need to call `message.retain()` on underlying
servers that use pooled data buffers (for example, Netty). Otherwise, the data buffer may be
released before you have had a chance to read the data. For more background, see
<<core.adoc#databuffers, Data Buffers and Codecs>>.
The following implementation combines the inbound and outbound streams:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Flux<WebSocketMessage> output = session.receive() // <1>
.doOnNext(message -> {
// ...
})
.concatMap(message -> {
// ...
})
.map(value -> session.textMessage("Echo " + value)); // <2>
return session.send(output); // <3>
}
}
----
<1> Handle the inbound message stream.
<2> Create the outbound message, producing a combined flow.
<3> Return a `Mono<Void>` that does not complete while we continue to receive.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
val output = session.receive() // <1>
.doOnNext {
// ...
}
.concatMap {
// ...
}
.map { session.textMessage("Echo $it") } // <2>
return session.send(output) // <3>
}
}
----
<1> Handle the inbound message stream.
<2> Create the outbound message, producing a combined flow.
<3> Return a `Mono<Void>` that does not complete while we continue to receive.
Inbound and outbound streams can be independent and be joined only for completion,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> input = session.receive() <1>
.doOnNext(message -> {
// ...
})
.concatMap(message -> {
// ...
})
.then();
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage)); <2>
return Mono.zip(input, output).then(); <3>
}
}
----
<1> Handle inbound message stream.
<2> Send outgoing messages.
<3> Join the streams and return a `Mono<Void>` that completes when either stream ends.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
val input = session.receive() // <1>
.doOnNext {
// ...
}
.concatMap {
// ...
}
.then()
val source: Flux<String> = ...
val output = session.send(source.map(session::textMessage)) // <2>
return Mono.zip(input, output).then() // <3>
}
}
----
<1> Handle inbound message stream.
<2> Send outgoing messages.
<3> Join the streams and return a `Mono<Void>` that completes when either stream ends.
[[webflux-websocket-databuffer]]
=== `DataBuffer`
`DataBuffer` is the representation for a byte buffer in WebFlux. The Spring Core part of
the reference has more on that in the section on
<<core#databuffers, Data Buffers and Codecs>>. The key point to understand is that on some
servers like Netty, byte buffers are pooled and reference counted, and must be released
when consumed to avoid memory leaks.
When running on Netty, applications must use `DataBufferUtils.retain(dataBuffer)` if they
wish to hold on input data buffers in order to ensure they are not released, and
subsequently use `DataBufferUtils.release(dataBuffer)` when the buffers are consumed.
[[webflux-websocket-server-handshake]]
=== Handshake
[.small]#<<web.adoc#websocket-server-handshake, See equivalent in the Servlet stack>>#
`WebSocketHandlerAdapter` delegates to a `WebSocketService`. By default, that is an instance
of `HandshakeWebSocketService`, which performs basic checks on the WebSocket request and
then uses `RequestUpgradeStrategy` for the server in use. Currently, there is built-in
support for Reactor Netty, Tomcat, Jetty, and Undertow.
`HandshakeWebSocketService` exposes a `sessionAttributePredicate` property that allows
setting a `Predicate<String>` to extract attributes from the `WebSession` and insert them
into the attributes of the `WebSocketSession`.
[[webflux-websocket-server-config]]
=== Server Configuration
[.small]#<<web.adoc#websocket-server-runtime-configuration, See equivalent in the Servlet stack>>#
The `RequestUpgradeStrategy` for each server exposes configuration specific to the
underlying WebSocket server engine. When using the WebFlux Java config you can customize
such properties as shown in the corresponding section of the
<<web-reactive.adoc#webflux-config-websocket-service, WebFlux Config>>, or otherwise if
not using the WebFlux config, use the below:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
class WebConfig {
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter(webSocketService());
}
@Bean
public WebSocketService webSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
class WebConfig {
@Bean
fun handlerAdapter() =
WebSocketHandlerAdapter(webSocketService())
@Bean
fun webSocketService(): WebSocketService {
val strategy = TomcatRequestUpgradeStrategy().apply {
setMaxSessionIdleTimeout(0L)
}
return HandshakeWebSocketService(strategy)
}
}
----
Check the upgrade strategy for your server to see what options are available. Currently,
only Tomcat and Jetty expose such options.
[[webflux-websocket-server-cors]]
=== CORS
[.small]#<<web.adoc#websocket-server-allowed-origins, See equivalent in the Servlet stack>>#
The easiest way to configure CORS and restrict access to a WebSocket endpoint is to
have your `WebSocketHandler` implement `CorsConfigurationSource` and return a
`CorsConfiguration` with allowed origins, headers, and other details. If you cannot do
that, you can also set the `corsConfigurations` property on the `SimpleUrlHandler` to
specify CORS settings by URL pattern. If both are specified, they are combined by using the
`combine` method on `CorsConfiguration`.
[[webflux-websocket-client]]
=== Client
Spring WebFlux provides a `WebSocketClient` abstraction with implementations for
Reactor Netty, Tomcat, Jetty, Undertow, and standard Java (that is, JSR-356).
NOTE: The Tomcat client is effectively an extension of the standard Java one with some extra
functionality in the `WebSocketSession` handling to take advantage of the Tomcat-specific
API to suspend receiving messages for back pressure.
To start a WebSocket session, you can create an instance of the client and use its `execute`
methods:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
WebSocketClient client = new ReactorNettyWebSocketClient();
URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
session.receive()
.doOnNext(System.out::println)
.then());
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val client = ReactorNettyWebSocketClient()
val url = URI("ws://localhost:8080/path")
client.execute(url) { session ->
session.receive()
.doOnNext(::println)
.then()
}
----
Some clients, such as Jetty, implement `Lifecycle` and need to be stopped and started
before you can use them. All clients have constructor options related to configuration
of the underlying WebSocket client.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
[[webmvc-client]]
= REST Clients
This section describes options for client-side access to REST endpoints.
[[webmvc-resttemplate]]
== `RestTemplate`
`RestTemplate` is a synchronous client to perform HTTP requests. It is the original
Spring REST client and exposes a simple, template-method API over underlying HTTP client
libraries.
NOTE: As of 5.0 the `RestTemplate` is in maintenance mode, with only requests for minor
changes and bugs to be accepted. Please, consider using the
<<web-reactive.adoc#webflux-client, WebClient>> which offers a more modern API and
supports sync, async, and streaming scenarios.
See <<integration.adoc#rest-client-access, REST Endpoints>> for details.
[[webmvc-webclient]]
== `WebClient`
`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was
introduced in 5.0 and offers a modern alternative to the `RestTemplate`, with efficient
support for both synchronous and asynchronous, as well as streaming scenarios.
In contrast to `RestTemplate`, `WebClient` supports the following:
* Non-blocking I/O.
* Reactive Streams back pressure.
* High concurrency with fewer hardware resources.
* Functional-style, fluent API that takes advantage of Java 8 lambdas.
* Synchronous and asynchronous interactions.
* Streaming up to or streaming down from a server.
See <<web-reactive.adoc#webflux-client, WebClient>> for more details.
[[webmvc-http-interface]]
== HTTP Interface
The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
exchange methods. You can then generate a proxy that implements this interface and
performs the exchanges. This helps to simplify HTTP remote access and provides additional
flexibility for to choose an API style such as synchronous or reactive.
See <<integration.adoc#rest-http-interface, REST Endpoints>> for details.

View File

@@ -0,0 +1,379 @@
[[mvc-cors]]
= CORS
[.small]#<<web-reactive.adoc#webflux-cors, See equivalent in the Reactive stack>>#
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
describes how to do so.
[[mvc-cors-intro]]
== Introduction
[.small]#<<web-reactive.adoc#webflux-cors-intro, See equivalent in the Reactive stack>>#
For security reasons, browsers prohibit AJAX calls to resources outside the current origin.
For example, you could have your bank account in one tab and evil.com in another. Scripts
from evil.com should not be able to make AJAX requests to your bank API with your
credentials -- for example withdrawing money from your account!
Cross-Origin Resource Sharing (CORS) is a https://www.w3.org/TR/cors/[W3C specification]
implemented by https://caniuse.com/#feat=cors[most browsers] that lets you specify
what kind of cross-domain requests are authorized, rather than using less secure and less
powerful workarounds based on IFRAME or JSONP.
[[mvc-cors-processing]]
== Processing
[.small]#<<web-reactive.adoc#webflux-cors-processing, See equivalent in the Reactive stack>>#
The CORS specification distinguishes between preflight, simple, and actual requests.
To learn how CORS works, you can read
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[this article], among
many others, or see the specification for more details.
Spring MVC `HandlerMapping` implementations provide built-in support for CORS. After successfully
mapping a request to a handler, `HandlerMapping` implementations check the CORS configuration for the
given request and handler and take further actions. Preflight requests are handled
directly, while simple and actual CORS requests are intercepted, validated, and have
required CORS response headers set.
In order to enable cross-origin requests (that is, the `Origin` header is present and
differs from the host of the request), you need to have some explicitly declared CORS
configuration. If no matching CORS configuration is found, preflight requests are
rejected. No CORS headers are added to the responses of simple and actual CORS requests
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{api-spring-framework}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the MVC Java configuration or the XML namespace to declare such mappings, which results
in a single global map being passed to all `HandlerMapping` instances.
You can combine global CORS configuration at the `HandlerMapping` level with more
fine-grained, handler-level CORS configuration. For example, annotated controllers can use
class- or method-level `@CrossOrigin` annotations (other handlers can implement
`CorsConfigurationSource`).
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, e.g. `allowCredentials` and `maxAge`, the local overrides the global value. See
{api-spring-framework}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
====
To learn more from the source or make advanced customizations, check the code behind:
* `CorsConfiguration`
* `CorsProcessor`, `DefaultCorsProcessor`
* `AbstractHandlerMapping`
====
[[mvc-cors-controller]]
== `@CrossOrigin`
[.small]#<<web-reactive.adoc#webflux-cors-controller, See equivalent in the Reactive stack>>#
The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
annotation enables cross-origin requests on annotated controller methods,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin
@GetMapping("/{id}")
fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long) {
// ...
}
}
----
By default, `@CrossOrigin` allows:
* All origins.
* All headers.
* All HTTP methods to which the controller method is mapped.
`allowCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
should only be used where appropriate. When it is enabled either `allowOrigins` must be
set to one or more specific domain (but not the special value `"*"`) or alternatively
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
`@CrossOrigin` is supported at the class level, too, and is inherited by all methods,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@GetMapping("/{id}")
fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long) {
// ...
}
----
You can use `@CrossOrigin` at both the class level and the method level,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long) {
// ...
}
}
----
[[mvc-cors-global]]
== Global Configuration
[.small]#<<web-reactive.adoc#webflux-cors-global, See equivalent in the Reactive stack>>#
In addition to fine-grained, controller method level configuration, you probably want to
define some global CORS configuration, too. You can set URL-based `CorsConfiguration`
mappings individually on any `HandlerMapping`. Most applications, however, use the
MVC Java configuration or the MVC XML namespace to do that.
By default, global configuration enables the following:
* All origins.
* All headers.
* `GET`, `HEAD`, and `POST` methods.
`allowCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
should only be used where appropriate. When it is enabled either `allowOrigins` must be
set to one or more specific domain (but not the special value `"*"`) or alternatively
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
[[mvc-cors-global-java]]
=== Java Configuration
[.small]#<<web-reactive.adoc#webflux-cors-global, See equivalent in the Reactive stack>>#
To enable CORS in the MVC Java config, you can use the `CorsRegistry` callback,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600)
// Add more mappings...
}
}
----
[[mvc-cors-global-xml]]
=== XML Configuration
To enable CORS in the XML namespace, you can use the `<mvc:cors>` element,
as the following example shows:
[source,xml,indent=0,subs="verbatim"]
----
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="https://domain1.com, https://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="https://domain1.com" />
</mvc:cors>
----
[[mvc-cors-filter]]
== CORS Filter
[.small]#<<webflux-cors.adoc#webflux-cors-webfilter, See equivalent in the Reactive stack>>#
You can apply CORS support through the built-in
{api-spring-framework}/web/filter/CorsFilter.html[`CorsFilter`].
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
CORS.
To configure the filter, pass a `CorsConfigurationSource` to its constructor, as the
following example shows:
[source,java,indent=0,subs="verbatim",role="primary"]
.Java
----
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
----
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
.Kotlin
----
val config = CorsConfiguration()
// Possibly...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", config)
val filter = CorsFilter(source)
----

View File

@@ -0,0 +1,881 @@
[[webmvc-fn]]
= Functional Endpoints
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
are used to route and handle requests and contracts are designed for immutability.
It is an alternative to the annotation-based programming model but otherwise runs on
the same <<web#mvc-servlet>>.
[[webmvc-fn-overview]]
== Overview
[.small]#<<web-reactive.adoc#webflux-fn-overview, See equivalent in the Reactive stack>>#
In WebMvc.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
`ServerRequest` and returns a `ServerResponse`.
Both the request and the response object have immutable contracts that offer JDK 8-friendly
access to the HTTP request and response.
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
annotation-based programming model.
Incoming requests are routed to a handler function with a `RouterFunction`: a function that
takes `ServerRequest` and returns an optional `HandlerFunction` (i.e. `Optional<HandlerFunction>`).
When the router function matches, a handler function is returned; otherwise an empty Optional.
`RouterFunction` is the equivalent of a `@RequestMapping` annotation, but with the major
difference that router functions provide not just data, but also behavior.
`RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route() // <1>
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public ServerResponse listPeople(ServerRequest request) {
// ...
}
public ServerResponse createPerson(ServerRequest request) {
// ...
}
public ServerResponse getPerson(ServerRequest request) {
// ...
}
}
----
<1> Create router using `route()`.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.servlet.function.router
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = router { // <1>
accept(APPLICATION_JSON).nest {
GET("/person/{id}", handler::getPerson)
GET("/person", handler::listPeople)
}
POST("/person", handler::createPerson)
}
class PersonHandler(private val repository: PersonRepository) {
// ...
fun listPeople(request: ServerRequest): ServerResponse {
// ...
}
fun createPerson(request: ServerRequest): ServerResponse {
// ...
}
fun getPerson(request: ServerRequest): ServerResponse {
// ...
}
}
----
<1> Create router using the router DSL.
If you register the `RouterFunction` as a bean, for instance by exposing it in a
`@Configuration` class, it will be auto-detected by the servlet, as explained in <<webmvc-fn-running>>.
[[webmvc-fn-handler-functions]]
== HandlerFunction
[.small]#<<web-reactive.adoc#webflux-fn-handler-functions, See equivalent in the Reactive stack>>#
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
access to the HTTP request and response, including headers, body, method, and status code.
[[webmvc-fn-request]]
=== ServerRequest
`ServerRequest` provides access to the HTTP method, URI, headers, and query parameters,
while access to the body is provided through the `body` methods.
The following example extracts the request body to a `String`:
[source,java,role="primary"]
.Java
----
String string = request.body(String.class);
----
[source,kotlin,role="secondary"]
.Kotlin
----
val string = request.body<String>()
----
The following example extracts the body to a `List<Person>`,
where `Person` objects are decoded from a serialized form, such as JSON or XML:
[source,java,role="primary"]
.Java
----
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
----
[source,kotlin,role="secondary"]
.Kotlin
----
val people = request.body<Person>()
----
The following example shows how to access parameters:
[source,java,role="primary"]
.Java
----
MultiValueMap<String, String> params = request.params();
----
[source,kotlin,role="secondary"]
.Kotlin
----
val map = request.params()
----
[[webmvc-fn-response]]
=== ServerResponse
`ServerResponse` provides access to the HTTP response and, since it is immutable, you can use
a `build` method to create it. You can use the builder to set the response status, to add response
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
content:
[source,java,role="primary"]
.Java
----
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
----
[source,kotlin,role="secondary"]
.Kotlin
----
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
----
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
[source,java,role="primary"]
.Java
----
URI location = ...
ServerResponse.created(location).build();
----
[source,kotlin,role="secondary"]
.Kotlin
----
val location: URI = ...
ServerResponse.created(location).build()
----
You can also use an asynchronous result as the body, in the form of a `CompletableFuture`,
`Publisher`, or any other type supported by the `ReactiveAdapterRegistry`. For instance:
[source,java,role="primary"]
.Java
----
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
----
[source,kotlin,role="secondary"]
.Kotlin
----
val person = webClient.get().retrieve().awaitBody<Person>()
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
----
If not just the body, but also the status or headers are based on an asynchronous type,
you can use the static `async` method on `ServerResponse`, which
accepts `CompletableFuture<ServerResponse>`, `Publisher<ServerResponse>`, or
any other asynchronous type supported by the `ReactiveAdapterRegistry`. For instance:
[source,java,role="primary"]
.Java
----
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
.map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);
----
https://www.w3.org/TR/eventsource/[Server-Sent Events] can be provided via the
static `sse` method on `ServerResponse`. The builder provided by that method
allows you to send Strings, or other objects as JSON. For example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public RouterFunction<ServerResponse> sse() {
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
// Save the sseBuilder object somewhere..
}));
}
// In some other thread, sending a String
sseBuilder.send("Hello world");
// Or an object, which will be transformed into JSON
Person person = ...
sseBuilder.send(person);
// Customize the event by using the other methods
sseBuilder.id("42")
.event("sse event")
.data(person);
// and done at some point
sseBuilder.complete();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
fun sse(): RouterFunction<ServerResponse> = router {
GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
// Save the sseBuilder object somewhere..
}
}
// In some other thread, sending a String
sseBuilder.send("Hello world")
// Or an object, which will be transformed into JSON
val person = ...
sseBuilder.send(person)
// Customize the event by using the other methods
sseBuilder.id("42")
.event("sse event")
.data(person)
// and done at some point
sseBuilder.complete()
----
[[webmvc-fn-handler-classes]]
=== Handler Classes
We can write a handler function as a lambda, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body("Hello World");
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val helloWorld: (ServerRequest) -> ServerResponse =
{ ServerResponse.ok().body("Hello World") }
----
--
That is convenient, but in an application we need multiple functions, and multiple inline
lambda's can get messy.
Therefore, it is useful to group related handler functions together into a handler class, which
has a similar role as `@Controller` in an annotation-based application.
For example, the following class exposes a reactive `Person` repository:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public ServerResponse listPeople(ServerRequest request) { // <1>
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
public ServerResponse createPerson(ServerRequest request) throws Exception { // <2>
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
public ServerResponse getPerson(ServerRequest request) { // <3>
int personId = Integer.parseInt(request.pathVariable("id"));
Person person = repository.getPerson(personId);
if (person != null) {
return ok().contentType(APPLICATION_JSON).body(person);
}
else {
return ServerResponse.notFound().build();
}
}
}
----
<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.
<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.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class PersonHandler(private val repository: PersonRepository) {
fun listPeople(request: ServerRequest): ServerResponse { // <1>
val people: List<Person> = repository.allPeople()
return ok().contentType(APPLICATION_JSON).body(people);
}
fun createPerson(request: ServerRequest): ServerResponse { // <2>
val person = request.body<Person>()
repository.savePerson(person)
return ok().build()
}
fun getPerson(request: ServerRequest): ServerResponse { // <3>
val personId = request.pathVariable("id").toInt()
return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) }
?: 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.
<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.
--
[[webmvc-fn-handler-validation]]
=== Validation
A functional endpoint can use Spring's <<core.adoc#validation, validation facilities>> to
apply validation to the request body. For example, given a custom Spring
<<core.adoc#validation, Validator>> implementation for a `Person`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public class PersonHandler {
private final Validator validator = new PersonValidator(); // <1>
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
validate(person); // <2>
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); // <3>
}
}
}
----
<1> Create `Validator` instance.
<2> Apply validation.
<3> Raise exception for a 400 response.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
class PersonHandler(private val repository: PersonRepository) {
private val validator = PersonValidator() // <1>
// ...
fun createPerson(request: ServerRequest): ServerResponse {
val person = request.body<Person>()
validate(person) // <2>
repository.savePerson(person)
return ok().build()
}
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.
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
a global `Validator` instance based on `LocalValidatorFactoryBean`.
See <<core.adoc#validation-beanvalidation, Spring Validation>>.
[[webmvc-fn-router-functions]]
== `RouterFunction`
[.small]#<<web-reactive.adoc#webflux-fn-router-functions, See equivalent in the Reactive stack>>#
Router functions are used to route the requests to the corresponding `HandlerFunction`.
Typically, you do not write router functions yourself, but rather use a method on the
`RouterFunctions` utility class to create one.
`RouterFunctions.route()` (no parameters) provides you with a fluent builder for creating a router
function, whereas `RouterFunctions.route(RequestPredicate, HandlerFunction)` offers a direct way
to create a router.
Generally, it is recommended to use the `route()` builder, as it provides
convenient short-cuts for typical mapping scenarios without requiring hard-to-discover
static imports.
For instance, the router function builder offers the method `GET(String, HandlerFunction)` to create a mapping for GET requests; and `POST(String, HandlerFunction)` for POSTs.
Besides HTTP method-based mapping, the route builder offers a way to introduce additional
predicates when mapping to requests.
For each HTTP method there is an overloaded variant that takes a `RequestPredicate` as a
parameter, through which additional constraints can be expressed.
[[webmvc-fn-predicates]]
=== Predicates
You can write your own `RequestPredicate`, but the `RequestPredicates` utility class
offers commonly used implementations, based on the request path, HTTP method, content-type,
and so on.
The following example uses a request predicate to create a constraint based on the `Accept`
header:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.servlet.function.router
val route = router {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().body("Hello World")
}
}
----
You can compose multiple request predicates together by using:
* `RequestPredicate.and(RequestPredicate)` -- both must match.
* `RequestPredicate.or(RequestPredicate)` -- either can match.
Many of the predicates from `RequestPredicates` are composed.
For example, `RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)`
and `RequestPredicates.path(String)`.
The example shown above also uses two request predicates, as the builder uses
`RequestPredicates.GET` internally, and composes that with the `accept` predicate.
[[webmvc-fn-routes]]
=== Routes
Router functions are evaluated in order: if the first route does not match, the
second is evaluated, and so on.
Therefore, it makes sense to declare more specific routes before general ones.
This is also important when registering router functions as Spring beans, as will
be described later.
Note that this behavior is different from the annotation-based programming model, where the
"most specific" controller method is picked automatically.
When using the router function builder, all defined routes are composed into one
`RouterFunction` that is returned from `build()`.
There are also other ways to compose multiple router functions together:
* `add(RouterFunction)` on the `RouterFunctions.route()` builder
* `RouterFunction.and(RouterFunction)`
* `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -- shortcut for
`RouterFunction.and()` with nested `RouterFunctions.route()`.
The following example shows the composition of four routes:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.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.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.web.servlet.function.router
val repository: PersonRepository = ...
val handler = PersonHandler(repository);
val otherRoute = router { }
val route = router {
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.
=== Nested Routes
It is common for a group of router functions to have a shared predicate, for instance a shared
path.
In the example above, the shared predicate would be a path predicate that matches `/person`,
used by three of the routes.
When using annotations, you would remove this duplication by using a type-level `@RequestMapping`
annotation that maps to `/person`.
In WebMvc.fn, path predicates can be shared through the `path` method on the router function builder.
For instance, the last few lines of the example above can be improved in the following way by using nested routes:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder // <1>
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST(handler::createPerson))
.build();
----
<1> Note that second parameter of `path` is a consumer that takes the router builder.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.servlet.function.router
val route = router {
"/person".nest { // <1>
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST(handler::createPerson)
}
}
----
<1> Using `nest` DSL.
Though path-based nesting is the most common, you can nest on any kind of predicate by using
the `nest` method on the builder.
The above still contains some duplication in the form of the shared `Accept`-header predicate.
We can further improve by using the `nest` method together with `accept`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.servlet.function.router
val route = router {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
POST(handler::createPerson)
}
}
}
----
[[webmvc-fn-running]]
== Running a Server
[.small]#<<web-reactive.adoc#webflux-fn-running, See equivalent in the Reactive stack>>#
You typically run router functions in a <<web.adoc#mvc-servlet, `DispatcherHandler`>>-based setup through the
<<web.adoc#mvc-config>>, which uses Spring configuration to declare the
components required to process requests. The MVC Java configuration declares the following
infrastructure components to support functional endpoints:
* `RouterFunctionMapping`: Detects one or more `RouterFunction<?>` beans in the Spring
configuration, <<core.adoc#beans-factory-ordered, orders them>>, combines them through
`RouterFunction.andOther`, and routes requests to the resulting composed `RouterFunction`.
* `HandlerFunctionAdapter`: Simple adapter that lets `DispatcherHandler` invoke
a `HandlerFunction` that was mapped to a request.
The preceding components let functional endpoints fit within the `DispatcherServlet` request
processing lifecycle and also (potentially) run side by side with annotated controllers, if
any are declared. It is also how functional endpoints are enabled by the Spring Boot Web
starter.
The following example shows a WebFlux Java configuration:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
@Configuration
@EnableMvc
class WebConfig : WebMvcConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) {
// configure message conversion...
}
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure view resolution for HTML rendering...
}
}
----
[[webmvc-fn-handler-filter-function]]
== Filtering Handler Functions
[.small]#<<web-reactive.adoc#webflux-fn-handler-filter-function, See equivalent in the Reactive stack>>#
You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing
function builder.
With annotations, you can achieve similar functionality by using `@ControllerAdvice`, a `ServletFilter`, or both.
The filter will apply to all routes that are built by the builder.
This means that filters defined in nested routes do not apply to "top-level" routes.
For instance, consider the following example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople)
.before(request -> ServerRequest.from(request) // <1>
.header("X-RequestHeader", "Value")
.build()))
.POST(handler::createPerson))
.after((request, response) -> logResponse(response)) // <2>
.build();
----
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.servlet.function.router
val route = router {
"/person".nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
before { // <1>
ServerRequest.from(it)
.header("X-RequestHeader", "Value").build()
}
}
POST(handler::createPerson)
after { _, response -> // <2>
logResponse(response)
}
}
----
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
The `filter` method on the router builder takes a `HandlerFilterFunction`: a
function that takes a `ServerRequest` and `HandlerFunction` and returns a `ServerResponse`.
The handler function parameter represents the next element in the chain.
This is typically the handler that is routed to, but it can also be another
filter if multiple are applied.
Now we can add a simple security filter to our route, assuming that we have a `SecurityManager` that
can determine whether a particular path is allowed.
The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
import org.springframework.web.servlet.function.router
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();
}
}
}
}
----
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
We only let the handler function be run when access is allowed.
Besides using the `filter` method on the router function builder, it is possible to apply a
filter to an existing router function via `RouterFunction.filter(HandlerFilterFunction)`.
NOTE: CORS support for functional endpoints is provided through a dedicated
<<webmvc-cors.adoc#mvc-cors-filter, `CorsFilter`>>.

View File

@@ -0,0 +1,28 @@
[[webmvc.test]]
= Testing
[.small]#<<web-reactive.adoc#webflux-test, See equivalent in the Reactive stack>>#
This section summarizes the options available in `spring-test` for Spring MVC applications.
* Servlet API Mocks: Mock implementations of Servlet API contracts for unit testing controllers,
filters, and other web components. See <<testing.adoc#mock-objects-servlet, Servlet API>>
mock objects for more details.
* TestContext Framework: Support for loading Spring configuration in JUnit and TestNG tests,
including efficient caching of the loaded configuration across test methods and support for
loading a `WebApplicationContext` with a `MockServletContext`.
See <<testing.adoc#testcontext-framework,TestContext Framework>> for more details.
* Spring MVC Test: A framework, also known as `MockMvc`, for testing annotated controllers
through the `DispatcherServlet` (that is, supporting annotations), complete with the
Spring MVC infrastructure but without an HTTP server.
See <<testing.adoc#spring-mvc-test-framework, Spring MVC Test>> for more details.
* Client-side REST: `spring-test` provides a `MockRestServiceServer` that you can use as
a mock server for testing client-side code that internally uses the `RestTemplate`.
See <<testing.adoc#spring-mvc-test-client, Client REST Tests>> for more details.
* `WebTestClient`: Built for testing WebFlux applications, but it can also be used for
end-to-end integration testing, to any server, over an HTTP connection. It is a
non-blocking, reactive client and is well suited for testing asynchronous and streaming
scenarios. See <<testing.adoc#webtestclient, `WebTestClient`>> for more details.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
[id={chapter}.websocket-intro]
= Introduction to WebSocket
The WebSocket protocol, https://tools.ietf.org/html/rfc6455[RFC 6455], provides a standardized
way to establish a full-duplex, two-way communication channel between client and server
over a single TCP connection. It is a different TCP protocol from HTTP but is designed to
work over HTTP, using ports 80 and 443 and allowing re-use of existing firewall rules.
A WebSocket interaction begins with an HTTP request that uses the HTTP `Upgrade` header
to upgrade or, in this case, to switch to the WebSocket protocol. The following example
shows such an interaction:
[source,yaml,indent=0,subs="verbatim,quotes"]
----
GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket <1>
Connection: Upgrade <2>
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
----
<1> The `Upgrade` header.
<2> Using the `Upgrade` connection.
Instead of the usual 200 status code, a server with WebSocket support returns output
similar to the following:
[source,yaml,indent=0,subs="verbatim,quotes"]
----
HTTP/1.1 101 Switching Protocols <1>
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
----
<1> Protocol switch
After a successful handshake, the TCP socket underlying the HTTP upgrade request remains
open for both the client and the server to continue to send and receive messages.
A complete introduction of how WebSockets work is beyond the scope of this document.
See RFC 6455, the WebSocket chapter of HTML5, or any of the many introductions and
tutorials on the Web.
Note that, if a WebSocket server is running behind a web server (e.g. nginx), you
likely need to configure it to pass WebSocket upgrade requests on to the WebSocket
server. Likewise, if the application runs in a cloud environment, check the
instructions of the cloud provider related to WebSocket support.
[id={chapter}.websocket-intro-architecture]
== HTTP Versus WebSocket
Even though WebSocket is designed to be HTTP-compatible and starts with an HTTP request,
it is important to understand that the two protocols lead to very different
architectures and application programming models.
In HTTP and REST, an application is modeled as many URLs. To interact with the application,
clients access those URLs, request-response style. Servers route requests to the
appropriate handler based on the HTTP URL, method, and headers.
By contrast, in WebSockets, there is usually only one URL for the initial connect.
Subsequently, all application messages flow on that same TCP connection. This points to
an entirely different asynchronous, event-driven, messaging architecture.
WebSocket is also a low-level transport protocol, which, unlike HTTP, does not prescribe
any semantics to the content of messages. That means that there is no way to route or process
a message unless the client and the server agree on message semantics.
WebSocket clients and servers can negotiate the use of a higher-level, messaging protocol
(for example, STOMP), through the `Sec-WebSocket-Protocol` header on the HTTP handshake request.
In the absence of that, they need to come up with their own conventions.
[id={chapter}.websocket-intro-when-to-use]
== When to Use WebSockets
WebSockets can make a web page be dynamic and interactive. However, in many cases,
a combination of AJAX and HTTP streaming or long polling can provide a simple and
effective solution.
For example, news, mail, and social feeds need to update dynamically, but it may be
perfectly okay to do so every few minutes. Collaboration, games, and financial apps, on
the other hand, need to be much closer to real-time.
Latency alone is not a deciding factor. If the volume of messages is relatively low (for example,
monitoring network failures) HTTP streaming or polling can provide an effective solution.
It is the combination of low latency, high frequency, and high volume that make the best
case for the use of WebSocket.
Keep in mind also that over the Internet, restrictive proxies that are outside of your control
may preclude WebSocket interactions, either because they are not configured to pass on the
`Upgrade` header or because they close long-lived connections that appear idle. This
means that the use of WebSocket for internal applications within the firewall is a more
straightforward decision than it is for public facing applications.

File diff suppressed because it is too large Load Diff