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 * Set the body of the request to the given synchronous {@code Object} and
* perform the request. * 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 * @param body the {@code Object} to write to the request
* @return a {@code Mono} with the response * @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. * 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>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 * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template * @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. * 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>URI Template variables are expanded using the given map.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to * <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, * add additional HTTP headers to the request
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template * @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 * 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. * {@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 * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @return the value for the {@code Location} header * @return the value for the {@code Location} header
@ -199,9 +211,13 @@ public interface RestOperations {
* and returns the representation found in the response. * and returns the representation found in the response.
* <p>URI Template variables are expanded using the given URI variables, if any. * <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 * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value * @param responseType the type of the return value
@ -218,9 +234,13 @@ public interface RestOperations {
* and returns the representation found in the response. * and returns the representation found in the response.
* <p>URI Template variables are expanded using the given map. * <p>URI Template variables are expanded using the given map.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value * @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, * Create a new resource by POSTing the given object to the URL,
* and returns the representation found in the response. * and returns the representation found in the response.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param responseType the type of the return value * @param responseType the type of the return value
@ -253,9 +277,13 @@ public interface RestOperations {
* and returns the response as {@link ResponseEntity}. * and returns the response as {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given URI variables, if any. * <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 * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template * @param uriVariables the variables to expand the template
@ -271,9 +299,13 @@ public interface RestOperations {
* and returns the response as {@link HttpEntity}. * and returns the response as {@link HttpEntity}.
* <p>URI Template variables are expanded using the given map. * <p>URI Template variables are expanded using the given map.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @param uriVariables the variables to expand the template * @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, * Create a new resource by POSTing the given object to the URL,
* and returns the response as {@link ResponseEntity}. * and returns the response as {@link ResponseEntity}.
* <p>The {@code request} parameter can be a {@link HttpEntity} in order to * <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, * add additional HTTP headers to the request.
* can be a {@link org.springframework.http.client.MultipartBodyBuilder MultiValueMap} to * <p>The body of the entity, or {@code request} itself, can be a
* simulate a multipart from submission. * {@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 url the URL
* @param request the Object to be POSTed (may be {@code null}) * @param request the Object to be POSTed (may be {@code null})
* @return the converted object * @return the converted object

View File

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

View File

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

View File

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

View File

@ -293,3 +293,13 @@ from the `reactor-test` module to do that, for example:
.thenCancel() .thenCancel()
.verify(); .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]] [[webflux-client-builder]]
== Builder options == Builder options

View File

@ -2,24 +2,9 @@
= WebSockets = WebSockets
:doc-spring-security: {doc-root}/spring-security/site/docs/current/reference :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 This part of the reference documentation covers support for Servlet stack WebSocket
based WebSocket messaging including use of STOMP as a a WebSocket sub-protocol. messaging, SockJS-based fallback options, and the use of STOMP as a WebSocket messaging
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.
@ -27,52 +12,54 @@ applications.
[[websocket-intro]] [[websocket-intro]]
== Introduction == Introduction
The WebSocket protocol http://tools.ietf.org/html/rfc6455[RFC 6455] defines an important The WebSocket protocol http://tools.ietf.org/html/rfc6455[RFC 6455] provides a standardized
new capability for web applications: full-duplex, two-way communication between client way to establish a full-duplex, two-way communication channel between client and server
and server. It is an exciting new capability on the heels of a long history of over a single TCP connection. It is a different TCP protocol from HTTP but is designed to
techniques to make the web more interactive including Java Applets, XMLHttpRequest, work over HTTP ports 80 and 443 thus allowing use of existing firewall rules.
Adobe Flash, ActiveXObject, various Comet techniques, server-sent events, and others.
A proper introduction to the WebSocket protocol is beyond the scope of this A WebSocket interaction begins with an HTTP compatible handshake request that uses the
document. At a minimum however it's important to understand that HTTP is used only for HTTP `"Upgrade"` header to request switching to the WebSocket protocol:
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.
Spring Framework 4 includes a new `spring-websocket` module with comprehensive [subs="quotes"]
WebSocket support. It is compatible with the Java WebSocket API standard ----
(http://jcp.org/en/jsr/detail?id=356[JSR-356]) GET /spring-websocket-portfolio/portfolio HTTP/1.1
and also provides additional value-add as explained in the rest of the introduction. 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 WebSocket brings up important design considerations that are important to recognize
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
early on, especially in contrast to what we know about building web applications today. early on, especially in contrast to what we know about building web applications today.
Today REST is a widely accepted, understood, and supported Today REST is a widely accepted, understood, and supported