Structural Fixes
This commit is contained in:
@@ -558,6 +558,3 @@ Therefore, it has limited namespace support. As a result, it is rather unsuitabl
|
||||
within Web Services.
|
||||
|
||||
|
||||
|
||||
|
||||
include:../:data-access/appendix.adoc[leveloffset=+1]
|
||||
|
||||
@@ -39,7 +39,7 @@ Version `1.4.0` and above are supported.
|
||||
|
||||
|
||||
|
||||
[[how-reactive-translates-to-coroutines?]]
|
||||
[[how-reactive-translates-to-coroutines]]
|
||||
== How Reactive translates to Coroutines?
|
||||
|
||||
For return values, the translation from Reactive to Coroutines APIs is the following:
|
||||
|
||||
@@ -9,65 +9,3 @@ the reactive xref:web/webflux-webclient.adoc[`WebClient`], support for xref:web-
|
||||
and xref:web-reactive.adoc#webflux-reactive-libraries[reactive libraries]. For Servlet-stack web applications,
|
||||
see xref:web.adoc[Web on Servlet Stack].
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-http-interface-client]]
|
||||
== HTTP Interface Client
|
||||
|
||||
The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
|
||||
exchange methods. You can then generate a proxy that implements this interface and
|
||||
performs the exchanges. This helps to simplify HTTP remote access and provides additional
|
||||
flexibility for to choose an API style such as synchronous or reactive.
|
||||
|
||||
See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-test]]
|
||||
== Testing
|
||||
[.small]#xref:web/webmvc-test.adoc[Same in Spring MVC]#
|
||||
|
||||
The `spring-test` module provides mock implementations of `ServerHttpRequest`,
|
||||
`ServerHttpResponse`, and `ServerWebExchange`.
|
||||
See xref:testing/unit.adoc#mock-objects-web-reactive[Spring Web Reactive] for a
|
||||
discussion of mock objects.
|
||||
|
||||
xref:testing/webtestclient.adoc[`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-reactive-libraries]]
|
||||
== 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 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
|
||||
provides pluggable support for reactive library and other asynchronous types. The registry
|
||||
has built-in support for RxJava 3, Kotlin coroutines and SmallRye Mutiny, 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 a Reactive Streams
|
||||
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
|
||||
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`.
|
||||
|
||||
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 (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are buffered into a list and rendered as a JSON array.
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
In the context of web applications, _data binding_ involves the binding of HTTP request
|
||||
parameters (that is, form data or query parameters) to properties in a model object and
|
||||
its nested objects.
|
||||
|
||||
Only `public` properties following the
|
||||
https://www.oracle.com/java/technologies/javase/javabeans-spec.html[JavaBeans naming conventions]
|
||||
are exposed for data binding — for example, `public String getFirstName()` and
|
||||
`public void setFirstName(String)` methods for a `firstName` property.
|
||||
|
||||
TIP: The model object, and its nested object graph, is also sometimes referred to as a
|
||||
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
|
||||
|
||||
By default, Spring permits binding to all public properties in the model object graph.
|
||||
This means you need to carefully consider what public properties the model has, since a
|
||||
client could target any public property path, even some that are not expected to be
|
||||
targeted for a given use case.
|
||||
|
||||
For example, given an HTTP form data endpoint, a malicious client could supply values for
|
||||
properties that exist in the model object graph but are not part of the HTML form
|
||||
presented in the browser. This could lead to data being set on the model object and any
|
||||
of its nested objects, that is not expected to be updated.
|
||||
|
||||
The recommended approach is to use a _dedicated model object_ that exposes only
|
||||
properties that are relevant for the form submission. For example, on a form for changing
|
||||
a user's email address, the model object should declare a minimum set of properties such
|
||||
as in the following `ChangeEmailForm`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class ChangeEmailForm {
|
||||
|
||||
private String oldEmailAddress;
|
||||
private String newEmailAddress;
|
||||
|
||||
public void setOldEmailAddress(String oldEmailAddress) {
|
||||
this.oldEmailAddress = oldEmailAddress;
|
||||
}
|
||||
|
||||
public String getOldEmailAddress() {
|
||||
return this.oldEmailAddress;
|
||||
}
|
||||
|
||||
public void setNewEmailAddress(String newEmailAddress) {
|
||||
this.newEmailAddress = newEmailAddress;
|
||||
}
|
||||
|
||||
public String getNewEmailAddress() {
|
||||
return this.newEmailAddress;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
If you cannot or do not want to use a _dedicated model object_ for each data
|
||||
binding use case, you **must** limit the properties that are allowed for data binding.
|
||||
Ideally, you can achieve this by registering _allowed field patterns_ via the
|
||||
`setAllowedFields()` method on `WebDataBinder`.
|
||||
|
||||
For example, to register allowed field patterns in your application, you can implement an
|
||||
`@InitBinder` method in a `@Controller` or `@ControllerAdvice` component as shown below:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class ChangeEmailController {
|
||||
|
||||
@InitBinder
|
||||
void initBinder(WebDataBinder binder) {
|
||||
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
|
||||
}
|
||||
|
||||
// @RequestMapping methods, etc.
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
In addition to registering allowed patterns, it is also possible to register _disallowed
|
||||
field patterns_ via the `setDisallowedFields()` method in `DataBinder` and its subclasses.
|
||||
Please note, however, that an "allow list" is safer than a "deny list". Consequently,
|
||||
`setAllowedFields()` should be favored over `setDisallowedFields()`.
|
||||
|
||||
Note that matching against allowed field patterns is case-sensitive; whereas, matching
|
||||
against disallowed field patterns is case-insensitive. In addition, a field matching a
|
||||
disallowed pattern will not be accepted even if it also happens to match a pattern in the
|
||||
allowed list.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is extremely important to properly configure allowed and disallowed field patterns
|
||||
when exposing your domain model directly for data binding purposes. Otherwise, it is a
|
||||
big security risk.
|
||||
|
||||
Furthermore, it is strongly recommended that you do **not** use types from your domain
|
||||
model such as JPA or Hibernate entities as the model object in data binding scenarios.
|
||||
====
|
||||
@@ -1,330 +0,0 @@
|
||||
[[uricomponents]]
|
||||
= UriComponents
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
UriComponents uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.encode() // <3>
|
||||
.build(); // <4>
|
||||
|
||||
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
<3> Request to have the URI template and URI variables encoded.
|
||||
<4> Build a `UriComponents`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.encode() // <3>
|
||||
.build() // <4>
|
||||
|
||||
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
<3> Request to have the URI template and URI variables encoded.
|
||||
<4> Build a `UriComponents`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
|
||||
The preceding example can be consolidated into one chain and shortened with `buildAndExpand`,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("Westin", "123")
|
||||
.toUri();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("Westin", "123")
|
||||
.toUri()
|
||||
----
|
||||
|
||||
You can shorten it further by going directly to a URI (which implies encoding),
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123")
|
||||
----
|
||||
|
||||
You can shorten it further still with a full URI template, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
|
||||
.build("Westin", "123")
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[uribuilder]]
|
||||
= UriBuilder
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
<<web-uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
|
||||
`UriBuilder`, in turn, with a `UriBuilderFactory`. Together, `UriBuilderFactory` and
|
||||
`UriBuilder` provide a pluggable mechanism to build URIs from URI templates, based on
|
||||
shared configuration, such as a base URL, encoding preferences, and other details.
|
||||
|
||||
You can configure `RestTemplate` and `WebClient` with a `UriBuilderFactory`
|
||||
to customize the preparation of URIs. `DefaultUriBuilderFactory` is a default
|
||||
implementation of `UriBuilderFactory` that uses `UriComponentsBuilder` internally and
|
||||
exposes shared configuration options.
|
||||
|
||||
The following example shows how to configure a `RestTemplate`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
|
||||
|
||||
String baseUrl = "https://example.org";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(factory);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
|
||||
|
||||
val baseUrl = "https://example.org"
|
||||
val factory = DefaultUriBuilderFactory(baseUrl)
|
||||
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
|
||||
val restTemplate = RestTemplate()
|
||||
restTemplate.uriTemplateHandler = factory
|
||||
----
|
||||
|
||||
The following example configures a `WebClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
|
||||
|
||||
String baseUrl = "https://example.org";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
|
||||
|
||||
val baseUrl = "https://example.org"
|
||||
val factory = DefaultUriBuilderFactory(baseUrl)
|
||||
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
|
||||
val client = WebClient.builder().uriBuilderFactory(factory).build()
|
||||
----
|
||||
|
||||
In addition, you can also use `DefaultUriBuilderFactory` directly. It is similar to using
|
||||
`UriComponentsBuilder` but, instead of static factory methods, it is an actual instance
|
||||
that holds configuration and preferences, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
String baseUrl = "https://example.com";
|
||||
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
|
||||
|
||||
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val baseUrl = "https://example.com"
|
||||
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)
|
||||
|
||||
val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123")
|
||||
----
|
||||
|
||||
|
||||
[[uri-encoding]]
|
||||
= URI Encoding
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` exposes encoding options at two levels:
|
||||
|
||||
* {api-spring-framework}/web/util/UriComponentsBuilder.html#encode--[UriComponentsBuilder#encode()]:
|
||||
Pre-encodes the URI template first and then strictly encodes URI variables when expanded.
|
||||
* {api-spring-framework}/web/util/UriComponents.html#encode--[UriComponents#encode()]:
|
||||
Encodes URI components _after_ URI variables are expanded.
|
||||
|
||||
Both options replace non-ASCII and illegal characters with escaped octets. However, the first option
|
||||
also replaces characters with reserved meaning that appear in URI variables.
|
||||
|
||||
TIP: Consider ";", which is legal in a path but has reserved meaning. The first option replaces
|
||||
";" with "%3B" in URI variables but not in the URI template. By contrast, the second option never
|
||||
replaces ";", since it is a legal character in a path.
|
||||
|
||||
For most cases, the first option is likely to give the expected result, because it treats URI
|
||||
variables as opaque data to be fully encoded, while the second option is useful if URI
|
||||
variables do intentionally contain reserved characters. The second option is also useful
|
||||
when not expanding URI variables at all since that will also encode anything that
|
||||
incidentally looks like a URI variable.
|
||||
|
||||
The following example uses the first option:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("New York", "foo+bar")
|
||||
.toUri();
|
||||
|
||||
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("New York", "foo+bar")
|
||||
.toUri()
|
||||
|
||||
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
|
||||
----
|
||||
|
||||
You can shorten the preceding example by going directly to the URI (which implies encoding),
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("New York", "foo+bar");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
|
||||
You can shorten it further still with a full URI template, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
|
||||
.build("New York", "foo+bar");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
|
||||
The `WebClient` and the `RestTemplate` expand and encode URI templates internally through
|
||||
the `UriBuilderFactory` strategy. Both can be configured with a custom strategy,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
String baseUrl = "https://example.com";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
// Customize the RestTemplate..
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(factory);
|
||||
|
||||
// Customize the WebClient..
|
||||
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val baseUrl = "https://example.com"
|
||||
val factory = DefaultUriBuilderFactory(baseUrl).apply {
|
||||
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
}
|
||||
|
||||
// Customize the RestTemplate..
|
||||
val restTemplate = RestTemplate().apply {
|
||||
uriTemplateHandler = factory
|
||||
}
|
||||
|
||||
// Customize the WebClient..
|
||||
val client = WebClient.builder().uriBuilderFactory(factory).build()
|
||||
----
|
||||
|
||||
The `DefaultUriBuilderFactory` implementation uses `UriComponentsBuilder` internally to
|
||||
expand and encode URI templates. As a factory, it provides a single place to configure
|
||||
the approach to encoding, based on one of the below encoding modes:
|
||||
|
||||
* `TEMPLATE_AND_VALUES`: Uses `UriComponentsBuilder#encode()`, corresponding to
|
||||
the first option in the earlier list, to pre-encode the URI template and strictly encode URI variables when
|
||||
expanded.
|
||||
* `VALUES_ONLY`: Does not encode the URI template and, instead, applies strict encoding
|
||||
to URI variables through `UriUtils#encodeUriVariables` prior to expanding them into the
|
||||
template.
|
||||
* `URI_COMPONENT`: Uses `UriComponents#encode()`, corresponding to the second option in the earlier list, to
|
||||
encode URI component value _after_ URI variables are expanded.
|
||||
* `NONE`: No encoding is applied.
|
||||
|
||||
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historic
|
||||
reasons and for backwards compatibility. The `WebClient` relies on the default value
|
||||
in `DefaultUriBuilderFactory`, which was changed from `EncodingMode.URI_COMPONENT` in
|
||||
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.
|
||||
@@ -0,0 +1,10 @@
|
||||
[[webflux-http-interface-client]]
|
||||
= HTTP Interface Client
|
||||
|
||||
The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
|
||||
exchange methods. You can then generate a proxy that implements this interface and
|
||||
performs the exchanges. This helps to simplify HTTP remote access and provides additional
|
||||
flexibility for to choose an API style such as synchronous or reactive.
|
||||
|
||||
See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[[webflux-reactive-libraries]]
|
||||
= 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 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
|
||||
provides pluggable support for reactive library and other asynchronous types. The registry
|
||||
has built-in support for RxJava 3, Kotlin coroutines and SmallRye Mutiny, 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 a Reactive Streams
|
||||
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
|
||||
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`.
|
||||
|
||||
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 (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are buffered into a list and rendered as a JSON array.
|
||||
12
framework-docs/modules/ROOT/pages/web/webflux-test.adoc
Normal file
12
framework-docs/modules/ROOT/pages/web/webflux-test.adoc
Normal file
@@ -0,0 +1,12 @@
|
||||
[[webflux-test]]
|
||||
= Testing
|
||||
[.small]#xref:web/webmvc-test.adoc[Same in Spring MVC]#
|
||||
|
||||
The `spring-test` module provides mock implementations of `ServerHttpRequest`,
|
||||
`ServerHttpResponse`, and `ServerWebExchange`.
|
||||
See xref:testing/unit.adoc#mock-objects-web-reactive[Spring Web Reactive] for a
|
||||
discussion of mock objects.
|
||||
|
||||
xref:testing/webtestclient.adoc[`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.
|
||||
@@ -5,9 +5,7 @@
|
||||
This part of the reference documentation covers support for reactive-stack WebSocket
|
||||
messaging.
|
||||
|
||||
|
||||
|
||||
|
||||
include::partial$web/websocket-intro.adoc[leveloffset=+1]
|
||||
|
||||
[[webflux-websocket-server]]
|
||||
== WebSocket API
|
||||
|
||||
@@ -171,7 +171,3 @@ to 412 (PRECONDITION_FAILED) to prevent concurrent modification.
|
||||
You should serve static resources with a `Cache-Control` and conditional response headers
|
||||
for optimal performance. See the section on configuring xref:web/webflux/config.adoc#webflux-config-static-resources[Static Resources].
|
||||
|
||||
|
||||
include:../:webflux-view.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
@@ -62,8 +62,3 @@ performance if used extensively. See the
|
||||
{api-spring-framework}/web/bind/annotation/ControllerAdvice.html[`@ControllerAdvice`]
|
||||
javadoc for more details.
|
||||
|
||||
include:../../:webflux-functional.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -102,6 +102,4 @@ controller-specific `Formatter` instances, as the following example shows:
|
||||
== Model Design
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[See equivalent in the Servlet stack]#
|
||||
|
||||
include:../../:web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
This section describes various options available in the Spring Framework to prepare URIs.
|
||||
|
||||
include:../:web-uris.adoc[leveloffset=+2]
|
||||
|
||||
include:../:webflux-cors.adoc[leveloffset=+1]
|
||||
include::partial$web/web-uris.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
@@ -484,7 +484,3 @@ Note that you can also set the default timeout value on a `DeferredResult`,
|
||||
a `ResponseBodyEmitter`, and an `SseEmitter`. For a `Callable`, you can use
|
||||
`WebAsyncTask` to provide a timeout value.
|
||||
|
||||
|
||||
include:../:webmvc-cors.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
@@ -187,8 +187,3 @@ for optimal performance. See the section on configuring xref:web/webmvc/mvc-conf
|
||||
You can use the `ShallowEtagHeaderFilter` to add "`shallow`" `eTag` values that are computed from the
|
||||
response content and, thus, save bandwidth but not CPU time. See xref:web/webmvc/filters.adoc#filters-shallow-etag[Shallow ETag].
|
||||
|
||||
include:../:webmvc-view.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ performance if used extensively. See the
|
||||
{api-spring-framework}/web/bind/annotation/ControllerAdvice.html[`@ControllerAdvice`]
|
||||
javadoc for more details.
|
||||
|
||||
include:../../:webmvc-functional.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,6 @@ controller-specific `Formatter` implementations, as the following example shows:
|
||||
== Model Design
|
||||
[.small]#xref:web/webflux/controller/ann-initbinder.adoc#webflux-ann-initbinder-model-design[See equivalent in the Reactive stack]#
|
||||
|
||||
include:../../:web-data-binding-model-design.adoc[]
|
||||
include::partial$web/web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This section describes various options available in the Spring Framework to work with URI's.
|
||||
|
||||
include:../:web-uris.adoc[leveloffset=+2]
|
||||
include::partial$web/web-uris.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
[[introduction-to-websocket]]
|
||||
= Introduction to WebSocket
|
||||
|
||||
The WebSocket protocol, https://tools.ietf.org/html/rfc6455[RFC 6455], provides a standardized
|
||||
way to establish a full-duplex, two-way communication channel between client and server
|
||||
over a single TCP connection. It is a different TCP protocol from HTTP but is designed to
|
||||
work over HTTP, using ports 80 and 443 and allowing re-use of existing firewall rules.
|
||||
|
||||
A WebSocket interaction begins with an HTTP request that uses the HTTP `Upgrade` header
|
||||
to upgrade or, in this case, to switch to the WebSocket protocol. The following example
|
||||
shows such an interaction:
|
||||
|
||||
[source,yaml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
GET /spring-websocket-portfolio/portfolio HTTP/1.1
|
||||
Host: localhost:8080
|
||||
Upgrade: websocket <1>
|
||||
Connection: Upgrade <2>
|
||||
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
|
||||
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
|
||||
Sec-WebSocket-Version: 13
|
||||
Origin: http://localhost:8080
|
||||
----
|
||||
<1> The `Upgrade` header.
|
||||
<2> Using the `Upgrade` connection.
|
||||
|
||||
|
||||
Instead of the usual 200 status code, a server with WebSocket support returns output
|
||||
similar to the following:
|
||||
|
||||
[source,yaml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
HTTP/1.1 101 Switching Protocols <1>
|
||||
Upgrade: websocket
|
||||
Connection: Upgrade
|
||||
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
|
||||
Sec-WebSocket-Protocol: v10.stomp
|
||||
----
|
||||
<1> Protocol switch
|
||||
|
||||
|
||||
After a successful handshake, the TCP socket underlying the HTTP upgrade request remains
|
||||
open for both the client and the server to continue to send and receive messages.
|
||||
|
||||
A complete introduction of how WebSockets work is beyond the scope of this document.
|
||||
See RFC 6455, the WebSocket chapter of HTML5, or any of the many introductions and
|
||||
tutorials on the Web.
|
||||
|
||||
Note that, if a WebSocket server is running behind a web server (e.g. nginx), you
|
||||
likely need to configure it to pass WebSocket upgrade requests on to the WebSocket
|
||||
server. Likewise, if the application runs in a cloud environment, check the
|
||||
instructions of the cloud provider related to WebSocket support.
|
||||
|
||||
|
||||
|
||||
|
||||
[[http-versus-websocket]]
|
||||
== HTTP Versus WebSocket
|
||||
|
||||
Even though WebSocket is designed to be HTTP-compatible and starts with an HTTP request,
|
||||
it is important to understand that the two protocols lead to very different
|
||||
architectures and application programming models.
|
||||
|
||||
In HTTP and REST, an application is modeled as many URLs. To interact with the application,
|
||||
clients access those URLs, request-response style. Servers route requests to the
|
||||
appropriate handler based on the HTTP URL, method, and headers.
|
||||
|
||||
By contrast, in WebSockets, there is usually only one URL for the initial connect.
|
||||
Subsequently, all application messages flow on that same TCP connection. This points to
|
||||
an entirely different asynchronous, event-driven, messaging architecture.
|
||||
|
||||
WebSocket is also a low-level transport protocol, which, unlike HTTP, does not prescribe
|
||||
any semantics to the content of messages. That means that there is no way to route or process
|
||||
a message unless the client and the server agree on message semantics.
|
||||
|
||||
WebSocket clients and servers can negotiate the use of a higher-level, messaging protocol
|
||||
(for example, STOMP), through the `Sec-WebSocket-Protocol` header on the HTTP handshake request.
|
||||
In the absence of that, they need to come up with their own conventions.
|
||||
|
||||
|
||||
|
||||
|
||||
[[when-to-use-websockets]]
|
||||
== When to Use WebSockets
|
||||
|
||||
WebSockets can make a web page be dynamic and interactive. However, in many cases,
|
||||
a combination of AJAX and HTTP streaming or long polling can provide a simple and
|
||||
effective solution.
|
||||
|
||||
For example, news, mail, and social feeds need to update dynamically, but it may be
|
||||
perfectly okay to do so every few minutes. Collaboration, games, and financial apps, on
|
||||
the other hand, need to be much closer to real-time.
|
||||
|
||||
Latency alone is not a deciding factor. If the volume of messages is relatively low (for example,
|
||||
monitoring network failures) HTTP streaming or polling can provide an effective solution.
|
||||
It is the combination of low latency, high frequency, and high volume that make the best
|
||||
case for the use of WebSocket.
|
||||
|
||||
Keep in mind also that over the Internet, restrictive proxies that are outside of your control
|
||||
may preclude WebSocket interactions, either because they are not configured to pass on the
|
||||
`Upgrade` header or because they close long-lived connections that appear idle. This
|
||||
means that the use of WebSocket for internal applications within the firewall is a more
|
||||
straightforward decision than it is for public facing applications.
|
||||
@@ -7,7 +7,6 @@ This part of the reference documentation covers support for Servlet stack, WebSo
|
||||
messaging that includes raw WebSocket interactions, WebSocket emulation through SockJS, and
|
||||
publish-subscribe messaging through STOMP as a sub-protocol over WebSocket.
|
||||
|
||||
|
||||
|
||||
include::partial$web/websocket-intro.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user