Webclient retryWhen and onErrorResume mutually exclusive? - spring

I am trying to implement a retry on specific exception but I could not make it work using the following:
return client
.sendWebhook(request, url)
.exchangeToMono(
response -> {
final HttpStatus status = response.statusCode();
return response
.bodyToMono(String.class)
.defaultIfEmpty(StringUtils.EMPTY)
.map(
body -> {
if (status.is2xxSuccessful()) {
log.info("HTTP_SUCCESS[{}][{}] body[{}]", functionName, company, body);
return ResponseEntity.ok().body(body);
} else {
log.warn(
format(
"HTTP_ERROR[%s][%s] status[%s] body[%s]",
functionName, company, status, body));
return status.is4xxClientError()
? ResponseEntity.badRequest().body(body)
: ResponseEntity.internalServerError().body(body);
}
});
})
.retryWhen(
Retry.backoff(1, Duration.ofSeconds(1))
.filter(
err -> {
if (err instanceof PrematureCloseException) {
log.warn("PrematureCloseException detected retrying.");
return true;
}
return false;
}))
.onErrorResume(
ex -> {
log.warn(
format(
"HTTP_ERROR[%s][%s] errorInternal[%s]",
functionName, company, ex.getMessage()));
return Mono.just(ResponseEntity.internalServerError().body(ex.getMessage()));
});
It seems that the retry is never getting called on PrematureCloseException.

Resolved, it was not working because of rootCause
Retry.backoff(3, Duration.ofMillis(500))
.filter(
ex -> {
if (ExceptionUtils.getRootCause(ex) instanceof PrematureCloseException) {
log.info(
"HTTP_RETRY[{}][{}] PrematureClose detected retrying", functionName, company);
return true;
}
return false;
});

Related

How to mock callbacks with mockito correctly?

I have a method like this:
override fun functionToBeMocked(
param: Param,
onSuccess: (param: Param) -> Unit,
onError: (param: Param, errorMessage: String) -> Unit
) {
val response = factory.request(param)
.exchange()
.block()
if (response?.statusCode()?.is2xxSuccessful == true {
onSuccess(param)
} else if (response?.statusCode()?.isError == true) {
val status = response.rawStatusCode()
val body = response.bodyToMono(String::class.java).block()
val errorMessage = "$status : $body"
onError(param, errorMessage)
} else {
return
}
}
I want to test the service which calls this method with the given onSuccess and onError functions. How can I mock functionToBeMocked() to just return onSuccess(param) or onError(param)?
Current test:
#Test
fun test() {
val failure = ParamDoc(param.id, param)
whenever(repo.findAll()).thenReturn(listOf(failure))
// This method call should just execute onSuccess() or onError depending on the testcase
// whenever(mockedService.functionToBeMocked).thenAnswer.. (?)
underTest.functionToBeTested()
// verify(..)
}
Update: request function in factory:
fun request(param: Param): WebClient.RequestHeadersSpec<*> {
val client = WebClient
.builder()
.defaultHeader("API-KEY", config.apiKey)
.baseUrl(config.baseUrl)
.build()
return client
.post()
.uri("/service/test")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Dto.fromParam(param))
}

How to call multiple completablefuture concurrently after main completable future completed

