Spring Boot 2.0 WebClient Handle 404 before continuing - spring-boot

I want to handle 404 from an API call before continuing the pipeline. If the customerId passed in doesn't bring a record back, I would like to throw a 404, I have tried checking for stauscode inside first flatmap but map underneath expects a Mono so that doesn't compile.
#PostMapping(path = ["/customers/{customerId}/place"])
fun create(#PathVariable customerId: String): Mono<ResponseEntity<OrderPlacedResponse>> {
return webClient
.get()
.uri("/$customerId/cart", customerId)
.exchange()
.flatMap { response ->
response.bodyToMono(Cart::class.java)
}
.map { it.items.map { OrderItem(it.productId, it.quantity, it.price) } }
.map { items -> Order(customerId, items, UUID.randomUUID().toString()) }
.flatMap { orderRepository.save(it) }
.map {
ResponseEntity.ok(OrderPlacedResponse("Order Placed", it))
}
.doOnError {
ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.build<OrderPlacedResponse>().toMono()
}
}

Ahha moment after battling for hours:
#PostMapping(path = ["/customers/{customerId}/place"])
fun create(#PathVariable customerId: String): Mono<ResponseEntity<OrderPlacedResponse>> {
return webClient
.get()
.uri("/$customerId/cart", customerId)
.exchange()
.flatMap { response ->
response.bodyToMono(Cart::class.java)
}
.map { it.items.map { OrderItem(it.productId, it.quantity, it.price) } }
.map { items -> Order(customerId, items, UUID.randomUUID().toString()) }
.flatMap { orderRepository.save(it) }
.map {
ResponseEntity.ok(OrderPlacedResponse("Order Placed", it))
}
.switchIfEmpty(
ResponseEntity
.status(HttpStatus.NOT_FOUND)
.build<OrderPlacedResponse>().toMono()
)
.doOnError {
ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.build<OrderPlacedResponse>().toMono()
}
}

Related

Spring RouterFunction endpoint with coroutines coRouter return 404

I have a code
#Configuration
class RouteConfiguration {
#Bean
fun apiRouter(handler: UserHandler): RouterFunction<ServerResponse> {
return router {
"/api".nest {
GET("/users") { ok().bodyValue(handler.getAllUsers()) }
GET("/users/{id}") {
val user: User? = handler.getUserById(it.pathVariable("id").toInt())
if (user != null) {
ok().bodyValue(user)
} else {
ok().build()
}
}
POST("/users") {
ok().bodyValue(handler.createUser(it.bodyToMono(User::class.java).block()))
}
}
}
}
}
and this code works fine.
So i want use coroutines inside handling functions:
#Configuration
class RouteConfiguration {
#Bean
fun apiRouter(handler: UserHandler): RouterFunction<ServerResponse> {
return coRouter {
"/api".nest {
GET("/users") { ok().bodyAndAwait(handler.getAllUsers()) }
GET("/users/{id}") {
val user: User? = handler.getUserById(it.pathVariable("id").toInt())
if (user != null) {
ok().bodyValueAndAwait(user)
} else {
ok().buildAndAwait()
}
}
POST("/users") {
ok().bodyValueAndAwait(handler.createUser(it.bodyToMono(User::class.java).awaitSingle()))
}
}
}
}
}
So after i change router RouterFunctionDsl to coRouter CoRouterFunctionDsl my endpoints start return 404 NOT FOUND and IDEAD also can't find these endpoints.
What should i do to set up coRouter in a correct way.

Throw exception to parent method in webclient

