Update reference with examples of multipart requests

Issue: SPR-16118
This commit is contained in:
Rossen Stoyanchev 2017-11-06 16:28:27 -05:00
parent 4ec60f08ad
commit 14f02d7192
9 changed files with 247 additions and 135 deletions

View File

@ -615,6 +615,17 @@ public interface WebTestClient {
/**
* Set the body of the request to the given synchronous {@code Object} and
* perform the request.
* <p>This method is a convenient shortcut for:
* <pre class="code">
* .body(BodyInserters.fromObject(object))
* </pre>
* <p>The body can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create
* a multipart request. The values in the {@code MultiValueMap} can be
* any Object representing the body of the part, or an
* {@link org.springframework.http.HttpEntity HttpEntity} representing a
* part with body and headers. The {@code MultiValueMap} can be built
* conveniently using
* @param body the {@code Object} to write to the request
* @return a {@code Mono} with the response
*/

View File

@ -149,9 +149,13 @@ public interface RestOperations {
* the {@code Location} header. This header typically indicates where the new resource is stored.
* <p>URI Template variables are expanded using the given URI variables, if any.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
@ -166,9 +170,13 @@ public interface RestOperations {
* the {@code Location} header. This header typically indicates where the new resource is stored.
* <p>URI Template variables are expanded using the given map.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
@ -183,9 +191,13 @@ public interface RestOperations {
* Create a new resource by POSTing the given object to the URL, and returns the value of the
* {@code Location} header. This header typically indicates where the new resource is stored.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @return the value for the {@code Location} header
@ -199,9 +211,13 @@ public interface RestOperations {
* and returns the representation found in the response.
* <p>URI Template variables are expanded using the given URI variables, if any.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value
@ -218,9 +234,13 @@ public interface RestOperations {
* and returns the representation found in the response.
* <p>URI Template variables are expanded using the given map.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value
@ -236,9 +256,13 @@ public interface RestOperations {
* Create a new resource by POSTing the given object to the URL,
* and returns the representation found in the response.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value
@ -253,9 +277,13 @@ public interface RestOperations {
* and returns the response as {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given URI variables, if any.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
@ -271,9 +299,13 @@ public interface RestOperations {
* and returns the response as {@link HttpEntity}.
* <p>URI Template variables are expanded using the given map.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template
@ -288,9 +320,13 @@ public interface RestOperations {
* Create a new resource by POSTing the given object to the URL,
* and returns the response as {@link ResponseEntity}.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to
* add additional HTTP headers to the request. The body of the entity, or {@code request} itself,
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to
* simulate a multipart from submission.
* add additional HTTP headers to the request.
* <p>The body of the entity, or {@code request} itself, can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request.
* The values in the {@code MultiValueMap} can be any Object representing the body of the part,
* or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body
* and headers. The {@code MultiValueMap} can be built conveniently using
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* @param url the URL
* @param request the Object to be POSTed (may be {@code null})
* @return the converted object

View File

@ -220,10 +220,13 @@ public abstract class BodyInserters {
/**
* Return a {@code FormInserter} that writes the given {@code MultiValueMap}
* as multipart data. The {@code multipartData} parameter can conveniently be built using the
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* Note that the returned inserter allows for additional entries to be added
* via {@link FormInserter#with(String, Object)}.
* as multipart data. The values in the {@code MultiValueMap} can be any
* Object representing the body of the part, or an
* {@link org.springframework.http.HttpEntity HttpEntity} representing a part
* with body and headers. The {@code MultiValueMap} can be built conveniently
* using {@link org.springframework.http.client.MultipartBodyBuilder
* MultipartBodyBuilder}. Also the returned inserter allows for additional
* entries to be added via {@link FormInserter#with(String, Object)}.
*
* <p><strong>Note:</strong> you can also use the {@code syncBody(Object)}
* method in the request builders of both the {@code WebClient} and
@ -245,15 +248,13 @@ public abstract class BodyInserters {
}
/**
* Return a {@code FormInserter} that writes the key-value pair as multipart data. The
* {@code multipartData} parameter can conveniently be built using the
* {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}.
* Note that the returned inserter allows for additional entries to be added
* via {@link FormInserter#with(String, Object)}.
* {@link FormInserter#with(String, Object)}.
* @param key the key to add to the form
* @param value the value to add to the form
* @return a {@code FormInserter} that writes multipart data
* A variant of {@link #fromMultipartData(MultiValueMap)} for adding
* parts as name-value pairs in-line vs building a {@code MultiValueMap}
* and passing it in.
* @param key the part name
* @param value the part value, an Object or {@code HttpEntity}
* @return a {@code FormInserter} that can writes the provided multipart
* data and also allows adding more parts
*/
// Note that the returned BodyInserter is parameterized to ClientHttpRequest, not
// ReactiveHttpOutputMessage like other methods, since sending form data only typically happens

View File

@ -544,9 +544,17 @@ public interface WebClient {
/**
* Set the body of the request to the given synchronous {@code Object}.
* <p>This method is a convenient shortcut for {@link #body(BodyInserter)} with a
* {@linkplain org.springframework.web.reactive.function.BodyInserters#fromObject
* Object body inserter}.
* <p>This method is a convenient shortcut for:
* <pre class="code">
* .body(BodyInserters.fromObject(object))
* </pre>
* <p>The body can be a
* {@link org.springframework.util.MultiValueMap MultiValueMap} to create
* a multipart request. The values in the {@code MultiValueMap} can be
* any Object representing the body of the part, or an
* {@link org.springframework.http.HttpEntity HttpEntity} representing a
* part with body and headers. The {@code MultiValueMap} can be built
* conveniently using
* @param body the {@code Object} to write to the request
* @return this builder
*/

View File

@ -23,10 +23,12 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
@ -75,16 +77,11 @@ public class MultipartIntegrationTests extends AbstractRouterFunctionIntegration
.verifyComplete();
}
private MultiValueMap<String, Object> generateBody() {
HttpHeaders fooHeaders = new HttpHeaders();
fooHeaders.setContentType(MediaType.TEXT_PLAIN);
ClassPathResource fooResource = new ClassPathResource("org/springframework/http/codec/multipart/foo.txt");
HttpEntity<ClassPathResource> fooPart = new HttpEntity<>(fooResource, fooHeaders);
HttpEntity<String> barPart = new HttpEntity<>("bar");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fooPart", fooPart);
parts.add("barPart", barPart);
return parts;
private MultiValueMap<String, HttpEntity<?>> generateBody() {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fooPart", new ClassPathResource("org/springframework/http/codec/multipart/foo.txt"));
builder.part("barPart", "bar");
return builder.build();
}
@Override

View File

@ -32,16 +32,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@ -136,27 +134,13 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes
.verifyComplete();
}
private MultiValueMap<String, Object> generateBody() {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
ClassPathResource resource = new ClassPathResource("foo.txt", MultipartHttpMessageReader.class);
parts.add("fileParts", new HttpEntity<>(resource, headers));
headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
resource = new ClassPathResource("logo.png", getClass());
parts.add("fileParts", new HttpEntity<>(resource, headers));
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
parts.add("jsonPart", new HttpEntity<>(new Person("Jason"), headers));
return parts;
private MultiValueMap<String, HttpEntity<?>> generateBody() {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("fileParts", new ClassPathResource("foo.txt", MultipartHttpMessageReader.class));
builder.part("fileParts", new ClassPathResource("logo.png", getClass()));
builder.part("jsonPart", new Person("Jason"));
return builder.build();
}

View File

@ -293,3 +293,13 @@ from the `reactor-test` module to do that, for example:
.thenCancel()
.verify();
----
[[webtestclient-request-body]]
=== Request body
When it comes to building requests, the `WebTestClient` offers an identical API as the
`WebClient` and the implementation is mostly a simple pass-through. Please refer
to the <<web-reactive.adoc#webflux-client-body,WebClient documentation>> for examples on
how to prepare a request with a body including submitting form data, multipart requests,
and more.

View File

@ -158,6 +158,84 @@ Or if you have the actual value, use the `syncBody` shortcut method:
[[webflux-client-body-form]]
=== 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`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);
----
You can also supply form data in-line via `BodyInserters`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
----
[[webflux-client-body-multipart]]
=== Multipart data
To send multipart data, provide a `MultiValueMap<String, ?>` where values are either an
Object representing the part body, or an `HttpEntity` representing the part body and
headers. `MultipartBodyBuilder` can be used to build the parts:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(parts)
.retrieve()
.bodyToMono(Void.class);
----
Note that the content type for each part is automatically set based on the extension of the
file being written or the type of Object. If you prefer you can also be more explicit and
specify the content type for each part.
You can also supply multipart data in-line via `BodyInserters`:
[source,java,intent=0]
[subs="verbatim,quotes"]
----
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
----
[[webflux-client-builder]]
== Builder options

View File

@ -2,24 +2,9 @@
= WebSockets
:doc-spring-security: {doc-root}/spring-security/site/docs/current/reference
This part of the reference documentation covers the Spring Framework support for Servlet stack
based WebSocket messaging including use of STOMP as a a WebSocket sub-protocol.
<<websocket-intro>> establishes a frame of mind in which to think about
WebSocket, covering adoption challenges, design considerations, and thoughts on
when it is a good fit.
<<websocket-server>> reviews the Spring WebSocket API on the server-side, while
<<websocket-fallback>> explains the SockJS protocol and shows how to configure
and use it.
<<websocket-stomp-overview>> introduces the STOMP messaging protocol.
<<websocket-stomp-enable>> demonstrates how to configure STOMP support in Spring.
<<websocket-stomp-handle-annotations>> and the following sections explain how to
write annotated message handling methods, send messages, choose message broker
options, as well as work with the special "user" destinations. Finally,
<<websocket-stomp-testing>> lists three approaches to testing STOMP/WebSocket
applications.
This part of the reference documentation covers support for Servlet stack WebSocket
messaging, SockJS-based fallback options, and the use of STOMP as a WebSocket messaging
sub-protocol.
@ -27,52 +12,54 @@ applications.
[[websocket-intro]]
== Introduction
The WebSocket protocol http://tools.ietf.org/html/rfc6455[RFC 6455] defines an important
new capability for web applications: full-duplex, two-way communication between client
and server. It is an exciting new capability on the heels of a long history of
techniques to make the web more interactive including Java Applets, XMLHttpRequest,
Adobe Flash, ActiveXObject, various Comet techniques, server-sent events, and others.
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 ports 80 and 443 thus allowing use of existing firewall rules.
A proper introduction to the WebSocket protocol is beyond the scope of this
document. At a minimum however it's important to understand that HTTP is used only for
the initial handshake, which relies on a mechanism built into HTTP to request
a protocol upgrade (or in this case a protocol switch) to which the server can respond with
HTTP status 101 (switching protocols) if it agrees. Assuming the handshake succeeds
the TCP socket underlying the HTTP upgrade request remains open and both client and
server can use it to send messages to each other.
A WebSocket interaction begins with an HTTP compatible handshake request that uses the
HTTP `"Upgrade"` header to request switching to the WebSocket protocol:
Spring Framework 4 includes a new `spring-websocket` module with comprehensive
WebSocket support. It is compatible with the Java WebSocket API standard
(http://jcp.org/en/jsr/detail?id=356[JSR-356])
and also provides additional value-add as explained in the rest of the introduction.
[subs="quotes"]
----
GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
**Upgrade: websocket**
**Connection: Upgrade**
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
----
The response from a server with WebSocket support:
[subs="quotes"]
----
**HTTP/1.1 101 Switching Protocols**
Upgrade: websocket
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 and both client and server can use send and receive messages.
If you Servlet container is running behind a web server (e.g. nginx) you will likely need
to configure it to send WebSocket upgrades to the backend server. Likewise if running in a
cloud environment check the instructions for WebSocket support of your cloud provider.
Beyond that, what do you do about restrictive proxies, outside your control, that 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? The answer to this
is WebSocket emulation that tries to use WebSocket first and then falls back HTTP-based
techniques that WebSocket-like communication. See <<websocket-fallback>>.
[[websocket-into-fallback-options]]
=== Fallback Options
An important challenge to adoption is the lack of support for WebSocket in some
browsers. Notably the first Internet Explorer version to support WebSocket is
version 10 (see http://caniuse.com/websockets for support by browser versions).
Furthermore, some restrictive proxies may be configured in ways that either
preclude the attempt to do an HTTP upgrade or otherwise break connection after
some time because it has remained opened for too long. A good overview on this
topic from Peter Lubbers is available in the InfoQ article
http://www.infoq.com/articles/Web-Sockets-Proxy-Servers["How HTML5 Web Sockets Interact With Proxy Servers"].
Therefore to build a WebSocket application today, fallback options are required in
order to simulate the WebSocket API where necessary. The Spring Framework provides
such transparent fallback options based on the https://github.com/sockjs/sockjs-protocol[SockJS protocol].
These options can be enabled through configuration and do not require modifying the
application otherwise.
[[websocket-intro-architecture]]
=== Messaging
Aside from short-to-midterm adoption challenges, using WebSocket
brings up important design considerations that are important to recognize
WebSocket brings up important design considerations that are important to recognize
early on, especially in contrast to what we know about building web applications today.
Today REST is a widely accepted, understood, and supported