Update reference with examples of multipart requests
Issue: SPR-16118
This commit is contained in:
parent
4ec60f08ad
commit
14f02d7192
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue