How to return either Uni<Object> or Uni<Void>? - vert.x

#Route(...)
public Uni<?> call() {
return Uni.createFrom().voidItem();
}
throws a NullPointerException: Invalid value returned by Uni: null
However
#Route(...)
public Uni<Void> call() {
return Uni.createFrom().voidItem();
}
works perfectly fine and responds with HTTP 204
How do I manage to get either Uni<Void> or Uni<AnyObject> from the same method?
I need to return http 204 only in specific scenarios

You can't do that directly as the types are different.
I would recommend using RESTEasy Reactive and do:
#GET
public Uni<Response> call() {
Uni<AnyObject> uni = .... ;
return uni
.onItem().transform(res -> {
if (res == null) return Response.noContent().build();
return Response.ok(res).build();
});
}
By emitting a Response object, you can customize the response status.
Another solution if you want to keep using Reactive Routes is to not return a Uni, but get the RoutingContext as a parameter:
#Route(...)
public void call(RoutingContext rc) {
HttpServerResponse response = rc.response();
Uni<AnyObject> uni = .... ;
return uni
.subscribe().with(res -> {
if (res == null) response.setStatus(204).end();
else response.setStatus(200).end(res);
}, failure -> rc.fail(failure)); // will produce a 500.
}

Related

Can't handle bad request using doOnError WebFlux

I wanna send some DTO object to server. Server have "Valid" annotation, and when server getting not valid DTO, he should send validation errors and something like "HttpStatus.BAD_REQUEST", but when I'm trying to send HttpStatus.BAD_REQUEST doOnError just ignore it.
POST-request from client
BookDTO bookDTO = BookDTO
.builder()
.author(authorTf.getText())
.title(titleTf.getText())
.publishDate(LocalDate.parse(publishDateDp.getValue().toString()))
.owner(userAuthRepository.getUser().getLogin())
.fileData(file.readAllBytes())
.build();
webClient.post()
.uri(bookAdd)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(bookDTO)
.retrieve()
.bodyToMono(Void.class)
.doOnError(exception -> log.error("Error on server - [{}]", exception.getMessage()))
.onErrorResume(WebClientResponseException.class, throwable -> {
if (throwable.getStatusCode() == HttpStatus.BAD_REQUEST) {
log.error("BAD_REQUEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); --My log doesn't contain this error, but server still has errors from bindingResult
return Mono.empty();
}
return Mono.error(throwable);
})
.block();
Server-part
#PostMapping(value = "/add", consumes = {MediaType.APPLICATION_JSON_VALUE})
public HttpStatus savingBook(#RequestBody #Valid BookDTO bookDTO, BindingResult bindingResult) {
List<FieldError> errors = bindingResult.getFieldErrors();
if (bindingResult.hasErrors()) {
for (FieldError error : errors ) {
log.info("Client post uncorrected data [{}]", error.getDefaultMessage());
}
return HttpStatus.BAD_REQUEST;
}else{libraryService.addingBookToDB(bookDTO);}
return null;
}
doOnError is a so-called side effect operation that could be used for instrumentation before onError signal is propagated downstream. (e.g. to log error).
To handle errors you could use onErrorResume. The example, the following code handles the WebClientResponseException and returns Mono.empty instead.
...
.retrieve()
.doOnError(ex -> log.error("Error on server: {}", ex.getMessage()))
.onErrorResume(WebClientResponseException.class, ex -> {
if (ex.getStatusCode() == HttpStatus.BAD_REQUEST) {
return Mono.empty();
}
return Mono.error(ex);
})
...
As an alternative as #Toerktumlare mentioned in his comment, in case you want to handle http status, you could use onStatus method of the WebClient
...
.retrieve()
.onStatus(HttpStatus.BAD_REQUEST::equals, res -> Mono.empty())
...
Update
While working with block it's important to understand how reactive signals will be transformed.
onNext(T) -> T in case of Mono and List<T> for Flux
onError -> exception
onComplete -> null, in case flow completes without onNext
Here is a full example using WireMock for tests
class WebClientErrorHandlingTest {
private WireMockServer wireMockServer;
#BeforeEach
void init() {
wireMockServer = new WireMockServer(wireMockConfig().dynamicPort());
wireMockServer.start();
WireMock.configureFor(wireMockServer.port());
}
#Test
void test() {
stubFor(post("/test")
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withStatus(400)
)
);
WebClient webClient = WebClient.create("http://localhost:" + wireMockServer.port());
Mono<Void> request = webClient.post()
.uri("/test")
.retrieve()
.bodyToMono(Void.class)
.doOnError(e -> log.error("Error on server - [{}]", e.getMessage()))
.onErrorResume(WebClientResponseException.class, e -> {
if (e.getStatusCode() == HttpStatus.BAD_REQUEST) {
log.info("Ignoring error: {}", e.getMessage());
return Mono.empty();
}
return Mono.error(e);
});
Void response = request.block();
assertNull(response);
}
}
The response is null because we had just complete signal Mono.empty() that was transformed to null by applying block

Spring boot return multiple response code with message body in a POST request