How to enhance function below, where getB, getC, getD have dependency with A, and required to wait A to complete before call.
However i wish to call B C D concurrently after A completed.
Thankyou for all the helps
note: all dbService return a completableFuture
CompletableFuture<List<A>> a= dbService.getA(request);
a.thenApply(a-> {
try {
return dbService.getB(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.thenAccept (result->{
//do something with B
});
a.thenApply(a-> {
try {
return dbService.getC(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.thenAccept (result->{
//do something with C
});
a.thenApply(a-> {
try {
return dbService.getD(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.thenAccept (result->{
//do something with D
});
If you don't care about the returns of B, C, D then:
CompletionStage<List<A>> aFuture = dbService.getA(request);
aFuture.whenCompleteAsync((a, ex) -> {
if (ex != null) {
dbService.getB(a);
// ...
} else {
// ...
}
});
aFuture.whenCompleteAsync((a, ex) -> {
if (ex != null) {
dbService.getC(a);
// ...
} else {
// ...
}
});
aFuture.whenCompleteAsync((a, ex) -> {
if (ex != null) {
dbService.getD(a);
// ...
} else {
// ...
}
});
Your program will return to the main event loop as soon as dbService.getA(request) kicks off, i.e. before it even completes. And when that does complete, each of the 3 blocks execute (due to having been queued by whenComplete) on different threads (than main and each other, due to the Async).
If you do want to do something with the returns of B, C, and D, then:
CompletionStage<List<A>> aFuture = dbService.getA(request);
CompletionStage<B> bFuture = aFuture.thenApplyAsync(a -> {
return dbService.getB(a);
});
CompletionStage<C> cFuture = aFuture.thenApplyAsync(a -> {
return dbService.getC(a);
});
CompletionStage<D> dFuture = aFuture.thenApplyAsync(a -> {
return dbService.getD(a);
});
CompletionStage<Something> sFuture = bFuture.thenCompose(b -> {
cFuture.thenCompose(c -> {
dFuture.thenApply(d -> {
// do something with b, c, d
return new Something();
});
});
});
You can shorten this using thenCombine, which is like a thenApply that waits on 2 futures:
CompletionStage<List<A>> aFuture = dbService.getA(request);
CompletionStage<B> bFuture = aFuture.thenApplyAsync(a -> {
return dbService.getB(a);
});
CompletionStage<C> cFuture = aFuture.thenApplyAsync(a -> {
return dbService.getC(a);
});
CompletionStage<D> dFuture = aFuture.thenApplyAsync(a -> {
return dbService.getD(a);
});
CompletionStage<Something> sFuture = bFuture.thenCompose(b -> {
cFuture.thenCombine(dFuture, (c, d) -> {
// do something with b, c, d
return new Something();
});
});
To extend this to any number of concurrent stages, I like to use Spotify's CompletableFutures package like this:
CompletionStage<List<A>> aFuture = dbService.getA(request);
CompletionStage<B> bFuture = aFuture.thenApplyAsync(a -> {
return dbService.getB(a);
});
CompletionStage<C> cFuture = aFuture.thenApplyAsync(a -> {
return dbService.getC(a);
});
CompletionStage<D> dFuture = aFuture.thenApplyAsync(a -> {
return dbService.getD(a);
});
CompletionStage<Something> sFuture = CompletableFutures.combine((bFuture, cFuture, dFuture) -> {
// do something with b, c, d
return new Something();
}

Spring webClient generate NullPointException after going through the filter

I want to do exception handling. When StatusCode 200, but not the desired body.
To make the processing global I am using filter.
below is the code.
public WebClient webClient() {
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
// statusCode 400 || 500
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(body ->
Mono.error(new ExternalClientException(body))
)
);
}
// statusCode 200
return response.bodyToMono(String.class)
.flatMap(body -> {
if (YanoljaConstants.EMPTY_BODY.contains(body)) {
return Mono.error(new ExternalClientException(body))
.cast(ClientResponse.class);
}
return Mono.just(response); // normal
});
}))
.build();
}
And the actual client code is shown below.
public Foo getPlace(int no) {
return Objects.requireNonNull(contentsApiV2Client.get()
.uri(uriBuilder -> uriBuilder.path("/path").build(no))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Foo>() {
})
.block());
}
When using this, NullPointerException occurs when responding normally after going through the filter(return Mono#just(response)).
I want clear this issue.
For reference, the below code works normally.
public WebClient webClient() {
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
// statusCode 400 || 500
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(body ->
Mono.error(new ExternalClientException(body))
)
);
}
return Mono.just(response); // normal
}))
.build();
}
Thank you. waiting for your answer!
Solve!
public WebClient webClient() {
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(response -> {
// statusCode 400 || 500
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(body ->
Mono.error(new ExternalClientException(body))
)
);
}
// statusCode 200
return response.bodyToMono(String.class)
.flatMap(body -> {
if (YanoljaConstants.EMPTY_BODY.contains(body)) {
return Mono.error(new ExternalClientException(body))
.cast(ClientResponse.class);
}
return Mono.just(ClientResponse.create(HttpStatus.OK)
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(body)
.build()); // normal
});
}))
.build();
}
I realized that running the monoTobody method consumes it and the value disappears. So I solved it by creating a new ClientResponse.
Thank you.

How can i return rejected from async-await?

I would like to return rejected, when fetch request is fail. How can i reject async await?
class FetchUrl {
static async getJson(api) {
try {
const response = await fetch(api);
if (response.ok) {
const questions = await response.json();
return questions;
}
} catch (error) {
throw new Error("Request Failed!");
}
}
}
FetchUrl.getJson(this.api).then((resolved) => {
console.log(resolved)
// this sampe of code is executing even fetch is rejected.. what can i do
to avoid it?
}, rejected => {
console.log(rejected)
})
}

Alamofire custom reponse migration from Alamofire v1.3 to 3.0 (and Swift 2 syntax)

