WebClient is a non-blocking, reactive client for performing HTTP requests in Spring WebFlux. It is designed to work efficiently in reactive applications and offers a more flexible and functional approach compared to traditional RestTemplate.
Key Components and Significance
WebClient.Builder:
- Used to configure and build WebClient instances.
- Allows customization like setting base URL, default headers, and common request parameters.
WebClient:
- The core class used to initiate HTTP requests.
- Supports asynchronous and reactive programming paradigms.
- Can handle requests and responses in a non-blocking manner, leveraging Project Reactor.
Request Specification:
- Specifies HTTP method (GET, POST, PUT, DELETE, etc.).
- Defines URI, headers, query parameters, and request body.
Response Handling:
- Provides methods to handle and process responses.
- Supports extracting and mapping response data to different formats and types.
Implementation Details
Creating a WebClient Instance
WebClient webClient = WebClient.builder() .baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
Making a GET Request
Mono<String> response = webClient.get() .uri("/data")
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
Making a POST Request
Mono<String> response = webClient.post() .uri("/submit")
.bodyValue(new DataObject("example"))
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
Creating and Configuring WebClient
Spring WebFlux provides WebClient
as a non-blocking, reactive HTTP client for making HTTP requests. Here is a detailed guide on creating and configuring WebClient
.
1. Creating a WebClient Instance
Basic Creation
You can create a WebClient
instance using the create()
method, which provides a default configuration.
WebClient webClient = WebClient.create();
Creation with Base URL
To create a WebClient
instance with a specified base URL:
WebClient webClient = WebClient.create("https://api.example.com");
2. Configuring WebClient Using WebClient.Builder
For more advanced configurations, use WebClient.Builder
. This allows you to set default headers, base URLs, custom codecs, and more.
Setting Base URL
WebClient webClient = WebClient.builder()
.baseUrl("https://api.example.com")
.build();
Adding Default Headers
WebClient webClient = WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
Adding Custom Headers
WebClient webClient = WebClient.builder()
.defaultHeader("Custom-Header", "HeaderValue")
.build();
Configuring Timeout
You can configure timeouts using ExchangeStrategies
and custom ClientHttpConnector
.
ClientHttpConnector connector = new ReactorClientHttpConnector(
HttpClient.create().responseTimeout(Duration.ofSeconds(5))
);
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.build();
3. Using WebClient for Requests
GET Request
Mono<String> response = webClient.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
POST Request with Body
Mono<String> response = webClient.post()
.uri("/submit")
.bodyValue(new DataObject("example"))
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
PUT Request with Headers
Mono<String> response = webClient.put()
.uri("/update/{id}", 123)
.header(HttpHeaders.AUTHORIZATION, "Bearer token")
.bodyValue(new DataObject("updated example"))
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
DELETE Request
Mono<Void> response = webClient.delete()
.uri("/delete/{id}", 123)
.retrieve()
.bodyToMono(Void.class);
response.subscribe(() -> System.out.println("Deleted successfully"));
4. Handling Responses
Handling Status Codes
You can handle different HTTP status codes using onStatus
.
Mono<String> response = webClient.get()
.uri("/data")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new RuntimeException("Client Error"))
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new RuntimeException("Server Error"))
)
.bodyToMono(String.class);
response.subscribe(System.out::println, System.err::println);
Extracting Response Headers
Mono<String> response = webClient.get()
.uri("/data")
.retrieve()
.toEntity(String.class)
.flatMap(entity -> {
HttpHeaders headers = entity.getHeaders();
String customHeaderValue = headers.getFirst("Custom-Header");
return Mono.just(entity.getBody());
});
response.subscribe(System.out::println);
5. Error Handling
Mono<String> response = webClient.get()
.uri("/data")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new CustomClientException(errorBody)))
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new CustomServerException(errorBody)))
)
.bodyToMono(String.class);
response.subscribe(System.out::println, System.err::println);
Mono vs Flux
- Mono:
- Represents a single value or an empty result.
- Used for scenarios where you expect at most one item.
- Flux:
- Represents a stream of multiple values.
- Used for scenarios where you expect zero or more items.
Scenarios
Mono Example
Fetching a Single Item:
Mono<User> userMono = webClient.get().uri("/user/{id}", userId) .retrieve() .bodyToMono(User.class); userMono.subscribe(user -> System.out.println(user.getName()));
Processing a Single Response:
Mono<ResponseEntity<String>> response = webClient.post().uri("/process") .bodyValue(new ProcessRequest(data)) .retrieve() .toEntity(String.class); response.subscribe(entity -> { System.out.println("Status Code: " + entity.getStatusCode()); System.out.println("Body: " + entity.getBody()); });
Flux Example
Fetching Multiple Items:
Flux<User> userFlux = webClient.get().uri("/users") .retrieve() .bodyToFlux(User.class); userFlux.subscribe(user -> System.out.println(user.getName()));
Streaming Data:
Flux<ServerSentEvent<String>> eventFlux = webClient.get().uri("/stream") .retrieve() .bodyToFlux(new ParameterizedTypeReference<ServerSentEvent<String>>() {}); eventFlux.subscribe(event -> System.out.println("Received event: " + event.data()));
Mono
- Definition: Represents a single or empty result. Think of it as a
CompletableFuture
that either completes successfully with a value or completes empty. - Use Cases:
- When expecting a single item from a service, database, or external API.
- For operations like fetching a specific entity by ID.
- For handling completion signals or one-time events.
Examples
Fetching a Single Item:
Mono<User> userMono = webClient.get() .uri("/user/{id}", userId) .retrieve() .bodyToMono(User.class); userMono.subscribe(user -> System.out.println(user.getName()));
Processing a Single Response:
Mono<ResponseEntity<String>> response = webClient.post() .uri("/process") .bodyValue(new ProcessRequest(data)) .retrieve() .toEntity(String.class); response.subscribe(entity -> { System.out.println("Status Code: " + entity.getStatusCode()); System.out.println("Body: " + entity.getBody()); });
Handling Completion:
Mono<Void> completionMono = someAsyncOperation(); completionMono.subscribe( null, error -> System.err.println("Error: " + error), () -> System.out.println("Operation completed") );
Flux
- Definition: Represents a sequence of 0 to N items. It can be thought of as a
Stream
that can emit multiple values over time. - Use Cases:
- When expecting a collection or stream of items.
- For operations like retrieving a list of entities from a database.
- For handling continuous streams of data, like real-time updates or event streams.
Examples
Fetching Multiple Items:
Flux<User> userFlux = webClient.get() .uri("/users") .retrieve() .bodyToFlux(User.class); userFlux.subscribe(user -> System.out.println(user.getName()));
Streaming Data:
Flux<ServerSentEvent<String>> eventFlux = webClient.get() .uri("/stream") .retrieve() .bodyToFlux(new ParameterizedTypeReference<ServerSentEvent<String>>() {}); eventFlux.subscribe(event -> System.out.println("Received event: " + event.data()));
Handling Multiple Items:
Flux<String> itemsFlux = someAsyncItemProvider(); itemsFlux.subscribe( item -> System.out.println("Received item: " + item), error -> System.err.println("Error: " + error), () -> System.out.println("All items received") );
Conclusion
WebClient in Spring WebFlux offers a powerful, flexible, and efficient way to handle HTTP requests in a reactive programming style. By understanding and utilizing its key components and the differences between Mono and Flux, developers can build highly responsive and scalable applications.
Post a Comment