Spring web frameworks - Spring WebFlux
https://docs.spring.io/spring-framework/reference/web/webflux.html
Reading material
A great article: https://docs.spring.io/spring-framework/reference/web/webflux/new-framework.html
https://docs.spring.io/spring-framework/reference/web/webflux.html https://www.baeldung.com/spring-webflux-concurrency
Overview
The original web framework included in the Spring Framework, Spring Web MVC, was purpose-built for the Servlet API and Servlet containers. The reactive-stack web framework, Spring WebFlux, was added later in version 5.0.
It is
- fully non-blocking,
- supports Reactive Streams back pressure, and
- runs on such servers as Netty, Undertow, and Servlet containers.
Usage of spring WebClient and setting up test cases for it
Maven dependency
In order to use WebClients in a Spring Boot project include a starter dependency for Spring WebFlux.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
This dependency implicitly sets all the required dependencies including underlying Netty server.
Usage
If you have to use WebClient in a client class in the application, do it this way:
@Component
public class ClientClassName {
private WebClient webClient;
@Value("$endpointUrl")
private String endpointUrl;
@Value("$username")
private String username;
@Value("$password")
private String password;
public ClientClassName(WebClient webClient) {
this.webClient = webClient;
}
@PostConstruct
private void init() {
webClient = WebClient.builder()
.baseUrl(endpointUrl)
.defaultHeaders(header -> header.setBasicAuth(username, password))
.build();
}
public TestResponse callExternalService(TestRequest testRequest) {
TestResponse resp = null;
try {
resp = webClient.post()
.body(Mono.just(testRequest), TestRequest.class)
.retrieve()
.bodyToMono(TestResponse.class)
.toFuture()
.get();
} catch (Exception1 | Exception2 ex) {
handleException(ex);
}
return resp;
}
}
How to define WebClient bean?
If you are not using the bean in individual client files, you have to add custom configuration in the WebClients defined in each of the classes. If you are defining custom WebClient in a class and not using the WebClient bean (like the example shown above), the configuration that you put on the bean will not be applicable in those individual classes.
@Configuration
public class WebClientConfig {
@Bean
public WebClient configureWebClient() {
return WebClient.builder().build();
}
}
How to set up test cases for ClientClassName?
@RequiredArgsConstructor
public class ClientClassNameTests {
ClientClassName clientClassName;
public static MockWebServer mockWebServer;
@BeforeAll
static void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
}
@BeforeEach
void initialize() {
String baseUrl = String.format("http://localhost:%s", mockWebServer.getPort());
clientClassName = new ClientClassName(WebClient.builder().baseUrl(baseUrl).build());
}
@AfterAll
static void tearDown() throws IOException {
mockWebServer.shutdown();
}
@Test
void test1() throws JsonProcessingException {
TestResponse mockedTestResponse = new TestResponse();
// set dummy values to mockedTestResponse here
mockWebServer.enqueue(new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody(MAPPER.writeValueAsString(mockedTestResponse))
);
TestRequest testReq = new TestRequest();
// set dummy values to testReq here
TestResponse actualResult = clientClassName.callExternalService(testReq);
RecordedRequest request = mockWebServer.takeRequest();
assertAll(() -> {
// verify the response
assertNotNull(actualResult);
assertThat(actualResult).usingRecursiveComparison().isEqualTo(mockedTestResponse));
// verify the request
assertThat(request.getMethod()).isEqualTo("POST");
assertThat(request.getPath()).isEqualTo("/Accounts/ACd936ed6d/Messages.json");
});
}
}
Required dependencies in pom.xml for the test case
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
Issues or exceptions
org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:101)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException
Option 1:
You can increase the MaxInMemorySize like this:
public WebClient webClient() {
final int size = 16 * 1024 * 1024;
final ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(size))
.build();
return WebClient.builder()
.exchangeStrategies(strategies)
.build();
}
Option 2:
I suppose this issue is about adding a new spring.codec.max-in-memory-size configuration property in Spring Boot. Add it to the application.yml file like:
spring:
codec:
max-in-memory-size: 10MB
Need further research
3 techniques to stream JSON in Spring WebFlux - https://nurkiewicz.com/2021/08/json-streaming-in-webflux.html
Processing streaming data with Spring WebFlux - https://medium.com/@nithinmallya4/processing-streaming-data-with-spring-webflux-ed0fc68a14de
Making Fully Asynchronous Requests - https://reflectoring.io/spring-webclient/ (Making Fully Asynchronous Requests)
References:
https://www.arhohuttunen.com/spring-boot-webclient-mockwebserver/ https://www.baeldung.com/spring-mocking-webclient