I am converting an project from Swift 1.1 to Swift 2.0 that used Alamofire 1.3.
Because this version of Alamofire was not compatible with Swift 2.0, I switched to the very last version of Alamofire (3.1.x).
However this broke the code, I am now getting the error message
"Cannot call value of non-function type 'NSHTTPURLResponse?'"
which is due to my my extension to create a custom "responseShop" Alamofire.Request.
How can this Alamofire.Request extension correctly be converted to the Alamofire 3.x/Swift 2.0 syntax?
extension Alamofire.Request {
func responseShop(options: NSJSONReadingOptions = .AllowFragments, var errorHandler:ProtocolWebServiceErrorHandler?, completionHandler: (ShopCallResult?, WebServiceErrorCode) -> Void ) -> Self {
objc_sync_enter(communicationLockObj)
return response(serializer: Request.JSONResponseSerializer(options: options), completionHandler: {
(request, response, JSON, error) in
log.verbose("\(JSON)")
//network error?
if let error = error {
//is it cancelled?
if error.code == -999 {
dispatch_async(dispatch_get_main_queue()) {
//call handler with cancelled code
completionHandler(nil, WebServiceErrorCode.requestCancelled)
}
objc_sync_exit(communicationLockObj)
return
}
dispatch_async(dispatch_get_main_queue()) {
errorHandler?.handleWebServiceError(WebServiceErrorCode.connectError, errorText : error.localizedDescription, request: request, json: JSON)
//call and supply org error
completionHandler(nil, WebServiceErrorCode.connectError)
}
objc_sync_exit(communicationLockObj)
return
}
if JSON == nil {
dispatch_async(dispatch_get_main_queue()) {
errorHandler?.handleWebServiceError(WebServiceErrorCode.jsonNil, errorText: nil, request: request, json: JSON)
//call and supply org error
completionHandler(nil, WebServiceErrorCode.jsonNil)
}
objc_sync_exit(communicationLockObj)
return
}
//api return error?
let callResult = ShopCallResult(json: JSON!)
if callResult.statusCode.failed {
dispatch_async(dispatch_get_main_queue()) {
errorHandler?.handleWebServiceError(callResult.statusCode, errorText: callResult.statusCode.localizedText, request: request, json: JSON)
completionHandler(callResult, callResult.statusCode)
}
objc_sync_exit(communicationLockObj)
return
}
//no error
dispatch_async(dispatch_get_main_queue()) {
completionHandler(callResult, WebServiceErrorCode.OK)
}
objc_sync_exit(communicationLockObj)
})
return self
}
}
Based on the feedback of #ProblemSolver I managed to convert the code, now it looks like this:
extension Alamofire.Request {
func responseShop(queue: dispatch_queue_t? = nil, options: NSJSONReadingOptions = .AllowFragments, errorHandler:ProtocolWebServiceErrorHandler?, completionHandler: (ShopCallResult?, WebServiceErrorCode) -> Void ) -> Self {
//enter thread protected area...
objc_sync_enter(communicationLockObj)
return responseJSON() {
response in
switch response.result {
case .Success(let JSON):
//log json in verbose mode
log.verbose("\(JSON)")
//parse the returned json
let callResult = ShopCallResult(json: JSON)
//is it failed?
if callResult.statusCode.failed {
//call supplied error handler on the main thread
dispatch_async(dispatch_get_main_queue()) {
errorHandler?.handleWebServiceError(callResult.statusCode, errorText: callResult.statusCode.localizedText, request: self.request!, json: JSON)
completionHandler(callResult, callResult.statusCode)
}
//release the lock
objc_sync_exit(communicationLockObj)
//processing done!
return
}
//no error call handler on main thread
dispatch_async(dispatch_get_main_queue()) {
completionHandler(callResult, WebServiceErrorCode.OK)
}
//release the lock
objc_sync_exit(communicationLockObj)
case .Failure(let error):
//WARNING: cancelled is still error code 999?
//is it cancelled?
if error.code == -999 {
//just call the completion handler
dispatch_async(dispatch_get_main_queue()) {
//call handler with cancelled code
completionHandler(nil, WebServiceErrorCode.requestCancelled)
}
//release the lock
objc_sync_exit(communicationLockObj)
//stop furhter processing
return
}
//error with the json?
if error.code == Alamofire.Error.Code.JSONSerializationFailed .rawValue {
//call the error handler
dispatch_async(dispatch_get_main_queue()) {
errorHandler?.handleWebServiceError(WebServiceErrorCode.jsonNil, errorText: nil, request: self.request!, json: nil)
//call and supply org error
completionHandler(nil, WebServiceErrorCode.jsonNil)
}
//release the lock
objc_sync_exit(communicationLockObj)
//stop further processing
return
}
//must be some other kind of network error
dispatch_async(dispatch_get_main_queue()) {
log.error("\(error)")
//so call the error handler
errorHandler?.handleWebServiceError(WebServiceErrorCode.connectError, errorText : error.localizedDescription, request: self.request!, json: nil)
//call and supply org error
completionHandler(nil, WebServiceErrorCode.connectError)
}
//release the lock
objc_sync_exit(communicationLockObj)
}
}
}
}

Resources