Is there is a way to throw the exception to parent method in webclient. I am not able to throw the exception to parent method getStores(). What is the way to throw the exception to parent method in below code.
For example:
#Override
public Mono<Stores> getStores(String id) {
Mono<Stores> stores = null;
try {
WebClientRequest<Stores> webClientRequest = new WebClientRequest<>();
webClientRequest.setContentType("application/json");
webClientRequest.setEndPoint("http://localhost:8090/api/stores/eee");
webClientRequest.setPathParam(id);
stores= webClient.getAsynchronousWebClient(webClientRequest, Stores.class);
} catch(MtampWebClientException e) {
System.out.println("Hello");
}
return stores;
}
public <T> Mono<K> getAsynchronousWebClient(WebClientRequest<T> webClientRequest, Class<K> clazz) {
ResponseSpec retrieve = performWebRequest(webClientRequest);
return retrieve.bodyToMono(clazz).doOnNext(response -> webClientResponse(webClientRequest, response));
}
private <T> ResponseSpec performWebRequest(WebClientRequest<T> webClientRequest) {
WebClient client = webClientBuilder();
LinkedMultiValueMap<String, String> map = setHeaders(webClientRequest);
Consumer<HttpHeaders> consumer = it -> it.addAll(map);
ResponseSpec retrieve;
if (null != webClientRequest.getPathParam()) {
retrieve = client.get().uri(webClientRequest.getEndPoint() + "/" + webClientRequest.getPathParam())
.headers(consumer).retrieve();
} else if (null != webClientRequest.getQueryParam()) {
LinkedMultiValueMap<String, String> queryMap = new LinkedMultiValueMap<>();
webClientRequest.getQueryParam().entrySet().stream().forEach(e -> queryMap.add(e.getKey(), e.getValue()));
retrieve = client.get()
.uri(uriBuilder -> uriBuilder.path(webClientRequest.getEndPoint()).queryParams(queryMap).build())
.headers(consumer).retrieve();
} else {
retrieve = client.get().uri(webClientRequest.getEndPoint()).headers(consumer).retrieve();
}
return retrieve;
}
private WebClient webClientBuilder() {
HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofMillis(5000))
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
return WebClient.builder().filter(ExchangeFilterFunction.ofResponseProcessor(this::errorHandler))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
public Mono<ClientResponse> errorHandler(ClientResponse clientResponse) {
if (clientResponse.statusCode().is5xxServerError()) {
return clientResponse.bodyToMono(String.class).flatMap(errorBody -> Mono
.error(new MtampWebClientException(clientResponse.statusCode().toString(), errorBody)));
} else if (clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class).flatMap(errorBody -> Mono
.error(new MtampWebClientException(clientResponse.statusCode().toString(), errorBody)));
} else {
return Mono.just(clientResponse);
}
}

Handling exception with Mono flow

I have a WebFlux handler as below.
#Transactional
public Mono<ServerResponse> submitOrder(final ServerRequest request) {
return context.retrieveUser().flatMap(usr -> {
try {
return Mono.zip(branchSetting, labOrderDetail, labOrderTests).flatMap(response -> {
final Mono<String> submitOrderMono = service.submitOrder(usr);
final Mono<Integer> updateStatusMono = orderRepository.updateStatus(orderId);
return Mono.zip(submitOrderMono, updateStatusMono).flatMap(submitResponse -> {
return ok().bodyValue(submitResponse.getT1());
}).onErrorResume(e -> {
if (e instanceof ServiceException) {
ServiceException ex = (ServiceException) e;
return status(ex.getStatusCode()).bodyValue(e.getMessage());
} else {
return status(500).bodyValue(e.getMessage());
}
});
});
} catch (Throwable e) {
if (e instanceof ServiceException) {
ServiceException ex = (ServiceException) e;
return status(ex.getStatusCode()).bodyValue(e.getMessage());
} else {
return status(500).bodyValue(e.getMessage());
}
}
});
}
submitOrder method from service class,
public Mono<String> submitOrder(final Order order,
if (order.getPatientId() != null) {
throw new ServiceException("Missing Id for patient !!!", HttpStatus.BAD_REQUEST.value());
}
}
Here, I am doing some validation and throwing Exception.
But, this exception is not getting into onErrorResume or catch block in the calling main method and hence the service caller sees 500 error.
Not sure what is wrong here.
When working in a reactive WebFlux context, throwing exceptions and using try-catch-block is, imho, not really best practice.
The more idiomatic approach would be to use Mono.error() instead of throw commands. Mono.error() emits an error signal so that a subsequent onErrorResume() could deal with it.
That being said, submitOrder() could look like this:
public Mono<String> submitOrder(final Order order) {
if (order.getPatientId() == null) {
Mono.error(new ServiceException("Missing Id for patient !!!", 500));
}
return Mono.just("some reasonable result");
}
With this rewrite, the first snippet should (maybe with some minor adjustments) work this way:
public Mono<ServerResponse> submitOrder(final ServerRequest request) {
return context.retrieveUser().flatMap(usr -> {
return Mono.zip(branchSetting, labOrderDetail, labOrderTests).flatMap(response -> {
final Mono<String> submitOrderMono = service.submitOrder(usr);
final Mono<Integer> updateStatusMono = orderRepository.updateStatus(orderId);
return Mono.zip(submitOrderMono, updateStatusMono).flatMap(submitResponse -> {
return ok().bodyValue(submitResponse.getT1());
}).onErrorResume(e -> {
if (e instanceof ServiceException) {
ServiceException ex = (ServiceException) e;
return status(ex.getStatusCode()).bodyValue(e.getMessage());
} else {
return status(500).bodyValue(e.getMessage());
}
});
});
});
}

