Java springboot reactive combining mono - java

I am writing a signup entry point, which want to return a dto with id and token. I have tried using flatmap instead of map in the controller,but the result is the same.The code have two part first is the signup(for saving and checking user) and second (find the user id).But
my problem is how to map the the result ,which involve two mono variable.
The code:
#PostMapping("/signup")
public Mono<AuthResponse> signup(#RequestBody user_info user) {
return tokenService.signup(user)
.doOnNext(System.out::println)
.switchIfEmpty(Mono.error(new Error("signup not work")))
.flatMap(token->{
return userRespository.findByyUsername(user.getUsername())
.doOnNext(System.out::println)
.switchIfEmpty(Mono.error(new Error("finding fail")))
.flatMap(userInfo ->
Mono.just(new AuthResponse(userInfo.getId(),token)));
});
AuthResponse:(dto)
#Data
#AllArgsConstructor
public class AuthResponse {
private Integer id;
private String token;
}
Error output:
2023-02-05T02:45:41.539+08:00 ERROR 147274 --- [tor-tcp-epoll-2] a.w.r.e.AbstractErrorWebExceptionHandler : [c841e0d1-1] 500 Server Error for HTTP POST "/signup"
java.lang.Error: finding fail
at com.springboot.sohinalex.java.Controller.apigatewayController.lambda$signup$1(apigatewayController.java:51) ~[classes/:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ Handler com.springboot.sohinalex.java.Controller.apigatewayController#signup(user_info) [DispatcherHandler]
*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContex
for reference:
signup function:
public Mono<String> signup(user_info user) {
log.info("signup start");
return Mono.just(user).doOnNext(System.out::println)
.switchIfEmpty(Mono.error(new RuntimeException()))
.flatMap(Monouser->{
Mono<Boolean> isuserexist=IsUsernameExist(Monouser.getUsername())
.doOnNext(System.out::println)
.switchIfEmpty(Mono.just(false)) //no user found => can register
.mapNotNull(res-> res
);
Monouser.setPassword( //encode the password
passwordEncoder.encode(user.getPassword()));
Mono<user_info> savedusr = respository.save(
Monouser).log();
return isuserexist.doOnNext(System.out::println) //check the username exist in my db
.switchIfEmpty(Mono.error(new RuntimeException()))
.flatMap(
res->{
log.info("start the map");
if(res){
log.info("error");
return null;
}
else {
savedusr.subscribe();//execute the saving user
log.info("check");
return reactiveAuthenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),user.getPassword()
)
).map(this::generateToken);
}
}
);

I have change the signup service instead to return the Authrespond with nested flatmap inside the service and it return what i want.Use nested flatmap to get the two variable and return it. Here for you with reference,any advice is welcomed:
signup controller:
#PostMapping("/signup")
public Mono<AuthResponse> signup(#RequestBody user_info user) {
return tokenService.signup(user);
}
signup service:
public Mono<AuthResponse> signup(user_info user) {
log.info("signup start");
// convert IsUserexist to boolean
Mono<Boolean> isuserexist=IsUsernameExist(user.getUsername())
.doOnNext(System.out::println)
.switchIfEmpty(Mono.just(false)) //no user found => can register
.mapNotNull(res-> res
);
return isuserexist
.switchIfEmpty(Mono.error(new Error("cant check username exist")))
.doOnNext(System.out::println)
. flatMap(
nameExist->{ //check username exist
if(nameExist){
log.info("repeated");
return Mono.empty();
} else
{
log.info("not repeat");
user.setPassword(passwordEncoder.encode(user.getPassword())); //encode the password
return respository.save(user)
.switchIfEmpty(Mono.error(new Error("cant save user")))
.doOnNext(System.out::println)
.flatMap(
savedUser->{
log.info("user saved with id: "+savedUser.getId());
return reactiveAuthenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),user.getPassword()
)
). switchIfEmpty(Mono.error(new Error("cant auth")))
.doOnNext(System.out::println)
.flatMap( //generate token
auth->{ //return id and token
String token=generateToken(auth);
log.info("token generated");
return Mono.just(new AuthResponse(savedUser.getId(), token));
});
}
);
}

Related

JSON decoding error: Invalid UTF-8 start byte 0xb0

I have a problem when I run the Junit for this method:
public ResponseEntity<InputStreamResource> xlsexp(#RequestHeader MultiValueMap<String, String> headerMap,
#RequestBody ExRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=example.xls");
return ResponseEntity
.ok()
.headers(headers)
.body(exportatorService.exportToXls(request));
}
And the test is:
public void testing_01() {
this.webClient.post()
.uri("/xlsexp")
.accept(MediaType.APPLICATION_JSON_UTF8)
.header(...., ....)
.
.
.body(BodyInserters.fromValue(request))
.exchange()
.expectStatus().isEqualTo(HttpStatus.OK)
.expectBody(ResponseEntity.class);
}
And the exception is:
org.springframework.core.codec.DecodingException: JSON decoding error: Invalid UTF-8 start byte 0xb0; nested exception is com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 start byte 0xb0
at [Source: (org.springframework.core.io.buffer.DefaultDataBuffer$DefaultDataBufferInputStream); line: 1, column: 12]
at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:242)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Body from POST /xlsexp [DefaultClientResponse]
Stack trace:
at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:242)
at org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:198)
at org.springframework.http.codec.json.AbstractJackson2Decoder.lambda$decodeToMono$1(AbstractJackson2Decoder.java:179)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:295)
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
My intention is test with Junit the method for export to Excel. Do you have any idea what could be happening?
Thanks

