Non-blocking cross-service communication with WebClient

In the previous sections, we looked at an overview of the basic design of and changes in the new Spring WebFlux module and learned about new functional approaches with RoutesFunction. However, Spring WebFlux also contains other new possibilities. One of the most important introductions is the new non-blocking HTTP client, which is called WebClient.

Essentially, WebClient is the reactive replacement for the old RestTemplate.  However, in WebClient, we have a functional API that fits better with the reactive approach and offers built-in mapping to Project Reactor types such as Flux or Mono. In order to learn more about WebClient, let's have a look at the following example:

WebClient.create("http://localhost/api")                           // (1)
.get() // (2)
.uri("/users/{id}", userId) // (3)
.retrieve() // (4)
.bodyToMono(User.class) // (5)
.map(...) // (6)
.subscribe(); //

In the preceding example, we create a WebClient instance using a factory method called create, shown at point 1. Here, the create method allows us to specify the base URI, which is used internally for all future HTTP calls. Then, in order to start building a call to a remote server, we may execute one of the WebClient methods that sounds like an HTTP method. In the previous example, we used WebClient#get, shown at point (2). Once we call the WebClient#get method, we operate on the request builder instance and can specify the relative path in the uri method, shown at point (3). In addition to the relative path, we can specify headers, cookies, and a request body. However, for simplicity, we have omitted those settings in this case and moved on to composing the request by calling the retrieve or exchange methods. In this example, we use the retrieve method, shown at point (4). This option is useful when we are only interested in retrieving the body and performing further processing. Once the request is set up, we may use one of the methods that help us with the conversion of the response body. Here, we use the bodyToMono method, which converts the incoming payload of the User to Mono, shown at point (5). Finally, we can build the processing flow of the incoming response using the Reactor API, and execute the remote call by calling the subscribe method.

WebClient follows the behavior described in the Reactive Streams specification. This means that only by calling the subscribe method will WebClient wire the connection and start sending the data to the remote server.

Even though, in most cases, the most common response processing is body processing, there are some cases where we need to process the response status, headers, or cookies. For example, let's build a call to our password checking service and process the response status in a custom way using the WebClient API:

class DefaultPasswordVerificationService                           // (1)
implements PasswordVerificationService { //

final WebClient webClient; // (2)
//
public DefaultPasswordVerificationService( //
WebClient.Builder webClientBuilder //
) { //
this.webClient = webClientBuilder // (2.1)
.baseUrl("http://localhost:8080") //
.build(); //
} //

@Override // (3)
public Mono<Void> check(String raw, String encoded) { //
return webClient //
.post() // (3.1)
.uri("/check") //
.body(BodyInserters.fromPublisher( // (3.2)
Mono.just(new PasswordDTO(raw, encoded)), //
PasswordDTO.class //
)) //
.exchange() // (3.3)
.flatMap(response -> { // (3.4)
if (response.statusCode().is2xxSuccessful()) { // (3.5)
return Mono.empty(); //
} //
else if(resposne.statusCode() == EXPECTATION_FAILD) { //
return Mono.error( // (3.6)
new BadCredentialsException(...) //
); //
} //
return Mono.error(new IllegalStateException()); //
}); //
} //
} //

The following numbered list describes the preceding code sample:

  1. This is the implementation of the PasswordVerificationService interface.
  2. This is the initialization of the WebClient instance. It is important to note that we use a WebClient instance per class here, so we do not have to initialize a new one on each execution of the check method. Such a technique reduces the need to initialize a new instance of WebClient and decreases the method's execution time. However, the default implementation of WebClient uses the Reactor-Netty HttpClient, which in default configurations shares a common pool of resources among all the HttpClient instances. Hence, the creation of a new HttpClient instance does not cost that much. Once the constructor of DefaultPasswordVerificationService is called, we start initializing webClient and use a fluent builder, shown at point (2.1), in order to set up the client.  
  3. This is the implementation of the check method. Here, we use the webClient instance in order to execute a post request, shown at point (3.1). In addition, we send the body, using the body method, and prepare to insert it using the BodyInserters#fromPublisher factory method, shown in (3.2). We then execute the exchange method at point (3.3), which returns Mono<ClientResponse>. We may, therefore, process the response using the flatMap operator, shown in (3.4). If the password is verified successfully, as shown at point (3.5), the check method returns Mono.empty. Alternatively, in the case of an EXPECTATION_FAILED(417) status code, we may return the Mono of BadCredentialsExeception, as shown at point (3.6).

As we can see from the previous example, in a case where it is necessary to process the status code, headers, cookies, and other internals of the common HTTP response, the most appropriate method is the exchange method, which returns ClientResponse

As mentioned, DefaultWebClient uses the Reactor-Netty HttpClient in order to provide asynchronous and non-blocking interaction with the remote server. However, DefaultWebClient is designed to be able to change the underlying HTTP client easily. For that purpose, there is a low-level reactive abstraction around the HTTP connection, which is called org.springframework.http.client.reactive.ClientHttpConnector. By default, DefaultWebClient is preconfigured to use ReactorClientHttpConnector, which is an implementation of the ClientHttpConnector interface. Starting from Spring WebFlux 5.1, there is a JettyClientHttpConnector implementation, which uses the reactive HttpClient from Jetty. In order to change the underlying HTTP client engine, we may use the WebClient.Builder#clientConnector method and pass the desired instance, which might be either a custom implementation or the existing one.

In addition to the useful abstract layer, ClientHttpConnector may be used in a raw format. For example, it may be used for downloading large files, on-the-fly processing, or just simple byte scanning. We will not go into details about ClientHttpConnector; we will leave this for curious readers to look into themselves.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.29.119