In my spring boot app, I want to return different types of response codes with response body.
#RequestMapping(method = RequestMethod.POST, value = "/users")
public ResponseEntity<User> userSignsUp(#RequestBody User user) {
if(userService.getUserByNic(user.getNic()).equals(userService.getUserByNic(user.getNic()))) {
UserErrorBean userError = new UserErrorBean("User already exist","406 error");
return ResponseEntity<>(userError ,HttpStatus.CONFLICT); }
userService.userSave(user);
return ResponseEntity<>(user, HttpStatus.CREATED);
}
This is my rest Controller and I want to return different responses based on different conditions. But it only returns condition if user NIC condition is met. If add user to database, it throws NullPointerException().
I want to return responses according to the request.
You can write a simple RestController like this.
#PostMapping("/users")
public ResponseEntity<User> userSignsUp(#RequestBody User user) {
if(user == null){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
if(userService.getUserByNic(user.getNic()).equals(userService.getUserByNic(user.getNic()))) {
UserErrorBean userError = new UserErrorBean("User already exist","406 error");
return new ResponseEntity<>(userError, HttpStatus.CONFLICT);
}
if(userService.userSave(user)) { // make it return boolean on success or failed
return new ResponseEntity<>(user, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}

How to dynamically route an incoming request by using a URL->responsible-backend-service mapping service?

I am new to spring-cloud-gateway and I can't answer the question if I solved my problem the intended way. I hope someone can point me into the right direction, give advice or provide some exemplary sample code.
Requirement:
An incoming request at my spring-cloud-gateway service shall be forwarded to the correct backend service (there are X of such backend services, each responsible for a specific task).
The request itself does not contain enough information to decide which backend service to route to.
An additional REST-service exists that maps an arbitrary URL-to-responsible-backend-service-name. The response format is some small JSON, containing the name of the backend-service to forward to.
What would be the easiest/best/smartest/intended solution to implement this functionality with spring-cloud-gateway?
I tried implementing a GatewayFilter that first calls the mapping-service and depending on the result set GATEWAY_REQUEST_URL_ATTRfor the exchange.
This works ok. But I have additional questions.
Is it possible to omit the .uri("not://needed")) part in the route setup?
Why does the order value need to be higher than 9999? (see code example)
#SpringBootApplication
#RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r
.alwaysTrue()
.filters(f -> f.filter(new CustomRequestFilter()))
.uri("not://needed")) // how to omit ?
.build();
}
public static class CustomRequestFilter implements GatewayFilter, Ordered {
#Override
public int getOrder() {
return 10000; // why order > 9999 ? (below 10000 does not work)
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return getBackendServiceMappingResult(exchange.getRequest().getURI().toString()) //async call to REST service mapping URL->backend service name
.flatMap(mappingResult -> {
URI uri = mapServiceNameToBackendService(mappingResult.getServiceName());
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, uri);
return chain.filter(exchange);
});
}
private URI mapServiceNameToBackendService(String serviceName) {
try {
switch (serviceName) {
case "serviceA": return new URI("http://httpbin.org:80/get");
case "serviceB": return new URI("https://someotherhost:443/");
}
} catch (URISyntaxException e) {
//ignore
}
return null;
}
}
static class MappingResult {
String serviceName;
public String getServiceName() {
return serviceName;
}
}
static Mono<MappingResult> getBackendServiceMappingResult(String uri) {
WebClient client = WebClient.create("http://localhost:8080");
return client.get().uri(uriBuilder -> uriBuilder.path("/mapping").queryParam("uri", uri).build()).retrieve().bodyToMono(MappingResult.class);
}
}
Is there a better approach (with spring-cloud-gateway) to solve the requirement?
You can use this
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes().route(r ->
r.alwaysTrue()
.filters(f-> {
f.changeRequestUri(serverWebExchange -> buildRequestUri(serverWebExchange, CustomRequestFilter));
return f;
})
.uri(env.getProperty("birdeye.platform.url")))
.build();
private Optional buildRequestUri(ServerWebExchange exchange,CustomRequestFilter customRequestFilter ){
boolean updateuri = true;
if(updateuri) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
try {
uriBiulder = UriComponentsBuilder.fromUri(new URI(backendUri)).path(backendPath)
} catch (URISyntaxException e) {
e.printStackTrace();
}
}else {
uriBiulder = new URI("https://paytm.com:8080/category/create")
}
return Optional.of(uriBiulder);
}

Firebase in jax-rs or servlet

How can I use the Firebase java client from within a jax-rs service?
Let's say I make a the following call? That's an async callback?
authClient.checkAuthStatus(new SimpleLoginAuthenticatedHandler() {
#Override
public void authenticated(FirebaseSimpleLoginError error, FirebaseSimpleLoginUser user) {
if (error != null) {
result = "{\"result\" : \"Error checking authentication\"}";
} else if (user == null) {
result = "{\"result\" : \"Not logged in\"}";
} else {
deferred.resolve("done");
}
}
});
The jax-rs resource is returned before the completion of that async method. Is there any way to make this work?

ResponseEntity Spring MVC

What is the best way to return an error message with ReponseEntity?
Say I have the following method
#Transactional
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<User> getUser(#PathVariable("id") Long id) {
User user = userRepository.findOne(id);
if (user == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
else {
return new ResponseEntity<>(user, HttpStatus.OK);
}
Now what if I want to return an error message to the front end? I can't do the following because the method return type is
ResponseEntity<User>
not
ResponseEntity<String>
so this will not work
if (user == null) {
return new ResponseEntity<>("User does not exist", HttpStatus.NOT_FOUND);
}
I could make the method return type
ResponseEntity<Object>
but that just seems slopy and bad practice. I want to be able to return at least a brief error message that gives some indication of what went wrong to the front end. How is the best way to go about doing this?
Update:
After some digging around I came up with this and it seems to work but curious if it would have a performance impact.
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<?> getUser(#PathVariable("id") Long id) {
User user = userRepository.findOne(id);
if (user == null) {
return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND);
}
else {
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
I realise you asked specifically about returning the error message using ReponseEntity, but you could also consider using Spring MVCs exception handling to acheive the same outcome:
// Example from the linked Spring article:
#RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(#PathVariable("id") long id, Model model) {
Order order = orderRepository.findOrderById(id);
if (order == null) throw new OrderNotFoundException(id);
model.addAttribute(order);
return "orderDetail";
}
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}