Edit the Web chapter of the reference documentation
I edited for spelling, punctuation, grammar, usage, and corporate voice. I also added links and cross-references.
This commit is contained in:
parent
b29a278b2a
commit
00a3afcda8
|
@ -7,12 +7,12 @@
|
|||
:tabsize: 4
|
||||
:docinfo1:
|
||||
|
||||
This part of the documentation covers support for reactive stack, web applications built on a
|
||||
This part of the documentation covers support for reactive-stack web applications built on a
|
||||
http://www.reactive-streams.org/[Reactive Streams] API to run on non-blocking
|
||||
servers such as Netty, Undertow, and Servlet 3.1+ containers. Individual chapters cover
|
||||
servers, such as Netty, Undertow, and Servlet 3.1+ containers. Individual chapters cover
|
||||
the <<webflux,Spring WebFlux>> framework,
|
||||
the reactive <<webflux-client,WebClient>>, support for <<webflux-test>>,
|
||||
and <<webflux-reactive-libraries>>. For Servlet stack, web applications, please see
|
||||
the reactive <<webflux-client,`WebClient`>>, support for <<webflux-test, testing>>,
|
||||
and <<webflux-reactive-libraries, reactive libraries>>. For Servlet-stack web applications, see
|
||||
<<web.adoc#spring-web,Web on Servlet Stack>>.
|
||||
|
||||
include::web/webflux.adoc[leveloffset=+1]
|
||||
|
@ -29,19 +29,18 @@ include::web/webflux-websocket.adoc[leveloffset=+1]
|
|||
|
||||
The `spring-test` module provides mock implementations of `ServerHttpRequest`,
|
||||
`ServerHttpResponse`, and `ServerWebExchange`.
|
||||
See <<testing.adoc#mock-objects-web-reactive,Spring Web Reactive>> mock objects.
|
||||
See <<testing.adoc#mock-objects-web-reactive,Spring Web Reactive>> for a discussion of mock objects.
|
||||
|
||||
The <<testing.adoc#webtestclient,WebTestClient>> builds on these mock request and
|
||||
response objects to provide support for testing WebFlux applications without and HTTP
|
||||
server. The `WebTestClient` can be used for end-to-end integration tests too.
|
||||
<<testing.adoc#webtestclient,`WebTestClient`>> builds on these mock request and
|
||||
response objects to provide support for testing WebFlux applications without an HTTP
|
||||
server. You can use the `WebTestClient` for end-to-end integration tests, too.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-threading-model]]
|
||||
=== Threading model
|
||||
|
||||
|
||||
//[[webflux-threading-model]]
|
||||
//=== Threading model
|
||||
// TODO Once we have content for this heading, we should un-comment the heading and
|
||||
// anchor. Until then, we should leave it commented.
|
||||
|
||||
|
||||
|
||||
|
@ -49,30 +48,27 @@ server. The `WebTestClient` can be used for end-to-end integration tests too.
|
|||
== Reactive Libraries
|
||||
|
||||
`spring-webflux` depends on `reactor-core` and uses it internally to compose asynchronous
|
||||
logic and to provide Reactive Streams support. Generally WebFlux APIs return `Flux` or
|
||||
`Mono` -- since that's what's used internally, and leniently accept any Reactive Streams
|
||||
`Publisher` implementation as input. The use of `Flux` vs `Mono` is important because it
|
||||
helps to express cardinality -- e.g. whether a single or multiple async values are
|
||||
expected, and that can be essential for making decisions, for example when encoding or
|
||||
decoding HTTP messages.
|
||||
logic and to provide Reactive Streams support. Generally, WebFlux APIs return `Flux` or
|
||||
`Mono` (since those are used internally) and leniently accept any Reactive Streams
|
||||
`Publisher` implementation as input. The use of `Flux` versus `Mono` is important, because it
|
||||
helps to express cardinality -- for example, whether a single or multiple asynchronous values are
|
||||
expected, and that can be essential for making decisions (for example, when encoding or
|
||||
decoding HTTP messages).
|
||||
|
||||
For annotated controllers, WebFlux transparently adapts to the reactive library chosen by
|
||||
the application. This is done with the help of the
|
||||
{api-spring-framework}/core/ReactiveAdapterRegistry.html[ReactiveAdapterRegistry] which
|
||||
{api-spring-framework}/core/ReactiveAdapterRegistry.html[`ReactiveAdapterRegistry`], which
|
||||
provides pluggable support for reactive library and other asynchronous types. The registry
|
||||
has built-in support for RxJava and `CompletableFuture`, but others can be registered too.
|
||||
has built-in support for RxJava and `CompletableFuture`, but you can register others, too.
|
||||
|
||||
For functional APIs such as <<webflux-fn>>, the `WebClient`, and others, the general rules
|
||||
for WebFlux APIs apply -- `Flux` and `Mono` as return values, and Reactive Streams
|
||||
For functional APIs (such as <<webflux-fn>>, the `WebClient`, and others), the general rules
|
||||
for WebFlux APIs apply -- `Flux` and `Mono` as return values and a Reactive Streams
|
||||
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
|
||||
is provided, it can only be treated as a stream with unknown semantics (0..N). If however
|
||||
is provided, it can be treated only as a stream with unknown semantics (0..N). If, however,
|
||||
the semantics are known, you can wrap it with `Flux` or `Mono.from(Publisher)` instead
|
||||
of passing the raw `Publisher`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
For example, given a `Publisher` that is not a `Mono`, the Jackson JSON message writer
|
||||
expects multiple values. If the media type implies an infinite stream -- e.g.
|
||||
`"application/json+stream"`, values are written and flushed individually; otherwise
|
||||
expects multiple values. If the media type implies an infinite stream (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are buffered into a list and rendered as a JSON array.
|
||||
====
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
:tabsize: 4
|
||||
:docinfo1:
|
||||
|
||||
This part of the documentation covers support for Servlet stack, web applications built on the
|
||||
This part of the documentation covers support for Servlet-stack web applications built on the
|
||||
Servlet API and deployed to Servlet containers. Individual chapters include <<mvc,Spring MVC>>,
|
||||
<<mvc-view,View Technologies>>, <<mvc-cors,CORS Support>>, and <<websocket,WebSocket Support>>.
|
||||
For reactive stack, web applications, go to <<web-reactive.adoc#spring-web-reactive,Web on Reactive Stack>>.
|
||||
For reactive-stack web applications, see <<web-reactive.adoc#spring-web-reactive,Web on Reactive Stack>>.
|
||||
|
||||
include::web/webmvc.adoc[leveloffset=+1]
|
||||
|
||||
|
@ -21,4 +21,3 @@ include::web/webmvc-test.adoc[leveloffset=+1]
|
|||
include::web/websocket.adoc[leveloffset=+1]
|
||||
|
||||
include::web/integration.adoc[leveloffset=+1]
|
||||
|
||||
|
|
|
@ -1,53 +1,47 @@
|
|||
[[web-integration]]
|
||||
= Other Web Frameworks
|
||||
|
||||
|
||||
|
||||
|
||||
[[intro]]
|
||||
== Introduction
|
||||
|
||||
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 one to use or buy into any
|
||||
_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 framework
|
||||
(<<mvc,Spring MVC>>), while at the same time providing integration with a number of
|
||||
(<<mvc,Spring MVC>>) while, at the same time, providing integration with a number of
|
||||
popular third party web frameworks.
|
||||
|
||||
|
||||
|
||||
|
||||
[[web-integration-common]]
|
||||
== Common config
|
||||
== Common Configuration
|
||||
Before diving into the integration specifics of each supported web framework, let us
|
||||
first take a look at the Spring configuration that is __not__ specific to any one web
|
||||
first take a look at the Spring configuration that is not specific to any one web
|
||||
framework. (This section is equally applicable to Spring's own web framework, Spring
|
||||
MVC.)
|
||||
|
||||
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) defined in a service layer to satisfy business specific (and
|
||||
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, etc. 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 one configures a Spring container (a
|
||||
`WebApplicationContext`) that contains all of the 'business beans' in one's application.
|
||||
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.
|
||||
|
||||
On to specifics: all that one need do is to declare a
|
||||
Moving on to specifics, all you one need to do is declare a
|
||||
{api-spring-framework}/web/context/ContextLoaderListener.html[`ContextLoaderListener`]
|
||||
in the standard Java EE servlet `web.xml` file of one's web application, and add a
|
||||
in the standard Java 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.
|
||||
|
||||
Find below the <listener/> configuration:
|
||||
Consider the following `<listener/>` configuration:
|
||||
|
||||
====
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -55,9 +49,11 @@ Find below the <listener/> configuration:
|
|||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
----
|
||||
====
|
||||
|
||||
Find below the <context-param/> configuration:
|
||||
Further consider the following `<context-param/>` configuration:
|
||||
|
||||
====
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -66,30 +62,35 @@ Find below the <context-param/> configuration:
|
|||
<param-value>/WEB-INF/applicationContext*.xml</param-value>
|
||||
</context-param>
|
||||
----
|
||||
====
|
||||
|
||||
If you don't specify the `contextConfigLocation` context parameter, the
|
||||
`ContextLoaderListener` will look for a file called `/WEB-INF/applicationContext.xml` to
|
||||
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, and so one can use the
|
||||
following code snippet to get access to this 'business context' `ApplicationContext`
|
||||
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 don't have to remember the name of the `ServletContext`
|
||||
attribute. Its __getWebApplicationContext()__ method will return `null` if an object
|
||||
doesn't exist under the `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`
|
||||
key. Rather than risk getting `NullPointerExceptions` in your application, it's better
|
||||
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.
|
||||
|
||||
|
@ -99,29 +100,26 @@ 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
|
||||
allow you to use dependency injection on their controllers. Each web framework section
|
||||
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. As of Java EE 5, it is an official part of the Java EE umbrella.
|
||||
|
||||
For a popular JSF runtime as well as for popular JSF component libraries, check out the
|
||||
http://myfaces.apache.org/[Apache MyFaces project]. The MyFaces project also provides
|
||||
common JSF extensions such as http://myfaces.apache.org/orchestra/[MyFaces Orchestra]:
|
||||
a Spring-based JSF extension that provides rich conversation scope support.
|
||||
common JSF extensions, such as http://myfaces.apache.org/orchestra/[MyFaces Orchestra]
|
||||
(a Spring-based JSF extension that provides rich conversation scope support).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring Web Flow 2.0 provides rich JSF support through its newly established Spring Faces
|
||||
NOTE: Spring Web Flow 2.0 provides rich JSF support through its newly established Spring Faces
|
||||
module, both for JSF-centric usage (as described in this section) and for Spring-centric
|
||||
usage (using JSF views within a Spring MVC dispatcher). Check out the
|
||||
http://projects.spring.io/spring-webflow[Spring Web Flow website] for details!
|
||||
====
|
||||
usage (using JSF views within a Spring MVC dispatcher). See the
|
||||
http://projects.spring.io/spring-webflow[Spring Web Flow website] for details.
|
||||
|
||||
The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
|
||||
|
||||
|
@ -129,15 +127,17 @@ The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
|
|||
|
||||
[[jsf-springbeanfaceselresolver]]
|
||||
=== Spring Bean Resolver
|
||||
|
||||
`SpringBeanFacesELResolver` is a JSF 1.2+ compliant `ELResolver` implementation,
|
||||
integrating with the standard Unified EL as used by JSF 1.2 and JSP 2.1. Like
|
||||
`SpringBeanVariableResolver`, it delegates to the Spring's 'business context'
|
||||
`WebApplicationContext` __first__, then to the default resolver of the underlying JSF
|
||||
integrating with the standard Unified EL as used by JSF 1.2 and JSP 2.1. As
|
||||
`SpringBeanVariableResolver`, it delegates to Spring's "`business context`"
|
||||
`WebApplicationContext` first and then to the default resolver of the underlying JSF
|
||||
implementation.
|
||||
|
||||
Configuration-wise, simply define `SpringBeanFacesELResolver` in your JSF
|
||||
__faces-context.xml__ file:
|
||||
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"]
|
||||
----
|
||||
|
@ -148,33 +148,39 @@ __faces-context.xml__ file:
|
|||
</application>
|
||||
</faces-config>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[jsf-facescontextutils]]
|
||||
=== FacesContextUtils
|
||||
A custom `VariableResolver` works well when mapping one's properties to beans
|
||||
in __faces-config.xml__, but at times one may need to grab a bean explicitly. The
|
||||
=== Using `FacesContextUtils`
|
||||
|
||||
A custom `VariableResolver` 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 2.x
|
||||
Invented by Craig McClanahan, http://struts.apache.org[Struts] is an open source project
|
||||
|
||||
Invented by Craig McClanahan, http://struts.apache.org[Struts] is an open-source project
|
||||
hosted by the Apache Software Foundation. At the time, it 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 thus free as in
|
||||
beer), and it had a large community, which allowed the project to grow and become popular
|
||||
frameworks. It simplified the programming model, it was open source (and thus free, as in
|
||||
beer), and it had a large community, which let the project grow and become popular
|
||||
among Java web developers.
|
||||
|
||||
Check out the Struts
|
||||
|
@ -183,28 +189,25 @@ built-in Spring integration shipped with Struts.
|
|||
|
||||
|
||||
|
||||
|
||||
[[tapestry]]
|
||||
== Tapestry 5.x
|
||||
From the http://tapestry.apache.org/[Tapestry homepage]:
|
||||
|
||||
Tapestry is a "__Component oriented framework for creating dynamic, robust,
|
||||
highly scalable web applications in Java.__"
|
||||
http://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 using a combination of Tapestry
|
||||
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, check out Tapestry's dedicated
|
||||
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
|
||||
Find below links to further resources about the various web frameworks described in this
|
||||
The following links go to further resources about the various web frameworks described in this
|
||||
chapter.
|
||||
|
||||
* The http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html[JSF] homepage
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
= UriComponents
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` helps to build URI's from URI templates with variables:
|
||||
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -17,13 +18,16 @@
|
|||
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add and/or replace URI components.
|
||||
<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`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
====
|
||||
|
||||
The above can be consolidated into one chain and shortened with `buildAndExpand`:
|
||||
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"]
|
||||
----
|
||||
|
@ -34,9 +38,12 @@ The above can be consolidated into one chain and shortened with `buildAndExpand`
|
|||
.buildAndExpand("Westin", "123")
|
||||
.toUri();
|
||||
----
|
||||
====
|
||||
|
||||
It can be shortened further by going directly to URI (which implies encoding):
|
||||
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"]
|
||||
----
|
||||
|
@ -45,9 +52,11 @@ It can be shortened further by going directly to URI (which implies encoding):
|
|||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
====
|
||||
|
||||
Or shorter further yet, with a full URI template:
|
||||
You shorter it further still with a full URI template, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -55,24 +64,27 @@ Or shorter further yet, with a full URI template:
|
|||
.fromUriString("http://example.com/hotels/{hotel}?q={q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[web-uribuilder]]
|
||||
= UriBuilder
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
<<web-uricomponents,UriComponentsBuilder>> implements `UriBuilder`. A `UriBuilder` in turn
|
||||
can be created with a `UriBuilderFactory`. Together `UriBuilderFactory` and `UriBuilder`
|
||||
<<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 others.
|
||||
configuration, such as a base URL, encoding preferences, and other details.
|
||||
|
||||
The `RestTemplate` and the `WebClient` can be configured with a `UriBuilderFactory`
|
||||
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.
|
||||
|
||||
`RestTemplate` example:
|
||||
The following example shows how to configure a `RestTemplate`:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -85,9 +97,11 @@ exposes shared configuration options.
|
|||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(factory);
|
||||
----
|
||||
====
|
||||
|
||||
`WebClient` example:
|
||||
The following example configures a `WebClient`:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -99,11 +113,13 @@ exposes shared configuration options.
|
|||
|
||||
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
|
||||
----
|
||||
====
|
||||
|
||||
In addition `DefaultUriBuilderFactory` can also be used directly. It is similar to using
|
||||
`UriComponentsBuilder` but instead of static factory methods, it is an actual instance
|
||||
that holds configuration and preferences:
|
||||
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"]
|
||||
----
|
||||
|
@ -114,35 +130,35 @@ that holds configuration and preferences:
|
|||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[web-uri-encoding]]
|
||||
= URI Encoding
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` exposes encoding options at 2 levels:
|
||||
`UriComponentsBuilder` exposes encoding options at two levels:
|
||||
|
||||
. {api-spring-framework}/web/util/UriComponentsBuilder.html#encode--[UriComponentsBuilder#encode()] -
|
||||
pre-encodes the URI template first, 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.
|
||||
* {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 option
|
||||
1 also replaces characters with reserved meaning that appear in URI variables.
|
||||
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. Option 1 replaces
|
||||
";" with "%3B" in URI variables but not in the URI template. By contrast, option 2 never
|
||||
replaces ";" since it is a legal character in a path.
|
||||
====
|
||||
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 option 1 is likely to give the expected result because it treats URI
|
||||
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 option 2 is useful only if
|
||||
URI variables intentionally contain reserved characters.
|
||||
|
||||
Example usage using option 1:
|
||||
The following example uses the first option:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -154,9 +170,12 @@ URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
|||
|
||||
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
|
||||
----
|
||||
====
|
||||
|
||||
The above can be shortened by going directly to URI (which implies encoding):
|
||||
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"]
|
||||
----
|
||||
|
@ -164,19 +183,24 @@ URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
|||
.queryParam("q", "{q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
====
|
||||
|
||||
Or shorter further yet, with a full URI template:
|
||||
You can shorten it further still with a full URI template, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/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:
|
||||
the `UriBuilderFactory` strategy. Both can be configured with a custom strategy.
|
||||
as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -191,22 +215,23 @@ the `UriBuilderFactory` strategy. Both can be configured with a custom strategy:
|
|||
// Customize the WebClient..
|
||||
WebClient 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:
|
||||
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
|
||||
option 1 above, to pre-encode the URI template and strictly encode URI variables when
|
||||
* `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 via `UriUtils#encodeUriUriVariables` prior to expanding them into the
|
||||
* `VALUES_ONLY`: Does not encode the URI template and, instead, applies strict encoding
|
||||
to URI variables through `UriUtils#encodeUriUriVariables` prior to expanding them into the
|
||||
template.
|
||||
* `URI_COMPONENTS` -- uses `UriComponents#encode()`, corresponding to option 2 above, to
|
||||
* `URI_COMPONENTS`: 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.
|
||||
* `NONE`: No encoding is applied.
|
||||
|
||||
Out of the box the `RestTemplate` is set to `EncodingMode.URI_COMPONENTS` for historic
|
||||
The `RestTemplate` is set to `EncodingMode.URI_COMPONENTS` for historic
|
||||
reasons and for backwards compatibility. The `WebClient` relies on the default value
|
||||
in `DefaultUriBuilderFactory` which was changed from `EncodingMode.URI_COMPONENTS` in
|
||||
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.
|
||||
in `DefaultUriBuilderFactory`, which was changed from `EncodingMode.URI_COMPONENTS` in
|
||||
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.
|
||||
|
|
|
@ -1,84 +1,86 @@
|
|||
[[webflux-cors]]
|
||||
= CORS
|
||||
[.small]#<<web.adoc#mvc-cors,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors,Same as in Spring MVC>>#
|
||||
|
||||
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,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-intro,Same as in Spring MVC>>#
|
||||
|
||||
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
|
||||
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, e.g. withdrawing money from your account!
|
||||
credentials -- for example, withdrawing money from your account!
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) is a http://www.w3.org/TR/cors/[W3C specification]
|
||||
implemented by http://caniuse.com/#feat=cors[most browsers] that allows you to specify
|
||||
what kind of cross domain requests are authorized rather than using less secure and less
|
||||
implemented by http://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,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-processing,Same as in Spring MVC>>#
|
||||
|
||||
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 refer to the specification for more details.
|
||||
many others, or see the specification for more details.
|
||||
|
||||
Spring WebFlux ``HandlerMapping``'s provide built-in support for CORS. After successfully
|
||||
mapping a request to a handler, ``HandlerMapping``'s 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
|
||||
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 (i.e. the `Origin` header is present and
|
||||
differs from the host of the request) you need to have some explicitly declared CORS
|
||||
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.
|
||||
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
|
||||
will use the WebFlux Java config to declare such mappings, which results in a single,
|
||||
global map passed to all ``HadlerMappping``'s.
|
||||
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 `HadlerMappping` implementations.
|
||||
|
||||
Global CORS configuration at the `HandlerMapping` level can be combined with more
|
||||
fine-grained, handler-level CORS configuration. For example annotated controllers can use
|
||||
class or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
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 -- e.g.
|
||||
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
|
||||
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 make advanced customizations, check:
|
||||
To learn more from the source or to make advanced customizations, see:
|
||||
|
||||
* `CorsConfiguration`
|
||||
* `CorsProcessor`, `DefaultCorsProcessor`
|
||||
* `CorsProcessor` and `DefaultCorsProcessor`
|
||||
* `AbstractHandlerMapping`
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-controller]]
|
||||
== @CrossOrigin
|
||||
[.small]#<<web.adoc#mvc-cors-controller,Same in Spring MVC>>#
|
||||
== Using `@CrossOrigin`
|
||||
[.small]#<<web.adoc#mvc-cors-controller,Same as in Spring MVC>>#
|
||||
|
||||
The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
|
||||
annotation enables cross-origin requests on annotated controller methods:
|
||||
annotation enables cross-origin requests on annotated controller methods, as the
|
||||
following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -98,19 +100,25 @@ public class AccountController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
By default `@CrossOrigin` allows:
|
||||
By default, `@CrossOrigin` allows:
|
||||
|
||||
* All origins.
|
||||
* All headers.
|
||||
* All HTTP methods to which the controller method is mapped.
|
||||
* `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 only be used where appropriate.
|
||||
* `maxAge` is set to 30 minutes.
|
||||
|
||||
`@CrossOrigin` is supported at the class level too and inherited by all 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.
|
||||
|
||||
`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"]
|
||||
----
|
||||
|
@ -130,18 +138,21 @@ public class AccountController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
`CrossOrigin` can be used at both class and method-level:
|
||||
You can use `@CrossOrigin` at both the class and the method level,
|
||||
as the following example shows:
|
||||
====
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600)
|
||||
@CrossOrigin(maxAge = 3600) <1>
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin("http://domain2.com")
|
||||
@CrossOrigin("http://domain2.com") <2>
|
||||
@GetMapping("/{id}")
|
||||
public Mono<Account> retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
|
@ -153,31 +164,37 @@ public class AccountController {
|
|||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-global]]
|
||||
== Global Config
|
||||
[.small]#<<web.adoc#mvc-cors-global,Same in Spring MVC>>#
|
||||
== Global Configuration
|
||||
[.small]#<<web.adoc#mvc-cors-global,Same as in Spring MVC>>#
|
||||
|
||||
In addition to fine-grained, controller method level configuration you'll probably want to
|
||||
define some global CORS configuration too. You can set URL-based `CorsConfiguration`
|
||||
mappings individually on any `HandlerMapping`. Most applications however will use the
|
||||
WebFlux Java config to do that.
|
||||
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 only be used where appropriate.
|
||||
* `maxAge` is set to 30 minutes.
|
||||
|
||||
To enable CORS in the WebFlux Java config, use the `CorsRegistry` callback:
|
||||
`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.
|
||||
|
||||
`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"]
|
||||
----
|
||||
|
@ -199,19 +216,22 @@ public class WebConfig implements WebFluxConfigurer {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-webfilter]]
|
||||
== CORS WebFilter
|
||||
[.small]#<<web.adoc#mvc-cors-filter,Same in Spring MVC>>#
|
||||
== CORS `WebFilter`
|
||||
[.small]#<<web.adoc#mvc-cors-filter,Same as in Spring MVC>>#
|
||||
|
||||
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>>.
|
||||
|
||||
To configure the filter, you can declare a `CorsWebFilter` bean and pass a
|
||||
`CorsConfigurationSource` to its constructor:
|
||||
`CorsConfigurationSource` to its constructor, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
|
@ -234,4 +254,4 @@ CorsWebFilter corsFilter() {
|
|||
return new CorsWebFilter(source);
|
||||
}
|
||||
----
|
||||
|
||||
====
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
[[webflux-fn]]
|
||||
= Functional Endpoints
|
||||
|
||||
Spring WebFlux includes a lightweight, functional programming model in which functions
|
||||
Spring WebFlux includes 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 annotated-based programming model but otherwise running on
|
||||
the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation
|
||||
|
||||
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
|
||||
|
||||
An HTTP request is handled with a **`HandlerFunction`** that takes `ServerRequest` and
|
||||
returns `Mono<ServerResponse>`, both of which are immutable contracts that offer JDK-8
|
||||
friendly access to the HTTP request and response. `HandlerFunction` is the equivalent of
|
||||
an `@RequestMapping` method in the annotation-based programming model.
|
||||
An HTTP request is handled with a `HandlerFunction` that takes `ServerRequest` and
|
||||
returns `Mono<ServerResponse>`, both of which are immutable contracts that offer
|
||||
JDK 8-friendly access to the HTTP request and response. `HandlerFunction` is the equivalent of
|
||||
a `@RequestMapping` method in the annotation-based programming model.
|
||||
|
||||
Requests are routed to a `HandlerFunction` with a **`RouterFunction`** that takes
|
||||
Requests are routed to a `HandlerFunction` with a `RouterFunction` that takes
|
||||
`ServerRequest` and returns `Mono<HandlerFunction>`. When a request is matched to a
|
||||
particular route, the `HandlerFunction` mapped to the route is used. `RouterFunction` is
|
||||
the equivalent of an `@RequestMapping` annotation.
|
||||
the equivalent of a `@RequestMapping` annotation.
|
||||
|
||||
`RouterFunctions.route(RequestPredicate, HandlerFunction)` provides a router function
|
||||
default implementation that can be used with a number of built-in request predicates.
|
||||
For example:
|
||||
default implementation that can be used with a number of built-in request predicates,
|
||||
as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -59,6 +59,7 @@ public class PersonHandler {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
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>>:
|
||||
|
@ -66,96 +67,137 @@ through one of the built-in <<web-reactive.adoc#webflux-httphandler,server adapt
|
|||
* `RouterFunctions.toHttpHandler(RouterFunction)`
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
|
||||
|
||||
|
||||
Most applications will run through the WebFlux Java config, see <<webflux-fn-running>>.
|
||||
|
||||
Most applications can run through the WebFlux Java configuration, see <<webflux-fn-running>>.
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-handler-functions]]
|
||||
== HandlerFunction
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK-8 friendly
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
access to the HTTP request and response with
|
||||
http://www.reactive-streams.org[Reactive Streams] back pressure against the request
|
||||
and response body stream. 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
|
||||
and `Mono`. For more on that, see
|
||||
<<web-reactive.adoc#webflux-reactive-libraries,Reactive Libraries>>.
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-request]]
|
||||
=== ServerRequest
|
||||
=== Using `ServerRequest`
|
||||
|
||||
`ServerRequest` provides access to the HTTP method, URI, headers, and query parameters
|
||||
`ServerRequest` provides access to the HTTP method, URI, headers, and query parameters,
|
||||
while access to the body is provided through the `body` methods.
|
||||
|
||||
To extract the request body to a `Mono<String>`:
|
||||
The following example extracts the request body to a `Mono<String>`:
|
||||
|
||||
Mono<String> string = request.bodyToMono(String.class);
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Mono<String> string = request.bodyToMono(String.class);
|
||||
----
|
||||
====
|
||||
|
||||
To extract the body to a `Flux<Person>`, where `Person` objects are decoded from some
|
||||
The following example extracts the body to a `Flux<Person>`, where `Person` objects are decoded from some
|
||||
serialized form, such as JSON or XML:
|
||||
|
||||
Flux<Person> people = request.bodyToFlux(Person.class);
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Flux<Person> people = request.bodyToFlux(Person.class);
|
||||
----
|
||||
====
|
||||
|
||||
The above 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 above can
|
||||
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:
|
||||
|
||||
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
|
||||
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
|
||||
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
|
||||
----
|
||||
====
|
||||
|
||||
To access form data:
|
||||
The following example shows how to access form data:
|
||||
|
||||
Mono<MultiValueMap<String, String> map = request.body(BodyExtractors.toFormData());
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Mono<MultiValueMap<String, String> map = request.body(BodyExtractors.toFormData());
|
||||
----
|
||||
====
|
||||
|
||||
To access multipart data as a map:
|
||||
The following example shows how to access multipart data as a map:
|
||||
|
||||
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
|
||||
----
|
||||
====
|
||||
|
||||
To access multiparts, one at a time, in streaming fashion:
|
||||
The following example shows how to access multiparts, one at a time, in streaming fashion:
|
||||
|
||||
Flux<Part> parts = request.body(BodyExtractos.toParts());
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Flux<Part> parts = request.body(BodyExtractos.toParts());
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-response]]
|
||||
=== ServerResponse
|
||||
=== Using `ServerResponse`
|
||||
|
||||
`ServerResponse` provides access to the HTTP response and since it is immutable, you use
|
||||
a build to create it. The builder can be used to set the response status, to add response
|
||||
headers, or to provide a body. Below is an example with a 200 (OK) response with JSON
|
||||
`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:
|
||||
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
----
|
||||
====
|
||||
|
||||
This is how to build a 201 (CREATED) response with `"Location"` header, and no body:
|
||||
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
|
||||
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-handler-classes]]
|
||||
=== Handler Classes
|
||||
|
||||
We can write a handler function as a lambda. For example:
|
||||
We can write a handler function as a lambda, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
HandlerFunction<ServerResponse> helloWorld =
|
||||
request -> ServerResponse.ok().body(fromObject("Hello World"));
|
||||
----
|
||||
====
|
||||
|
||||
That is convenient but in an application we need multiple functions and useful to group
|
||||
related handler functions together into a handler (like an `@Controller`). For example,
|
||||
here is a class that exposes a reactive `Person` repository:
|
||||
That is convenient, but, in an application, we need multiple functions, and it is useful to group
|
||||
related handler functions together into a handler (like a `@Controller`). For example,
|
||||
the following class exposes a reactive `Person` repository:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -192,35 +234,36 @@ public class PersonHandler {
|
|||
<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
|
||||
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, i.e.
|
||||
when the `Person` has been saved.
|
||||
<3> `getPerson` is a handler function that returns a single person, identified via the path
|
||||
variable `id`. We retrieve that `Person` via the repository, and create a JSON response if it is
|
||||
`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.
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-router-functions]]
|
||||
== RouterFunction
|
||||
== Using `RouterFunction`
|
||||
|
||||
`RouterFunction` is used to route requests to a `HandlerFunction`. Typically, you do not
|
||||
write router functions yourself, but rather use
|
||||
`RouterFunctions.route(RequestPredicate, HandlerFunction)`. If the predicate applies, the
|
||||
request is routed to the given `HandlerFunction`, or otherwise no routing is performed,
|
||||
request is routed to the given `HandlerFunction`. Otherwise, no routing is performed,
|
||||
and that would translate to a 404 (Not Found) response.
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-predicates]]
|
||||
=== Predicates
|
||||
=== Using Predicates
|
||||
|
||||
You can write your own `RequestPredicate`, but the `RequestPredicates` utility class
|
||||
offers commonly implementations, based on the request path, HTTP method, content-type,
|
||||
and so on. For example:
|
||||
offers commonly used implementations, based on the request path, HTTP method, content-type,
|
||||
and so on. The following example creates a request predicate based on a path:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -228,18 +271,19 @@ RouterFunction<ServerResponse> route =
|
|||
RouterFunctions.route(RequestPredicates.path("/hello-world"),
|
||||
request -> Response.ok().body(fromObject("Hello World")));
|
||||
----
|
||||
====
|
||||
|
||||
You can compose multiple request predicates together via:
|
||||
You can compose multiple request predicates together by using:
|
||||
|
||||
* `RequestPredicate.and(RequestPredicate)` -- both must match.
|
||||
* `RequestPredicate.or(RequestPredicate)` -- either may match.
|
||||
* `RequestPredicate.or(RequestPredicate)` -- either can match.
|
||||
|
||||
Many of the predicates from `RequestPredicates` are composed. For example
|
||||
Many of the predicates from `RequestPredicates` are composed. For example,
|
||||
`RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)`
|
||||
and `RequestPredicates.path(String)`.
|
||||
|
||||
You can compose multiple router functions into one, such that they're evaluated in order,
|
||||
and if the first route doesn't match, the second is evaluated. You can declare more
|
||||
You can compose multiple router functions into one, such that they are evaluated in order,
|
||||
and, if the first route does not match, the second is evaluated. You can declare more
|
||||
specific routes before more general ones.
|
||||
|
||||
|
||||
|
@ -247,16 +291,17 @@ specific routes before more general ones.
|
|||
[[webflux-fn-routes]]
|
||||
=== Routes
|
||||
|
||||
You can compose multiple router functions together via:
|
||||
You can compose multiple router functions together by using:
|
||||
|
||||
* `RouterFunction.and(RouterFunction)`
|
||||
* `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -- shortcut for
|
||||
`RouterFunction.and()` with nested `RouterFunctions.route()`.
|
||||
|
||||
Using composed routes and predicates, we can then declare the following routes, referring
|
||||
to methods in the `PersonHandler`, shown in <<webflux-fn-handler-class>>, through
|
||||
to methods in the `PersonHandler` (shown in <<webflux-fn-handler-class>>) through
|
||||
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html[method-references]:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -271,45 +316,45 @@ RouterFunction<ServerResponse> personRoute =
|
|||
.andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
|
||||
.andRoute(POST("/person"), handler::createPerson);
|
||||
----
|
||||
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-running]]
|
||||
== Running a server
|
||||
== Running a Server
|
||||
|
||||
How do you run a router function in an HTTP server? A simple option is to convert a router
|
||||
function to an `HttpHandler` using one of the following:
|
||||
function to an `HttpHandler` by using one of the following:
|
||||
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction)`
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
|
||||
|
||||
The returned `HttpHandler` can then be used with a number of servers adapters by following
|
||||
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 advanced option 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 quired to process requests. The WebFlux Java config declares the following
|
||||
<<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, combines them via `RouterFunction.andOther`, and routes requests to the
|
||||
* `RouterFunctionMapping`: Detects one or more `RouterFunction<?>` beans in the Spring
|
||||
configuration, combines them through `RouterFunction.andOther`, and routes requests to the
|
||||
resulting composed `RouterFunction`.
|
||||
* `HandlerFunctionAdapter` -- simple adapter that allows the `DispatcherHandler` to invoke
|
||||
* `HandlerFunctionAdapter`: Simple adapter that lets `DispatcherHandler` invoke
|
||||
a `HandlerFunction` that was mapped to a request.
|
||||
* `ServerResponseResultHandler` -- handles the result from the invocation of a
|
||||
* `ServerResponseResultHandler`: Handles the result from the invocation of a
|
||||
`HandlerFunction` by invoking the `writeTo` method of the `ServerResponse`.
|
||||
|
||||
The above components allow functional endpoints to 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 the Spring Boot WebFlux
|
||||
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.
|
||||
|
||||
Below is example WebFlux Java config (see
|
||||
<<web-reactive.adoc#webflux-dispatcher-handler,DispatcherHandler>> for how to run):
|
||||
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"]
|
||||
----
|
||||
|
@ -345,23 +390,24 @@ public class WebConfig implements WebFluxConfigurer {
|
|||
}
|
||||
}
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-handler-filter-function]]
|
||||
== HandlerFilterFunction
|
||||
== Using `HandlerFilterFunction`
|
||||
|
||||
Routes mapped by a router function can be filtered by calling
|
||||
You can filter routes mapped by a router function by calling
|
||||
`RouterFunction.filter(HandlerFilterFunction)`, where `HandlerFilterFunction` is essentially 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
|
||||
`HandlerFunction` that is routed to, but can also be another `FilterFunction` if multiple filters
|
||||
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
|
||||
`HandlerFunction` that is routed to, but it can also be another `FilterFunction` if multiple filters
|
||||
are applied.
|
||||
With annotations, similar functionality can be achieved using `@ControllerAdvice` and/or a `ServletFilter`.
|
||||
Let's add a simple security filter to our route, assuming that we have a `SecurityManager` that
|
||||
can determine whether a particular path is allowed:
|
||||
With annotations, you can achieve similar functionality by using `@ControllerAdvice`, a `ServletFilter`, or both.
|
||||
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"]
|
||||
----
|
||||
|
@ -380,11 +426,9 @@ RouterFunction<ServerResponse> filteredRoute =
|
|||
}
|
||||
});
|
||||
----
|
||||
|
||||
You can see in this example that invoking the `next.handle(ServerRequest)` is optional: we only
|
||||
allow the handler function to be executed when access is allowed.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
CORS support for functional endpoints is provided via a dedicated <<webflux-cors-webfilter,`CorsWebFilter`>>.
|
||||
====
|
||||
|
||||
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional. We
|
||||
allow only the handler function to be executed when access is allowed.
|
||||
|
||||
NOTE: CORS support for functional endpoints is provided through a dedicated <<webflux-cors-webfilter,`CorsWebFilter`>>.
|
||||
|
|
|
@ -1,52 +1,51 @@
|
|||
[[webflux-view]]
|
||||
= View Technologies
|
||||
[.small]#<<web.adoc#mvc-view,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view,Same as in Spring MVC>>#
|
||||
|
||||
The use of view technologies in Spring WebFlux is pluggable, whether you decide to
|
||||
use Thymeleaf, FreeMarker, or other, is primarily a matter of a configuration change.
|
||||
This chapter covers view technologies integrated with Spring WebFlux. We assume you are
|
||||
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,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-thymeleaf,Same as in Spring MVC>>#
|
||||
|
||||
Thymeleaf is modern server-side Java template engine that emphasizes natural HTML
|
||||
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, e.g. by 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
|
||||
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
|
||||
http://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
|
||||
configuration involves a few bean declarations, such as
|
||||
`SpringResourceTemplateResolver`, `SpringWebFluxTemplateEngine`, and
|
||||
`ThymeleafReactiveViewResolver`. For more details see
|
||||
`ThymeleafReactiveViewResolver`. For more details, see
|
||||
http://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] and the WebFlux integration
|
||||
http://forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html[announcement].
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-view-freemarker]]
|
||||
== FreeMarker
|
||||
[.small]#<<web.adoc#mvc-view-freemarker,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-freemarker,Same as in Spring MVC>>#
|
||||
|
||||
http://www.freemarker.org[Apache FreeMarker] is a template engine for generating any
|
||||
kind of text output from HTML to email, and others. The Spring Framework has a built-in
|
||||
kind of text output from HTML to email and others. The Spring Framework has a built-in
|
||||
integration for using Spring WebFlux with FreeMarker templates.
|
||||
|
||||
|
||||
|
||||
[[webflux-view-freemarker-contextconfig]]
|
||||
=== View config
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig,Same in Spring MVC>>#
|
||||
=== View Configuration
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig,Same as in Spring MVC>>#
|
||||
|
||||
To configure FreeMarker as a view technology:
|
||||
The following example shows how to configure FreeMarker as a view technology:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -69,24 +68,26 @@ To configure FreeMarker as a view technology:
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer`
|
||||
shown above. Given the above configuration if your controller returns the view name
|
||||
"welcome" then the resolver will look for the
|
||||
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 config
|
||||
[.small]#<<web.adoc#mvc-views-freemarker,Same in Spring MVC>>#
|
||||
=== FreeMarker Configuration
|
||||
[.small]#<<web.adoc#mvc-views-freemarker,Same as in Spring MVC>>#
|
||||
|
||||
FreeMarker 'Settings' and 'SharedVariables' can be passed directly to the FreeMarker
|
||||
`Configuration` object managed by Spring by setting the appropriate bean properties on
|
||||
You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker
|
||||
`Configuration` object (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`.
|
||||
`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"]
|
||||
----
|
||||
|
@ -108,53 +109,53 @@ the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property requires a
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
See the FreeMarker documentation for details of settings and variables as they apply to
|
||||
the `Configuration` object.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-view-script]]
|
||||
== Script Views
|
||||
[.small]#<<web.adoc#mvc-view-script,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-script,Same as in Spring MVC>>#
|
||||
|
||||
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. Below is a list
|
||||
of templating libraries we've tested on different script engines:
|
||||
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:
|
||||
|
||||
[horizontal]
|
||||
http://handlebarsjs.com/[Handlebars] :: http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
https://mustache.github.io/[Mustache] :: http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
http://facebook.github.io/react/[React] :: http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
http://www.embeddedjs.com/[EJS] :: http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
http://www.stuartellis.eu/articles/erb/[ERB] :: http://jruby.org[JRuby]
|
||||
https://docs.python.org/2/library/string.html#template-strings[String templates] :: http://www.jython.org/[Jython]
|
||||
https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] :: http://kotlinlang.org/[Kotlin]
|
||||
[%header]
|
||||
|===
|
||||
|Scripting Library |Scripting Engine
|
||||
|http://handlebarsjs.com/[Handlebars] |http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|http://facebook.github.io/react/[React] |http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|http://www.embeddedjs.com/[EJS] |http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|http://www.stuartellis.eu/articles/erb/[ERB] |http://jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |http://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |http://kotlinlang.org/[Kotlin]
|
||||
|===
|
||||
|
||||
[TIP]
|
||||
====
|
||||
The basic rule for integrating any other script engine is that it must implement the
|
||||
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,Same in Spring MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-script-dependencies,Same as in Spring MVC>>#
|
||||
|
||||
You need to have the script engine on your classpath:
|
||||
You need to have the script engine on your classpath, the details of which vary by script engine:
|
||||
|
||||
* http://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
|
||||
* The http://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
|
||||
Java 8+. Using the latest update release available is highly recommended.
|
||||
* http://jruby.org[JRuby] should be added as a dependency for Ruby support.
|
||||
* http://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 details.
|
||||
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 http://www.webjars.org/[WebJars].
|
||||
|
@ -162,13 +163,14 @@ through http://www.webjars.org/[WebJars].
|
|||
|
||||
|
||||
[[webflux-view-script-integrate]]
|
||||
=== Script templates
|
||||
[.small]#<<web.adoc#mvc-view-script-integrate,Same in Spring MVC>>#
|
||||
=== Script Templates
|
||||
[.small]#<<web.adoc#mvc-view-script-integrate,Same as in Spring MVC>>#
|
||||
|
||||
Declare a `ScriptTemplateConfigurer` bean in order to specify the script engine to use,
|
||||
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.
|
||||
Below is an example with Mustache templates and the Nashorn JavaScript engine:
|
||||
The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -192,24 +194,27 @@ Below is an example with Mustache templates and the Nashorn JavaScript engine:
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The render function is called with the following parameters:
|
||||
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)
|
||||
* `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 may provide a script that
|
||||
If your templating technology requires some customization, you can provide a script that
|
||||
implements a custom render function. For example, http://handlebarsjs.com[Handlerbars]
|
||||
needs to compile templates before using them, and requires a
|
||||
needs to compile templates before using them and requires a
|
||||
http://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"]
|
||||
----
|
||||
|
@ -233,28 +238,31 @@ browser facilities not available in the server-side script engine.
|
|||
}
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Setting the `sharedEngine` property to `false` is required when using non thread-safe
|
||||
script engines with templating libraries not designed for concurrency, like Handlebars or
|
||||
React running on Nashorn for example. In that case, Java 8u60 or greater is required due
|
||||
|
||||
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 8u60 or greater is required, due
|
||||
to https://bugs.openjdk.java.net/browse/JDK-8076099[this bug].
|
||||
|
||||
`polyfill.js` defines only the `window` object needed by Handlebars to run properly,
|
||||
as the following snippet shows:
|
||||
|
||||
====
|
||||
|
||||
`polyfill.js` only defines the `window` object needed by Handlebars to run properly:
|
||||
|
||||
[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 / pre-compiled templates.
|
||||
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"]
|
||||
----
|
||||
|
@ -263,34 +271,31 @@ template engine configuration for example).
|
|||
return compiledTemplate(model);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Check out the Spring Framework unit tests,
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[java], and
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java], and
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script[resources],
|
||||
for more configuration examples.
|
||||
|
||||
|
||||
|
||||
[[webflux-view-httpmessagewriter]]
|
||||
== JSON, XML
|
||||
[.small]#<<web.adoc#mvc-view-jackson,Same in Spring MVC>>#
|
||||
== JSON and XML
|
||||
[.small]#<<web.adoc#mvc-view-jackson,Same as in Spring MVC>>#
|
||||
|
||||
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 this Spring WebFlux
|
||||
provides the `HttpMessageWriterView` that can be used to plug in any of the available
|
||||
<<webflux-codecs>> from `spring-web` such as `Jackson2JsonEncoder`,
|
||||
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 instead is <<webflux-config-view-resolvers,configured>> as a default view. You can
|
||||
configure one more such default views, wrapping different ``HttpMessageWriter``'s or
|
||||
``Encoder``'s. The one that matches the requested content type is used at runtime.
|
||||
|
||||
In most cases a model will contain multiple attributes. In order to determine which one
|
||||
to serialize, `HttpMessageWriterView` can be configured with the name of the model
|
||||
attribute to use render, of if the model contains only one attribute, it will be used.
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
|
|
@ -7,14 +7,13 @@ using a functional-style API that exposes Reactor `Flux` and `Mono` types, see
|
|||
<<web-reactive.adoc#webflux-codecs,codecs>> that WebFlux server applications use to work
|
||||
with request and response content.
|
||||
|
||||
Internally `WebClient` delegates to an HTTP client library. By default it uses
|
||||
Internally `WebClient` delegates to an HTTP client library. By default, it uses
|
||||
https://github.com/reactor/reactor-netty[Reactor Netty], there is built-in support for
|
||||
the Jetty https://github.com/jetty-project/jetty-reactive-httpclient[reactive HtpClient],
|
||||
and others can be plugged in through a `ClientHttpConnector`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-client-builder]]
|
||||
== Configuration
|
||||
|
||||
|
@ -23,22 +22,23 @@ The simplest way to create a `WebClient` is through one of the static factory me
|
|||
* `WebClient.create()`
|
||||
* `WebClient.create(String baseUrl)`
|
||||
|
||||
The above uses Reactor Netty `HttpClient` from "io.projectreactor.netty:reactor-netty"
|
||||
with default settings and participates in global resources such for event loop threads and
|
||||
a connection pool, see <<webflux-client-builder-reactor, Reactor Netty configuration>>.
|
||||
The preceding methods use Reactor Netty `HttpClient` from `io.projectreactor.netty:reactor-netty`
|
||||
with default settings and participates in global resources for event loop threads and
|
||||
a connection pool. See <<webflux-client-builder-reactor, Reactor Netty configuration>>.
|
||||
|
||||
The `WebClient.Builder` can be used for access to further options:
|
||||
You can use the `WebClient.Builder` for access to further options:
|
||||
|
||||
* `uriBuilderFactory` -- customized `UriBuilderFactory` to use as a base URL.
|
||||
* `defaultHeader` -- headers for every request.
|
||||
* `defaultCookie)` -- cookies for every request.
|
||||
* `defaultRequest` -- `Consumer` to customize every request.
|
||||
* `filter` -- client filter for every request.
|
||||
* `exchangeStrategies` -- HTTP message reader/writer customizations.
|
||||
* `clientConnector` -- HTTP client library settings.
|
||||
* `uriBuilderFactory`: Customized `UriBuilderFactory` to use as a base URL.
|
||||
* `defaultHeader`: Headers for every request.
|
||||
* `defaultCookie)`: Cookies for every request.
|
||||
* `defaultRequest`: `Consumer` to customize every request.
|
||||
* `filter`: Client filter for every request.
|
||||
* `exchangeStrategies`: HTTP message reader/writer customizations.
|
||||
* `clientConnector`: HTTP client library settings.
|
||||
|
||||
For example, to configure <<web-reactive.adoc#webflux-codecs,HTTP codecs>>:
|
||||
The following example configures <<web-reactive.adoc#webflux-codecs,HTTP codecs>>:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -52,10 +52,12 @@ For example, to configure <<web-reactive.adoc#webflux-codecs,HTTP codecs>>:
|
|||
.exchangeStrategies(strategies)
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
Once built a `WebClient` instance is immutable. However, you can clone it, and build a
|
||||
modified copy without affecting the original instance:
|
||||
Once built, a `WebClient` instance is immutable. However, you can clone it and build a
|
||||
modified copy without affecting the original instance, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -69,14 +71,16 @@ modified copy without affecting the original instance:
|
|||
|
||||
// client2 has filterA, filterB, filterC, filterD
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-builder-reactor]]
|
||||
=== Reactor Netty
|
||||
|
||||
To customize Reactor Netty settings:
|
||||
You can customize Reactor Netty settings:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -85,18 +89,21 @@ To customize Reactor Netty settings:
|
|||
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
====
|
||||
|
||||
By default `HttpClient` participates in the global Reactor Netty resources held in
|
||||
By default, `HttpClient` participates in the global Reactor Netty resources held in
|
||||
`reactor.netty.http.HttpResources`, including event loop threads and a connection pool.
|
||||
This is the recommended mode since fixed, shared resources are preferred for event loop
|
||||
This is the recommended mode, since fixed, shared resources are preferred for event loop
|
||||
concurrency. In this mode global resources remain active until the process exits.
|
||||
|
||||
If the server is timed with the process, there is typically no need for an explicit
|
||||
shutdown. However if the server can start or stop in-process, e.g. Spring MVC
|
||||
application deployed as a WAR, you can declare a Spring-managed bean of type
|
||||
`ReactorResourceFactory` with `useGlobalResources=true` (the default) to ensure the Reactor
|
||||
Netty global resources are shut down when the Spring `ApplicationContext` is closed:
|
||||
shutdown. However, if the server can start or stop in-process (for example, a Spring MVC
|
||||
application deployed as a WAR), you can declare a Spring-managed bean of type
|
||||
`ReactorResourceFactory` with `globalResources=true` (the default) to ensure that the Reactor
|
||||
Netty global resources are shut down when the Spring `ApplicationContext` is closed,
|
||||
as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -105,11 +112,13 @@ Netty global resources are shut down when the Spring `ApplicationContext` is clo
|
|||
return new ReactorResourceFactory();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You may also choose not to participate in the global Reactor Netty resources. However keep
|
||||
in mind in this mode the burden is on you to ensure all Reactor Netty client and server
|
||||
instances use shared resources:
|
||||
You can also choose not to participate in the global Reactor Netty resources. However,
|
||||
in this mode, the burden is on you to ensure that all Reactor Netty client and server
|
||||
instances use shared resources, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -134,15 +143,18 @@ instances use shared resources:
|
|||
}
|
||||
----
|
||||
<1> Create resources independent of global ones.
|
||||
<2> Use `ReactorClientHttpConnector` constructor with resource factory.
|
||||
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
|
||||
<3> Plug the connector into the `WebClient.Builder`.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-builder-jetty]]
|
||||
=== Jetty
|
||||
|
||||
To customize Jetty `HttpClient` settings:
|
||||
The following example shows how to customize Jetty `HttpClient` settings:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -152,14 +164,16 @@ To customize Jetty `HttpClient` settings:
|
|||
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
====
|
||||
|
||||
By default `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`)
|
||||
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
|
||||
which remain active until the process exits or `stop()` is called.
|
||||
|
||||
You can share resources between multiple intances of Jetty client (and server) and ensure the
|
||||
You can share resources between multiple instances of the Jetty client (and server) and ensure that the
|
||||
resources are shut down when the Spring `ApplicationContext` is closed by declaring a
|
||||
Spring-managed bean of type `JettyResourceFactory`:
|
||||
Spring-managed bean of type `JettyResourceFactory`, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -183,16 +197,19 @@ Spring-managed bean of type `JettyResourceFactory`:
|
|||
----
|
||||
|
||||
<1> Create shared resources.
|
||||
<2> Use `JettyClientHttpConnector` constructor with resource factory.
|
||||
<2> Use the `JettyClientHttpConnector` constructor with resource factory.
|
||||
<3> Plug the connector into the `WebClient.Builder`.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-retrieve]]
|
||||
== Retrieve
|
||||
== Using the `retrieve` Method
|
||||
|
||||
The `retrieve()` method is the easiest way to get a response body and decode it:
|
||||
The `retrieve()` method is the easiest way to get a response body and decode it.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -203,9 +220,11 @@ The `retrieve()` method is the easiest way to get a response body and decode it:
|
|||
.retrieve()
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
====
|
||||
|
||||
You can also get a stream of objects decoded from the response:
|
||||
You can also get a stream of objects decoded from the response, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -214,12 +233,15 @@ You can also get a stream of objects decoded from the response:
|
|||
.retrieve()
|
||||
.bodyToFlux(Quote.class);
|
||||
----
|
||||
====
|
||||
|
||||
By default, responses with 4xx or 5xx status codes result in an
|
||||
`WebClientResponseException` or one of its HTTP status specific sub-classes such as
|
||||
`WebClientResponseException` or one of its HTTP status specific sub-classes, such as
|
||||
`WebClientResponseException.BadRequest`, `WebClientResponseException.NotFound`, and others.
|
||||
You can also use the `onStatus` method to customize the resulting exception:
|
||||
You can also use the `onStatus` method to customize the resulting exception,
|
||||
as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -230,16 +252,17 @@ You can also use the `onStatus` method to customize the resulting exception:
|
|||
.onStatus(HttpStatus::is5xxServerError, response -> ...)
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-exchange]]
|
||||
== Exchange
|
||||
== Using the `exchange` Method
|
||||
|
||||
The `exchange()` method provides more control. The below example is equivalent
|
||||
The `exchange()` method provides more control than the `retrieve` method. The following example is equivalent
|
||||
to `retrieve()` but also provides access to the `ClientResponse`:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -248,9 +271,11 @@ to `retrieve()` but also provides access to the `ClientResponse`:
|
|||
.exchange()
|
||||
.flatMap(response -> response.bodyToMono(Person.class));
|
||||
----
|
||||
====
|
||||
|
||||
At this level you can also create a full `ResponseEntity`:
|
||||
At this level, you can also create a full `ResponseEntity`:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -259,27 +284,25 @@ At this level you can also create a full `ResponseEntity`:
|
|||
.exchange()
|
||||
.flatMap(response -> response.toEntity(Person.class));
|
||||
----
|
||||
====
|
||||
|
||||
Note that unlike `retrieve()`, with `exchange()` there are no automatic error signals for
|
||||
Note that (unlike `retrieve()`), with `exchange()`, there are no automatic error signals for
|
||||
4xx and 5xx responses. You have to check the status code and decide how to proceed.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
When using `exchange()` you must always use any of the body or toEntity methods of
|
||||
CAUTION: When you use `exchange()`, you must always use any of the `body` or `toEntity` methods of
|
||||
`ClientResponse` to ensure resources are released and to avoid potential issues with HTTP
|
||||
connection pooling. You can use `bodyToMono(Void.class)` if no response content is
|
||||
expected. However keep in mind that if the response does have content, the connection
|
||||
will be closed and will not be placed back in the pool.
|
||||
====
|
||||
|
||||
expected. However, if the response does have content, the connection
|
||||
is closed and is not placed back in the pool.
|
||||
|
||||
|
||||
|
||||
[[webflux-client-body]]
|
||||
== Request body
|
||||
== Request Body
|
||||
|
||||
The request body can be encoded from an Object:
|
||||
The request body can be encoded from an `Object`, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -292,9 +315,11 @@ The request body can be encoded from an Object:
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
====
|
||||
|
||||
You can also have a stream of objects encoded:
|
||||
You can also have a stream of objects be encoded, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -307,9 +332,12 @@ You can also have a stream of objects encoded:
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
====
|
||||
|
||||
Or if you have the actual value, use the `syncBody` shortcut method:
|
||||
Alternatively, if you have the actual value, you can use the `syncBody` shortcut method,
|
||||
as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -322,16 +350,18 @@ Or if you have the actual value, use the `syncBody` shortcut method:
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-body-form]]
|
||||
=== Form data
|
||||
=== Form Data
|
||||
|
||||
To send form data, provide a `MultiValueMap<String, String>` as the body. Note that the
|
||||
content is automatically set to `"application/x-www-form-urlencoded"` by the
|
||||
`FormHttpMessageWriter`:
|
||||
To send form data, you can provide a `MultiValueMap<String, String>` as the body. Note that the
|
||||
content is automatically set to `application/x-www-form-urlencoded` by the
|
||||
`FormHttpMessageWriter`. The following example shows how to use `MultiValueMap<String, String>`:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -343,9 +373,11 @@ content is automatically set to `"application/x-www-form-urlencoded"` by the
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
====
|
||||
|
||||
You can also supply form data in-line via `BodyInserters`:
|
||||
You can also supply form data in-line by using `BodyInserters`, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -357,16 +389,17 @@ You can also supply form data in-line via `BodyInserters`:
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-body-multipart]]
|
||||
=== Multipart data
|
||||
=== Multipart Data
|
||||
|
||||
To send multipart data, you need to provide a `MultiValueMap<String, ?>` whose values are
|
||||
either Objects representing part content, or `HttpEntity` representing the content and
|
||||
either `Object` instances that represent part content or `HttpEntity` instances that represent the content and
|
||||
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
|
||||
multipart request:
|
||||
multipart request. The following example shows how to create a `MultiValueMap<String, ?>`:
|
||||
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
@ -379,15 +412,16 @@ multipart request:
|
|||
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
|
||||
----
|
||||
|
||||
In most cases you do not have to specify the `Content-Type` for each part. The content
|
||||
type is determined automatically based on the `HttpMessageWriter` chosen to serialize it,
|
||||
or in the case of a `Resource` based on the file extension. If necessary you can
|
||||
explicitly provide the `MediaType` to use for each part through one fo the overloaded
|
||||
In most cases, you do not have to specify the `Content-Type` for each part. The content
|
||||
type is determined automatically based on the `HttpMessageWriter` chosen to serialize it
|
||||
or, in the case of a `Resource`, based on the file extension. If necessary, you can
|
||||
explicitly provide the `MediaType` to use for each part through one of the overloaded
|
||||
builder `part` methods.
|
||||
|
||||
Once a `MultiValueMap` is prepared, the easiest way to pass it to the the `WebClient` is
|
||||
through the `syncBody` method:
|
||||
through the `syncBody` method, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -399,15 +433,17 @@ through the `syncBody` method:
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
====
|
||||
|
||||
If the `MultiValueMap` contains at least one non-String value, which could also be
|
||||
represent regular form data (i.e. "application/x-www-form-urlencoded"), you don't have to
|
||||
set the `Content-Type` to "multipart/form-data". This is always the case when using
|
||||
`MultipartBodyBuilder` which ensures an `HttpEntity` wrapper.
|
||||
If the `MultiValueMap` contains at least one non-`String` value, which could also
|
||||
represent regular form data (that is, `application/x-www-form-urlencoded`), you need not
|
||||
set the `Content-Type` to `multipart/form-data`. This is always the case when using
|
||||
`MultipartBodyBuilder`, which ensures an `HttpEntity` wrapper.
|
||||
|
||||
As an alternative to `MultipartBodyBuilder`, you can also provide multipart content,
|
||||
inline-style, through the built-in `BodyInserters`. For example:
|
||||
inline-style, through the built-in `BodyInserters`, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -419,7 +455,7 @@ inline-style, through the built-in `BodyInserters`. For example:
|
|||
.retrieve()
|
||||
.bodyToMono(Void.class);
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
@ -427,8 +463,9 @@ inline-style, through the built-in `BodyInserters`. For example:
|
|||
== Client Filters
|
||||
|
||||
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
|
||||
in order to intercept and/or modify requests:
|
||||
in order to intercept and modify requests, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -443,10 +480,12 @@ WebClient client = WebClient.builder()
|
|||
})
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
This can be used for cross-cutting concerns such as authentication. The example below uses
|
||||
This can be used for cross-cutting concerns, such as authentication. The following example uses
|
||||
a filter for basic authentication through a static factory method:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -457,11 +496,13 @@ WebClient client = WebClient.builder()
|
|||
.filter(basicAuthentication("user", "password"))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
Filters apply globally to every request. To change how a filter's behavior for a specific
|
||||
Filters apply globally to every request. To change a filter's behavior for a specific
|
||||
request, you can add request attributes to the `ClientRequest` that can then be accessed
|
||||
by all filters in the chain:
|
||||
by all filters in the chain, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -479,11 +520,13 @@ client.get().uri("http://example.org/")
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can also replicate an existing `WebClient`, and insert new filters or remove already
|
||||
registered filters. In the example below, a basic authentication filter is inserted at
|
||||
You can also replicate an existing `WebClient`, insert new filters, or remove already
|
||||
registered filters. The following example, inserts a basic authentication filter at
|
||||
index 0:
|
||||
|
||||
====
|
||||
[source,java,intent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -496,17 +539,17 @@ WebClient client = webClient.mutate()
|
|||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-client-testing]]
|
||||
== Testing
|
||||
|
||||
To test code that uses the `WebClient`, you can use a mock web server such as the
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see example
|
||||
use, check
|
||||
https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[WebClientIntegrationTests]
|
||||
in the Spring Framework tests, or the
|
||||
https://github.com/square/okhttp/tree/master/samples/static-server[static-server]
|
||||
To test code that uses the `WebClient`, you can use a mock web server, such as the
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see an example
|
||||
of its use, check
|
||||
https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[`WebClientIntegrationTests`]
|
||||
in the Spring Framework tests or the
|
||||
https://github.com/square/okhttp/tree/master/samples/static-server[`static-server`]
|
||||
sample in the OkHttp repository.
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
[[webflux-websocket]]
|
||||
= WebSockets
|
||||
[.small]#<<web.adoc#websocket,Same in Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket,Same as in the Servlet stack>>#
|
||||
|
||||
This part of the reference documentation covers support for Reactive stack, WebSocket
|
||||
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,Same in Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server,Same as in the Servlet stack>>#
|
||||
|
||||
The Spring Framework provides a WebSocket API that can be used to write client and
|
||||
server side applications that handle WebSocket messages.
|
||||
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,Same in Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-handler,Same as in the Servlet stack>>#
|
||||
|
||||
To create a WebSocket server, first create a `WebSocketHandler`:
|
||||
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"]
|
||||
----
|
||||
|
@ -39,9 +42,11 @@ To create a WebSocket server, first create a `WebSocketHandler`:
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Then map it to a URL and add a `WebSocketHandlerAdapter`:
|
||||
Then you can map it to a URL and add a `WebSocketHandlerAdapter`, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -65,22 +70,24 @@ Then map it to a URL and add a `WebSocketHandlerAdapter`:
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-websockethandler]]
|
||||
=== WebSocketHandler
|
||||
=== Using `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:
|
||||
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
|
||||
| `WebSocketSession` method | Description
|
||||
|
||||
| `Flux<WebSocketMessage> receive()`
|
||||
| Provides access to the inbound message stream, and completes when the connection is closed.
|
||||
| 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
|
||||
|
@ -88,21 +95,23 @@ through two streams, one for inbound and one for outbound messages:
|
|||
|
||||
|===
|
||||
|
||||
A `WebSocketHandler` must compose the inbound and outbound streams into a unified flow, and
|
||||
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 inbound or outbound message streams complete.
|
||||
* Inbound stream completes (i.e. connection closed), while outbound is infinite.
|
||||
* At a chosen point through the `close` method of `WebSocketSession`.
|
||||
* 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 will terminate activity.
|
||||
The inbound stream receives a completion/error signal, and the outbound stream receives
|
||||
check if the connection is open, since Reactive Streams signals terminate 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 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"]
|
||||
----
|
||||
|
@ -121,21 +130,20 @@ class ExampleHandler implements WebSocketHandler {
|
|||
}
|
||||
}
|
||||
----
|
||||
<1> Access stream of inbound messages.
|
||||
<1> Access the stream of inbound messages.
|
||||
<2> Do something with each message.
|
||||
<3> Perform nested async operation using message content.
|
||||
<4> Return `Mono<Void>` that completes when receiving completes.
|
||||
|
||||
[TIP]
|
||||
<3> Perform nested asynchronous operations that use the message content.
|
||||
<4> Return a `Mono<Void>` that completes when receiving completes.
|
||||
====
|
||||
For nested, asynchronous operations, you may need to call `message.retain()` on underlying
|
||||
servers that use pooled data buffers (e.g. Netty), or otherwise the data buffer may be
|
||||
released before you've had a chance to read the data. For more background see
|
||||
|
||||
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:
|
||||
|
||||
====
|
||||
|
||||
The below implementation combines the inbound with the outbound streams:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -157,12 +165,15 @@ class ExampleHandler implements WebSocketHandler {
|
|||
}
|
||||
}
|
||||
----
|
||||
<1> Handle inbound message stream.
|
||||
<2> Create outbound message, producing a combined flow.
|
||||
<3> Return `Mono<Void>` that doesn't complete while we continue to receive.
|
||||
<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 joined only for completion:
|
||||
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"]
|
||||
----
|
||||
|
@ -189,17 +200,18 @@ class ExampleHandler implements WebSocketHandler {
|
|||
----
|
||||
<1> Handle inbound message stream.
|
||||
<2> Send outgoing messages.
|
||||
<3> Join the streams and return `Mono<Void>` that completes when _either_ stream ends.
|
||||
<3> Join the streams and return a `Mono<Void>` that completes when either stream ends.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[webflux-websocket-server-handshake]]
|
||||
=== Handshake
|
||||
[.small]#<<web.adoc#websocket-server-handshake,Same in Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-handshake,Same as in the Servlet stack>>#
|
||||
|
||||
`WebSocketHandlerAdapter` delegates to a `WebSocketService`. By default that's an instance
|
||||
`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
|
||||
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
|
||||
|
@ -208,15 +220,15 @@ into the attributes of the `WebSocketSession`.
|
|||
|
||||
|
||||
|
||||
|
||||
[[webflux-websocket-server-config]]
|
||||
=== Server config
|
||||
[.small]#<<web.adoc#websocket-server-runtime-configuration,Same in Servlet stack>>#
|
||||
=== Server Configation
|
||||
[.small]#<<web.adoc#websocket-server-runtime-configuration,Same as in the Servlet stack>>#
|
||||
|
||||
The `RequestUpgradeStrategy` for each server exposes WebSocket-related configuration
|
||||
options available for the underlying WebSocket engine. Below is an example of setting
|
||||
options available for the underlying WebSocket engine. The following example sets
|
||||
WebSocket options when running on Tomcat:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -236,21 +248,22 @@ WebSocket options when running on Tomcat:
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Check the upgrade strategy for your server to see what options are available. Currently
|
||||
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,Same in Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-allowed-origins,Same as 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
|
||||
`CorsConfiguraiton` with allowed origins, headers, etc. If for any reason you can't do
|
||||
`CorsConfiguraiton` 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're combined via the
|
||||
specify CORS settings by URL pattern. If both are specified, they are combined by using the
|
||||
`combine` method on `CorsConfiguration`.
|
||||
|
||||
|
||||
|
@ -259,18 +272,16 @@ specify CORS settings by URL pattern. If both are specified they're combined via
|
|||
=== Client
|
||||
|
||||
Spring WebFlux provides a `WebSocketClient` abstraction with implementations for
|
||||
Reactor Netty, Tomcat, Jetty, Undertow, and standard Java (i.e. JSR-356).
|
||||
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 taking advantage of Tomcat specific
|
||||
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, create an instance of the client and use its `execute`
|
||||
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"]
|
||||
----
|
||||
|
@ -282,7 +293,8 @@ client.execute(url, session ->
|
|||
.doOnNext(System.out::println)
|
||||
.then());
|
||||
----
|
||||
====
|
||||
|
||||
Some clients, e.g. Jetty, implement `Lifecycle` and need to be started in stopped
|
||||
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
|
@ -5,40 +5,35 @@ This section describes options for client-side access to REST endpoints.
|
|||
|
||||
|
||||
|
||||
|
||||
[[webmvc-resttemplate]]
|
||||
== RestTemplate
|
||||
== Using `RestTemplate`
|
||||
|
||||
`RestTemplate` is a synchronous client to perform HTTP requests. It is the original
|
||||
Spring REST client, exposing a simple, template method API over underlying HTTP client
|
||||
Spring REST client and exposes a simple, template-method API over underlying HTTP client
|
||||
libraries.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 5.0, the non-blocking, reactive `WebClient` offers a modern alternative to the
|
||||
`RestTemplate` with efficient support for both sync and async, as well as streaming
|
||||
NOTE: As of 5.0, the non-blocking, reactive `WebClient` offers a modern alternative to the
|
||||
`RestTemplate`, with efficient support for both synchronous and asynchronous, as well as streaming
|
||||
scenarios. The `RestTemplate` will be deprecated in a future version and will not have
|
||||
major new features added going forward.
|
||||
====
|
||||
|
||||
See <<integration.adoc#rest-client-access,RestTemplate>> for details.
|
||||
|
||||
See <<integration.adoc#rest-client-access,REST Endpoints>> for details.
|
||||
|
||||
|
||||
|
||||
[[webmvc-webclient]]
|
||||
== WebClient
|
||||
== Using `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
|
||||
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 the `RestTemplate`, the `WebClient` supports the following:
|
||||
In contrast to `RestTemplate`, `WebClient` supports the following:
|
||||
|
||||
* Non-blocking I/O.
|
||||
* Reactive Streams back pressure.
|
||||
* High concurrency with less hardware resources.
|
||||
* Functional-style, fluent API taking advantage of Java 8 lambdas.
|
||||
* 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.
|
||||
|
||||
|
|
|
@ -1,68 +1,69 @@
|
|||
[[mvc-cors]]
|
||||
= CORS
|
||||
[.small]#<<web-reactive.adoc#webflux-cors,Same in Spring WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors,Same as in Spring WebFlux>>#
|
||||
|
||||
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,Same in Spring WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-intro,Same as in Spring WebFlux>>#
|
||||
|
||||
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
|
||||
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, e.g. withdrawing money from your account!
|
||||
credentials -- for example withdrawing money from your account!
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) is a http://www.w3.org/TR/cors/[W3C specification]
|
||||
implemented by http://caniuse.com/#feat=cors[most browsers] that allows you to specify
|
||||
what kind of cross domain requests are authorized rather than using less secure and less
|
||||
implemented by http://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,Same in Spring WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-processing,Same as in Spring WebFlux>>#
|
||||
|
||||
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 refer to the specification for more details.
|
||||
many others, or see the specification for more details.
|
||||
|
||||
Spring MVC ``HandlerMapping``'s provide built-in support for CORS. After successfully
|
||||
mapping a request to a handler, ``HandlerMapping``'s check the CORS configuration for the
|
||||
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
|
||||
directly, while simple and actual CORS requests are intercepted, validated, and have
|
||||
required CORS response headers set.
|
||||
|
||||
In order to enable cross-origin requests (i.e. the `Origin` header is present and
|
||||
differs from the host of the request) you need to have some explicitly declared CORS
|
||||
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.
|
||||
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
|
||||
will use the MVC Java config or the XML namespace to declare such mappings, which results
|
||||
in a single, global map passed to all ``HadlerMappping``'s.
|
||||
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 `HandlerMappping` instances.
|
||||
|
||||
Global CORS configuration at the `HandlerMapping` level can be combined with more
|
||||
fine-grained, handler-level CORS configuration. For example annotated controllers can use
|
||||
class or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
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 -- e.g.
|
||||
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
|
||||
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 make advanced customizations, check:
|
||||
To learn more from the source or make advanced customizations, check the code behind:
|
||||
|
||||
* `CorsConfiguration`
|
||||
* `CorsProcessor`, `DefaultCorsProcessor`
|
||||
|
@ -71,14 +72,15 @@ To learn more from the source or make advanced customizations, check:
|
|||
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-controller]]
|
||||
== @CrossOrigin
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-controller,Same in Spring WebFlux>>#
|
||||
== Using `@CrossOrigin`
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-controller,Same as in Spring WebFlux>>#
|
||||
|
||||
The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
|
||||
annotation enables cross-origin requests on annotated controller methods:
|
||||
annotation enables cross-origin requests on annotated controller methods,
|
||||
as the following example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -98,19 +100,24 @@ public class AccountController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
By default `@CrossOrigin` allows:
|
||||
By default, `@CrossOrigin` allows:
|
||||
|
||||
* All origins.
|
||||
* All headers.
|
||||
* All HTTP methods to which the controller method is mapped.
|
||||
* `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
|
||||
|
||||
`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 only be used where appropriate.
|
||||
* `maxAge` is set to 30 minutes.
|
||||
|
||||
`@CrossOrigin` is supported at the class level too and inherited by all methods:
|
||||
`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"]
|
||||
----
|
||||
|
@ -130,9 +137,12 @@ public class AccountController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
`CrossOrigin` can be used at both class and method-level:
|
||||
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"]
|
||||
----
|
||||
|
@ -153,38 +163,43 @@ public class AccountController {
|
|||
}
|
||||
}
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-global]]
|
||||
== Global Config
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global,Same in Spring WebFlux>>#
|
||||
== Global Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global,Same as in Spring WebFlux>>#
|
||||
|
||||
In addition to fine-grained, controller method level configuration you'll probably want to
|
||||
define some global CORS configuration too. You can set URL-based `CorsConfiguration`
|
||||
mappings individually on any `HandlerMapping`. Most applications however will use the
|
||||
MVC Java config or the MVC XNM namespace to do that.
|
||||
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 XNM namespace to do that.
|
||||
|
||||
By default global configuration enables the following:
|
||||
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
|
||||
|
||||
|
||||
`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 only be used where appropriate.
|
||||
* `maxAge` is set to 30 minutes.
|
||||
|
||||
`maxAge` is set to 30 minutes.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-global-java]]
|
||||
=== Java Config
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global,Same in Spring WebFlux>>#
|
||||
=== Java Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global,Same as in Spring WebFlux>>#
|
||||
|
||||
To enable CORS in the MVC Java config, use the `CorsRegistry` callback:
|
||||
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"]
|
||||
----
|
||||
|
@ -206,14 +221,17 @@ public class WebConfig implements WebMvcConfigurer {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-global-xml]]
|
||||
=== XML Config
|
||||
=== XML Configuration
|
||||
|
||||
To enable CORS in the XML namespace, use the `<mvc:cors>` element:
|
||||
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"]
|
||||
----
|
||||
|
@ -231,28 +249,26 @@ To enable CORS in the XML namespace, use the `<mvc:cors>` element:
|
|||
|
||||
</mvc:cors>
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-filter]]
|
||||
== CORS Filter
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-webfilter,Same in Spring WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-webfilter,Same as in Spring WebFlux>>#
|
||||
|
||||
You can apply CORS support through the built-in
|
||||
{api-spring-framework}/web/filter/CorsFilter.html[`CorsFilter`].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If you're trying to use the `CorsFilter` with Spring Security, keep in mind that Spring
|
||||
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
|
||||
Security has
|
||||
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors[built-in support]
|
||||
for CORS.
|
||||
|
||||
To configure the filter, pass a
|
||||
`CorsConfigurationSource` to its constructor, as the following example shows:
|
||||
|
||||
====
|
||||
|
||||
To configure the filter pass a
|
||||
`CorsConfigurationSource` to its constructor:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
|
@ -271,4 +287,4 @@ source.registerCorsConfiguration("/**", config);
|
|||
|
||||
CorsFilter filter = new CorsFilter(source);
|
||||
----
|
||||
|
||||
====
|
||||
|
|
|
@ -4,35 +4,25 @@
|
|||
|
||||
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
|
||||
* 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
|
||||
* 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`, i.e. supporting annotations and complete with Spring MVC
|
||||
infrastructure, but without an HTTP server. See
|
||||
* 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 can be used as a mock server for
|
||||
* 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**
|
||||
|
||||
`WebTestClient` was built for testing WebFlux applications but it can also be used for
|
||||
* `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 well suited for testing asynchronous and streaming
|
||||
non-blocking, reactive client and is well suited for testing asynchronous and streaming
|
||||
scenarios.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +1,35 @@
|
|||
[[websocket-intro]]
|
||||
= Introduction
|
||||
= Introduction to WebSocket
|
||||
|
||||
The WebSocket protocol http://tools.ietf.org/html/rfc6455[RFC 6455] provides a standardized
|
||||
The WebSocket protocol, http://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:
|
||||
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:
|
||||
|
||||
====
|
||||
[subs="quotes"]
|
||||
----
|
||||
GET /spring-websocket-portfolio/portfolio HTTP/1.1
|
||||
Host: localhost:8080
|
||||
**Upgrade: websocket**
|
||||
**Connection: Upgrade**
|
||||
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:
|
||||
Instead of the usual 200 status code, a server with WebSocket support returns output
|
||||
similar to the following:
|
||||
|
||||
====
|
||||
[subs="quotes"]
|
||||
----
|
||||
**HTTP/1.1 101 Switching Protocols**
|
||||
|
@ -31,65 +38,65 @@ Connection: Upgrade
|
|||
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
|
||||
Sec-WebSocket-Protocol: v10.stomp
|
||||
----
|
||||
====
|
||||
|
||||
After a successful handshake the TCP socket underlying the HTTP upgrade request remains
|
||||
open for both client and server to continue to send and receive messages.
|
||||
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.
|
||||
Please read RFC 6455, the WebSocket chapter of HTML5, or one of many introductions and
|
||||
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 will
|
||||
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
|
||||
server. Likewise, if the application runs in a cloud environment, check the
|
||||
instructions of the cloud provider related to WebSocket support.
|
||||
|
||||
|
||||
|
||||
|
||||
[[websocket-intro-architecture]]
|
||||
== HTTP vs WebSocket
|
||||
== HTTP Versus WebSocket
|
||||
|
||||
Even though WebSocket is designed to be HTTP compatible and starts with an HTTP request,
|
||||
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
|
||||
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 just one URL for the initial connect and
|
||||
subsequently all application messages flow on that same TCP connection. This points to
|
||||
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 there is no way to route or process
|
||||
a message unless client and server agree on message semantics.
|
||||
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
|
||||
(e.g. STOMP), via the `"Sec-WebSocket-Protocol"` header on the HTTP handshake request,
|
||||
or in the absence of that they need to come up with their own conventions.
|
||||
(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.
|
||||
|
||||
|
||||
|
||||
[[websocket-intro-when-to-use]]
|
||||
== When to use it?
|
||||
== When to Use WebSockets
|
||||
|
||||
WebSockets can make a web page dynamic and interactive. However in many cases
|
||||
a combination of Ajax and HTTP streaming and/or long polling could provide a simple and
|
||||
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.
|
||||
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 (e.g.
|
||||
monitoring network failures) HTTP streaming or polling may provide an effective solution.
|
||||
It is the combination of low latency, high frequency and high volume that make the best
|
||||
case for the use WebSocket.
|
||||
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 outside 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
|
||||
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
|
||||
straight-forward decision than it is for public facing applications.
|
||||
straightforward decision than it is for public facing applications.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue