diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index d8e72697b5..b014d67343 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -615,6 +615,17 @@ public interface WebTestClient { /** * Set the body of the request to the given synchronous {@code Object} and * perform the request. + *

This method is a convenient shortcut for: + *

+		 * .body(BodyInserters.fromObject(object))
+		 * 
+ *

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 */ diff --git a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java index 2a0ad67241..69da49ba5e 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java @@ -149,9 +149,13 @@ public interface RestOperations { * the {@code Location} header. This header typically indicates where the new resource is stored. *

URI Template variables are expanded using the given URI variables, if any. *

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. + *

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. *

URI Template variables are expanded using the given map. *

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 + *

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. *

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. + *

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. *

URI Template variables are expanded using the given URI variables, if any. *

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. + *

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. *

URI Template variables are expanded using the given map. *

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. + *

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. *

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. + *

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}. *

URI Template variables are expanded using the given URI variables, if any. *

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. + *

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}. *

URI Template variables are expanded using the given map. *

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. + *

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}. *

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. + *

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 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java index 902319a9d6..fb0380ba06 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java @@ -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)}. * *

Note: 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 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 1d048db873..d697c4b3bc 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -544,9 +544,17 @@ public interface WebClient { /** * Set the body of the request to the given synchronous {@code Object}. - *

This method is a convenient shortcut for {@link #body(BodyInserter)} with a - * {@linkplain org.springframework.web.reactive.function.BodyInserters#fromObject - * Object body inserter}. + *

This method is a convenient shortcut for: + *

+		 * .body(BodyInserters.fromObject(object))
+		 * 
+ *

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 */ diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java index 1ed3236276..2f183d7520 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java @@ -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 generateBody() { - HttpHeaders fooHeaders = new HttpHeaders(); - fooHeaders.setContentType(MediaType.TEXT_PLAIN); - ClassPathResource fooResource = new ClassPathResource("org/springframework/http/codec/multipart/foo.txt"); - HttpEntity fooPart = new HttpEntity<>(fooResource, fooHeaders); - HttpEntity barPart = new HttpEntity<>("bar"); - MultiValueMap parts = new LinkedMultiValueMap<>(); - parts.add("fooPart", fooPart); - parts.add("barPart", barPart); - return parts; + private MultiValueMap> 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 diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java index 7f3155050c..1a35d49f6a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java @@ -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 generateBody() { - - MultiValueMap 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> 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(); } diff --git a/src/docs/asciidoc/testing-webtestclient.adoc b/src/docs/asciidoc/testing-webtestclient.adoc index f804201dc0..3346fab275 100644 --- a/src/docs/asciidoc/testing-webtestclient.adoc +++ b/src/docs/asciidoc/testing-webtestclient.adoc @@ -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 <> for examples on +how to prepare a request with a body including submitting form data, multipart requests, +and more. diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 88553e757a..879da9f3aa 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -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` 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 formData = ... ; + + Mono 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 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` 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> parts = builder.build(); + +Mono 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 result = client.post() + .uri("/path", id) + .body(fromMultipartData("fieldPart", "value").with("filePart", resource)) + .retrieve() + .bodyToMono(Void.class); +---- + + [[webflux-client-builder]] == Builder options diff --git a/src/docs/asciidoc/web/websocket.adoc b/src/docs/asciidoc/web/websocket.adoc index 4a6433cadb..c88f21cb38 100644 --- a/src/docs/asciidoc/web/websocket.adoc +++ b/src/docs/asciidoc/web/websocket.adoc @@ -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. - -<> 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. - -<> reviews the Spring WebSocket API on the server-side, while -<> explains the SockJS protocol and shows how to configure -and use it. - -<> introduces the STOMP messaging protocol. -<> demonstrates how to configure STOMP support in Spring. -<> 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, -<> 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-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