Bad Request when sending a WebClient request from a microservice to another microservice

So I have 5 microservices running on different ports. I have a service called the movie-catalogue-service
and I try to retrieve ratings from Movies by id which are passed via request parameters.
I have a discovery server running aswell, which works fine.
My endpoint of my function looks like this:
#GetMapping("/test")
fun testFun(#RequestParam movieIds:List<String>) : Flux<Rating> {
return movieCatalogService.findRatingByMovieId(movieIds)
}
My findRatingByMovieId looks like this:
fun findRatingByMovieId(movieIds: List<String>) : Flux<Rating> {
return webClient.build()
.get()
.uri { uribuilder ->
uribuilder.path("http://ratings-data-service/ratings/list")
.queryParam("movieIds", movieIds)
.build()
}
.retrieve()
.bodyToFlux(Rating::class.java)
}
My endpoint of my ratings-data-service looks like this:
#GetMapping("/list")
fun findRatingsByMovieIds(#RequestParam movieIds:List<String>) : Flux<Rating> {
return ratingsDataService.findRatingsByMovieId(movieIds)
}
And the service function:
fun findRatingsByMovieId(movieIds:List<String>) : Flux<Rating> {
return ratingsDataRepository.findAllById(movieIds)
}
When sending a request to localhost:8080/catalog/test?movieIds=6076bd2aa35f61406db0da84&movieIds=6076bd48a35f61406db0da85
I get the error in the
IntelliJ console like this:
400 Bad Request from UNKNOWN
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:179) ~[spring-webflux-5.3.5.jar:5.3.5]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 400 from GET localhost:7654/ratings/list?movieIds=6076bd2aa35f61406db0da84&movieIds=6076bd48a35f61406db0da85 [DefaultWebClient]
|_ checkpoint ⇢ Handler io.eraslan.moviecatalogservice.controller.MovieCatalogController#testFun(List) [DispatcherHandler]
|_ checkpoint ⇢ HTTP GET "/catalog/test?movieIds=6076bd2aa35f61406db0da84&movieIds=6076bd48a35f61406db0da85" [ExceptionHandlingWebHandler]
When I directly call the ratings-data-service endpoint:
localhost:7654/ratings/list?movieIds=6076bd2aa35f61406db0da84&movieIds=6076bd48a35f61406db0da85
everything works fine, how is this possible ?
For everyone who has a problem like this,with the help of aksappy, I have done it right in the end. I t has to look like this:
fun findRatingByMovieId(movieIds: List<String>) : Flux<Rating> {
return webClient.build()
.get()
.uri { uribuilder ->
uribuilder.host("ratings-data-service")
.path("/ratings/list")
.queryParam("movieIds",movieIds)
.build()
}
.retrieve()
.bodyToFlux(Rating::class.java)
}

How to handle OAuth webclient exceptions