Mono not executing with schedule

I have a Spring webflux app with the below method.
#Override
public Mono<Integer> updateSetting(int orgId, IntegrationDto dto,
Map<String, Object> jsonMap) {
return retrieveServices(dto.getClientId()).flatMap(services -> {
jsonMap.put("service", services);
return categoryRepository.findCategoryIdCountByName("test", orgId)
.flatMap(categoryIdCount -> {
final ServiceDto serviceInput = new ServiceDto();
if (categoryIdCount == 0) {
return inventoryCategoryRepository.save(InventoryCategory.of("test", orgId))
.flatMap(category -> {
return saveServices(serviceInput, orgId, jsonMap,
category.getCategoryId());
});
} else {
// Some Logc here ...
}
});
}).onErrorResume(e -> {
if (e instanceof WebClientResponseException) {
int statusCode = ((WebClientResponseException) e).getRawStatusCode();
throw new LabServiceException("Unable to connect to the service !", statusCode);
}
throw new ServiceException("Error connecting to the service !");
});
}
private Mono<Services> retrieveServices(final String clientId) {
return webClient.get().uri(props.getBaseUrl() + "/api/v1/services")
.retrieve().bodyToMono(Services.class);
}
private Mono<Integer> saveInventories(ServiceInput serviceInput, int orgId, Map<String, Object> jsonMap,
Long categoryId) {
return refreshInventories(serviceInput, orgId, categoryId).flatMap(reponse -> {
return updateSetting(branchId, jsonMap);
});
}
private Mono<Integer> refreshInventories(ServiceInput serviceInput, int orgId, Long categoryId) {
return inventoryRepository.findAllCodesByTypeBranchId(branchId).collectList().flatMap(codes -> {
return retrieveAvailableServices(Optional.of(serviceInput), categoryId).flatMap(services -> {
List<Inventory> inventories = services.stream()
.filter(inventory -> !codes.contains(inventory.getCode()))
.map(inventoryDto -> toInventory(inventoryDto, branchId)).collect(Collectors.toList());
if (inventories.size() > 0) {
return saveAllInventories(inventories).flatMap(response -> {
return Mono.just(orgId);
});
} else {
return Mono.just(orgId);
}
});
});
}
Here, the updateSettig public method is being invoked from a REST call and all gets executed as expected.
Now, I want to execute the same with a different flow as well like a scheduler.
When I invoke from a scheduler also, It works.
updateSetting(orgId, dto, jsonMap).subscribe();
But, I want to wait until the updateSetting gets executed.
So, tried with the code below.
updateSetting(orgId, dto, jsonMap).flatMap(response -> {
////
});
With the above code, updateSetting method gets invoked, but not getting into the retrieveServices.
return retrieveServices(dto.getClientId()).flatMap(services -> {
You always need to subscribe in the end. So your code should be:
updateSetting(orgId, dto, jsonMap).flatMap(response -> {
////
}).subscribe();

Spring WebFlux websocket close

I'cand figure out how to close websocket on server side.
Here is my code:
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<WebSocketMessage> flux = session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map(s -> {
if (s.equals("bye")) {
//todo: NEED TO CLOSE - session.close()
return "You said bye!";
} else {
return s;
}
})
.map(String::toUpperCase)
.map(session::textMessage).log();
return session.send(flux).log();
}
You can use takeUntil method:
Flux<WebSocketMessage> flux = session.receive()
.map(WebSocketMessage::getPayloadAsText)
.takeUntil("bye"::equals)
.map(s -> {
if (s.equals("bye")) {
return "You said bye!";
} else {
return s;
}
})
.map(session::textMessage).log();

Resources