Skip to content

Latest commit

 

History

History
377 lines (282 loc) · 10.7 KB

webflux-webclient.adoc

File metadata and controls

377 lines (282 loc) · 10.7 KB

WebClient

The spring-webflux module includes a reactive, non-blocking client for HTTP requests with a functional-style API client and Reactive Streams support. WebClient depends on a lower level HTTP client library to execute requests and that support is pluggable.

WebClient uses the same codecs as WebFlux server applications do, and shares a common base package, some common APIs, and infrastructure with the server functional web framework. The API exposes Reactor Flux and Mono types, also see web-reactive.adoc. By default it uses it uses Reactor Netty as the HTTP client library but others can be plugged in through a custom ClientHttpConnector.

By comparison to the RestTemplate, the WebClient is:

  • non-blocking, reactive, and supports higher concurrency with less hardware resources.

  • provides a functional API that takes advantage of Java 8 lambdas.

  • supports both synchronous and asynchronous scenarios.

  • supports streaming up or down from a server.

The RestTemplate is not a good fit for use in non-blocking applications, and therefore Spring WebFlux application should always use the WebClient. The WebClient should also be preferred in Spring MVC, in most high concurrency scenarios, and for composing a sequence of remote, inter-dependent calls.

Retrieve

The retrieve() method is the easiest way to get a response body and decode it:

	WebClient client = WebClient.create("http://example.org");

	Mono<Person> result = client.get()
			.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
			.retrieve()
			.bodyToMono(Person.class);

You can also get a stream of objects decoded from the response:

	Flux<Quote> result = client.get()
			.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
			.retrieve()
			.bodyToFlux(Quote.class);

By default, responses with 4xx or 5xx status codes result in an error of type WebClientResponseException but you can customize that:

	Mono<Person> result = client.get()
			.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
			.retrieve()
			.onStatus(HttpStatus::is4xxServerError, response -> ...)
			.onStatus(HttpStatus::is5xxServerError, response -> ...)
			.bodyToMono(Person.class);

Exchange

The exchange() method provides more control. The below example is equivalent to retrieve() but also provides access to the ClientResponse:

	Mono<Person> result = client.get()
			.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
			.exchange()
			.flatMap(response -> response.bodyToMono(Person.class));

At this level you can also create a full ResponseEntity:

	Mono<ResponseEntity<Person>> result = client.get()
			.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
			.exchange()
			.flatMap(response -> response.toEntity(Person.class));

Note that unlike retrieve(), with exchange() there are no automatic error signals for 4xx and 5xx responses. You have to check the status code and decide how to proceed.

Caution

When using exchange() you must always use any of the body or toEntity methods of ClientResponse to ensure resources are released and to avoid potential issues with HTTP connection pooling. You can use bodyToMono(Void.class) if no response content is expected. However keep in mind that if the response does have content, the connection will be closed and will not be placed back in the pool.

Request body

The request body can be encoded from an Object:

	Mono<Person> personMono = ... ;

	Mono<Void> result = client.post()
			.uri("/persons/{id}", id)
			.contentType(MediaType.APPLICATION_JSON)
			.body(personMono, Person.class)
			.retrieve()
			.bodyToMono(Void.class);

You can also have a stream of objects encoded:

	Flux<Person> personFlux = ... ;

	Mono<Void> result = client.post()
			.uri("/persons/{id}", id)
			.contentType(MediaType.APPLICATION_STREAM_JSON)
			.body(personFlux, Person.class)
			.retrieve()
			.bodyToMono(Void.class);

Or if you have the actual value, use the syncBody shortcut method:

	Person person = ... ;

	Mono<Void> result = client.post()
			.uri("/persons/{id}", id)
			.contentType(MediaType.APPLICATION_JSON)
			.syncBody(person)
			.retrieve()
			.bodyToMono(Void.class);

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:

	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:

	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);

Multipart data

To send multipart data, you need to provide a MultiValueMap<String, ?> whose values are either Objects representing part content, or HttpEntity representing the content and headers for a part. MultipartBodyBuilder provides a convenient API to prepare a multipart request:

	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();

In most cases you do not have to specify the Content-Type for each part. The content type is determined automatically based on the HttpMessageWriter chosen to serialize it, or in the case of a Resource based on the file extension. If necessary you can explicitly provide the MediaType to use for each part through one fo the overloaded builder part methods.

Once a MultiValueMap is prepared, the easiest way to pass it to the the WebClient is through the syncBody method:

	MultipartBodyBuilder builder = ...;

	Mono<Void> result = client.post()
			.uri("/path", id)
			.syncBody(builder.build())
			.retrieve()
			.bodyToMono(Void.class);

If the MultiValueMap contains at least one non-String value, which could also be represent regular form data (i.e. "application/x-www-form-urlencoded"), you don’t have to set the Content-Type to "multipart/form-data". This is always the case when using MultipartBodyBuilder which ensures an HttpEntity wrapper.

As an alternative to MultipartBodyBuilder, you can also provide multipart content, inline-style, through the built-in BodyInserters. For example:

	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);

Builder options

A simple way to create WebClient is through the static factory methods create() and create(String) with a base URL for all requests. You can also use WebClient.builder() for access to more options.

To customize the underlying HTTP client:

	SslContext sslContext = ...

	ClientHttpConnector connector = new ReactorClientHttpConnector(
			builder -> builder.sslContext(sslContext));

	WebClient webClient = WebClient.builder()
			.clientConnector(connector)
			.build();

To customize the HTTP codecs used for encoding and decoding HTTP messages:

	ExchangeStrategies strategies = ExchangeStrategies.builder()
			.codecs(configurer -> {
				// ...
			})
			.build();

	WebClient webClient = WebClient.builder()
			.exchangeStrategies(strategies)
			.build();

The builder can be used to insert Filters.

Explore the WebClient.Builder in your IDE for other options related to URI building, default headers (and cookies), and more.

After the WebClient is built, you can always obtain a new builder from it, in order to build a new WebClient, based on, but without affecting the current instance:

	WebClient modifiedClient = client.mutate()
			// user builder methods...
			.build();

Filters

WebClient supports interception style request filtering:

	WebClient client = WebClient.builder()
			.filter((request, next) -> {
				ClientRequest filtered = ClientRequest.from(request)
						.header("foo", "bar")
						.build();
				return next.exchange(filtered);
			})
			.build();

ExchangeFilterFunctions provides a filter for basic authentication:

	// static import of ExchangeFilterFunctions.basicAuthentication

	WebClient client = WebClient.builder()
			.filter(basicAuthentication("user", "pwd"))
			.build();

You can also mutate an existing WebClient instance without affecting the original:

	WebClient filteredClient = client.mutate()
			.filter(basicAuthentication("user", "pwd")
			.build();

Testing

To test code that uses the WebClient, you can use a mock web server such as the OkHttp MockWebServer. To see example use, check WebClientIntegrationTests in the Spring Framework tests, or the static-server sample in the OkHttp repository.