I am pretty new to Spring and have been trying to catch unauthorized exceptions when authentication to a server by OAUTH. I don't understand why the method handleResponseError() doesn't catch the exception.
The stacktrace I get is:
org.springframework.security.oauth2.client.ClientAuthorizationException: [invalid_client] Client authentication failed
at org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider.lambda$authorize$0(ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java:82)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Request to GET https://localhost:8181/catalog/NL/brands?language_code=nl [DefaultWebClient]
Stack trace:
at org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider.lambda$authorize$0(ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java:82)
at reactor.core.publisher.Mono.lambda$onErrorMap$29(Mono.java:3272)
at reactor.core.publisher.Mono.lambda$onErrorResume$31(Mono.java:3362)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:88)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onError(FluxHide.java:132)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:185)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:251)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onError(FluxMapFuseable.java:134)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onError(FluxMapFuseable.java:134)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:135)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144)
The code to authenticate to the server:
#Bean
public WebClient myClient() {
InMemoryReactiveClientRegistrationRepository clientRegistryRepo = new InMemoryReactiveClientRegistrationRepository(getClientRegistration());
InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistryRepo);
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistryRepo, clientService);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFilter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauthFilter.setDefaultClientRegistrationId(OAUTH_PROVIDER_NAME);
return WebClient.builder()
.clientConnector(new JettyClientHttpConnector(createHttpClient()))
.exchangeStrategies(getMaxMessageInMemorySize(maxInMemorySize))
.baseUrl(baseURL)
.filter(oauthFilter)
.filter(handleResponseError())
.build();
}
private static ExchangeFilterFunction handleResponseError() {
return ExchangeFilterFunction.ofResponseProcessor(
response -> response.statusCode().isError() ?
response.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new MyUnAuthorizedRequestException(response.statusCode().name(), errorBody, ""))) :
Mono.just(response));
}
I have looked at various examples:
How to set the access token once during the instanciation of the webClient in spring webflux?
https://www.baeldung.com/spring-webclient-oauth2
I catch all the other exceptions using #ControllerAdvice. Is that the correct way to handle this?
I found the solution:
.... omitted
ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFilter = new
ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauthFilter.setDefaultClientRegistrationId(OAUTH_PROVIDER_NAME);
return WebClient.builder()
.clientConnector(new JettyClientHttpConnector(createHttpClient()))
.exchangeStrategies(getMaxMessageInMemorySize(maxInMemorySize))
.baseUrl(baseURL)
.filter(oauthFilter)
.build();
}
private ReactiveOAuth2AuthorizationFailureHandler getReactiveOAuth2AuthorizationFailureHandler() {
final ReactiveOAuth2AuthorizationFailureHandler reactiveOAuth2AuthorizationFailureHandler = (authorizationException, principal, attributes) -> {
if (authorizationException instanceof ClientAuthorizationException) {
ClientAuthorizationException clientAuthorizationException = (ClientAuthorizationException)authorizationException;
return Mono.error(new MyUnAuthorizedRequestException("401","Could not authorize client", clientAuthorizationException.getMessage()));
} else {
return Mono.empty();
}
};
return reactiveOAuth2AuthorizationFailureHandler;
}

Spring Reactive Web - Exceptions are always wrapped in 500

With a very simple project, error handling is unclear.
public class WebfluxApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxApplication.class, args);
}
}
public class EndpointRouter
{
#Bean
public WebClient webClient() {
return WebClient.builder().build();
}
#Bean
public RouterFunction<ServerResponse> goodRoute(Handler handler, ConfigProperties configProperties) {
log.info(configProperties.toString());
return
RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/ok"),
handler::goodEndpoint)
.and(
RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/notfound") {
handler::badEndpoint));
}
public Mono<ServerResponse> goodEndpoint(ServerRequest r) {
return ServerResponse.ok().build();
}
public Mono<ServerResponse> badEndpoint(ServerRequest r) {
var result = service.badEndpoint();
return ServerResponse
.ok()
.body(result, String.class);
}
public class Service
{
private final WebClient webClient;
private final ConfigProperties configProperties;
public Service(WebClient webClient, ConfigProperties configurationProperties) {
this.webClient = webClient;
this.configProperties = configurationProperties;
}
public Mono<String> badEndpoint() {
return webClient
.get()
.uri(configProperties.getNotfound())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> {
if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
"Entity not found."));
} else {
return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
}
})
.bodyToMono(String.class);
}
From reading the docs, I shouldn't need to set up a Global error handler for the entire project, I should be able to handle the 404, and return a 404 back to the original caller.
This is the output
2020-08-29 16:52:46.301 ERROR 25020 --- [ctor-http-nio-4] a.w.r.e.AbstractErrorWebExceptionHandler : [b339763e-1] 500 Server Error for HTTP GET "/api/v1/integration/notfound"
org.springframework.web.client.HttpClientErrorException: 404 Entity not found.
at com.stevenpg.restperformance.webflux.Service.lambda$badEndpoint$0(Service.java:30) ~[main/:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 404 from GET https://httpbin.org/status/404 [DefaultWebClient]
|_ checkpoint ⇢ Handler com.stevenpg.restperformance.webflux.EndpointRouter$$Lambda$445/0x00000008003ae040#7799b58 [DispatcherHandler]
|_ checkpoint ⇢ HTTP GET "/api/v1/integration/notfound" [ExceptionHandlingWebHandler]
I've also tried using onErrorResume on my Mono from the service, but it never works correct and requires a return of Mono rather than Mono.
The documentation and Stack Overflow don't have many/any examples of making a WebClient call inside a RouterFunction and handling different types of responses.
Just adding onErrorResume solves your problem. Otherwise error is handled in AbstractErrorWebExceptionHandler.
return webClient
.get()
.uri(configProperties.getNotfound())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> {
if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
"Entity not found."));
} else {
return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
}
})
.bodyToMono(String.class)
.onErrorResume( e -> Mono.just(e.getMessage()) );

WebClient builder Post Call giving bad request error for first time in Webflux

First post request giving bad request error for Webclient on first click.
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST
After that every request process succsfully.
exception i'm getting below
* Submitting xyz
* #param submitFlowRequest
*/
#Override
public Mono<ApiResponse<SubmitResponse>> submitFlow(SubmitFlowRequest submitFlowRequest,
Map<String, String> headers) {
long startTime = System.currentTimeMillis();
String uri = propertyConfig.getAggregationService()
+ propertyConfig.getAggregationSubmitCCInfoURL();
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(uri);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
MultiValueMap<String, String> clientHeaders = buildHeaders(headers);
return webClientBuilder.uriBuilderFactory(factory).build().post()
.headers(httpHeaders -> httpHeaders.addAll(clientHeaders)).accept(MediaType.APPLICATION_JSON)
.syncBody(submitFlowRequest).retrieve().bodyToMono(ApiResponse.class)
.onErrorMap(ConnectException.class,
error -> new VzwRuntimeException(ErrorCodeEnum.V404.toString(), Constants.OPP_TC_SYSTEM_ERROR,
(Constants.CONNECTION_FAILURE_TEXT + Constants.AGGREGATION)))
.onErrorMap(WebClientResponseException.class,
error -> new VzwRuntimeException(ErrorCodeEnum.V404.toString(), Constants.OPP_TC_SYSTEM_ERROR,
(Constants.CONNECTION_FAILURE_TEXT + Constants.AGGREGATION)))
.flatMap(res -> {
Audit apiAudit = Audit.builder().apiUrl(uri).request(LoggerUtil.asJson(submitFlowRequest))
.response(LoggerUtil.asJson(res))
.executionTime(String.valueOf(System.currentTimeMillis() - startTime))
.headers(LoggerUtil.asJson(clientHeaders)).transactionType(res.getData()!=null?mapper.map(res.getData(), SubmitResponse.class).getTransactionType():"").build();
LoggerUtil.logExternalApiCalls(apiAudit);
return Mono.just((ApiResponse<SubmitResponse>) res);
});
}
Exception :::
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST http://WKWIN93.global.sangwan.net:9898/payment-ag/v1/creditcard/submitCreditCardInfo
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:179)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 400 from POST http://payment-service/payment-ag/v1/creditcard/submitCreditCardInfo [DefaultWebClient]
Stack trace:
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:179)
at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:209)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100)
at com.sgw.common.logging.mdc.MDCContextHelper.onNext(MDCContextHelper.java:30)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1630)
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onComplete(FluxDefaultIfEmpty.java:100)
at com.sgw.common.logging.mdc.MDCContextHelper.onComplete(MDCContextHelper.java:46)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onComplete(FluxHide.java:137)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:144)
at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onComplete(FluxContextStart.java:122)

Categories