The best way to repeat the request in Project reactor - spring-boot

I am quite new in reactive programming.
We have an application in SpringBoot using project-reactor. Inside this, we make an HTTP request to a third party service and get a Mono as a result.
fun getResultFromService() : Mono<Result> {
//requesting the third party REST API
}
I would like to:
check the response code
for some values repeat a request M times with N seconds difference between them
What is the best way to do it?
what should I use instead of Thread.sleep()
I checked repeatWhenEmpty, I don't think it is suitable only for M tries.

The key is that you need to convert the error response code into an error on Mono. After doing this you can use the retryBackoff operator mentioned by Michael Berry in the comments.
fun main()
{
getResultFromService()
.flatMap {
if (it.statusCode == 500 )
Mono.error(RuntimeException("Error which should be retried"))
else Mono.just(it)
}.retryBackoff(3, Duration.ofMillis(500), Duration.ofMillis(500))
.block()
}
fun getResultFromService() : Mono<Result>
{
//requesting the third party REST API
TODO("Implement it.")
}
data class Result(
val statusCode: Int,
val response: Any
)

Related

How to run multiple coroutines in Kotlin in parallel without waiting for result?

I have a service, which send messages to multiple users. I calling send method in for-each cycle and I want to parallel this operations without waiting a result. I've wrote some code, but i need your comment if i implemented it correctly. Use Kotlin + Spring 5.
Service A (singleton service):
fun send(users: List<User>) {
CoroutineScope(Dispatchers.Default).launch {
users.forEach {
launch(SupervisorJob() + MDCContext()) {
val messageText = "Hello, my friend!"
chatMessageSender.send(it, messageText)
}
}
}
}
chatMessageSender - external service, called via RestTemplate
Kotlin playground: Click here
l just want to point a couple of parts of your codes.
1- If you want to handle exceptions during sending message you can use try catch in launch block like that.
launch(SupervisorJob() + MDCContext()) {
try{
val messageText = "Hello, my friend!"
chatMessageSender.send(it, messageText)
}catch(e: Throwable){
//message could not be sent to it user
}
}
2-launch(SupervisorJob() + MDCContext()) Do you really need MDCContext here? l guess you are not using it.
3- Maybe you can consider about using Dispatchers.IO instead of Dispatchers.Default. You can find comparison of this two in here
https://medium.com/#bhavnathacker14/deep-dive-into-dispatchers-for-kotlin-coroutines-f38527bde94c

Spring Rest API calling service with Deffered<>

my code functions, but not sure if I am implementing it properly.
I have a service layer that calls Youtrack API using Retrofit, does some post-filtering and returns a list of issues. The code below is simplified version, but should be enough to make a picture.
suspend fun getProjectIssuesFromYt(
project: String,
youTrackInstance: YouTrackInstance,
after: Int,
max: Int
): List<Issues> = coroutineScope {
val service = Youtrack.getYoutrack(youTrackInstance).service
val deferreds: Deferred<List<Issues>> =
async(Dispatchers.Default) {
service.getIssuesByProject(
project = project, max = max,
after = after
).bodyList()
}
deferreds.await()
}
How can I call this service from REST api? The only solution that is functioning is to call it with runBlocking, but I don't think this ia a way to go.
#GetMapping("/getProjectIssuesFromYt")
fun getProjectIssuesFromYt(
project: String,
youTrackInstance: YouTrackInstance,
after: Int,
max: Int
): List<Issues> = runBlocking {
clientService.getProjectIssuesFromYt(
project = project,
youTrackInstance = youTrackInstance,
after = after,
max = max
)
}
I did try making my controller function suspended and running it without runBlocking, but I am getting an error.
"Parameter specified as non-null is null: method kotlinx.coroutines.internal.ScopeCoroutine.<init>, parameter context",
Thank you in advance,
Marko
If you are using Spring MVC:
Kotlin Coroutines are only available as an alternative to Java's Project Reactor in Spring Web Flux.
Spring MVC Controllers are blocking by design that is why they cannot be implemented as suspending functions.
https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow

Spring WebFlux + Kotlin Response Handling

I'm having some trouble wrapping my head around a supposedly simple RESTful WS response handling scenario when using Spring WebFlux in combination with Kotlin coroutines. Suppose we have a simple WS method in our REST controller that is supposed to return a possibly huge number (millions) of response "things":
#GetMapping
suspend fun findAllThings(): Flow<Thing> {
//Reactive DB query, return a flow of things
}
This works as one would expect: the result is streamed to the client as long as a streaming media type (e.g. "application/x-ndjson") is used. In more complex service calls that also accounts for the possibility of errors/warnings I would like to return a response object of the following form:
class Response<T> {
val errors: Flow<String>
val things: Flow<T>
}
The idea here being that a response either is successful (returning an empty error Flow and a Flow of things), or failed (errors contained in the corresponding Flow while the things Flow being empty). In blocking programming this is a quite common response idiom. My question now is how can I adapt this idiom to the reactive approach in Kotlin/Spring WebFlux?
I know its possible to just return the Response as described (or Mono<Response> for Java users), but this somewhat defeats the purpose of being reactive as the entire Mono has to exist in memory at serialization time. Is there any way to solve this? The only possible solution I can think of right now is a custom Spring Encoder that is smart enough to stream both errors or things (whatever is present).
How about returning Success/Error per Thing?
class Result<T> private constructor(val result: T?, val error: String?) {
constructor(data: T) : this(data, null)
constructor(error: String) : this(null, error)
val isError = error != null
}
#GetMapping
suspend fun findAllThings(): Flow<Result<Thing>> {
//Reactive DB query, return a flow of things
}

Mirror #RequestPart behavior in WebFlux functional router definitions with different content types

Problem
We're developing a Spring Boot service to upload data to different back end databases. The idea is that, in one multipart/form-data request a user will send a "model" (basically a file) and "modelMetadata" (which is JSON that defines an object of the same name in our code).
We got the below to work in the WebFlux annotated controller syntax, when the user sends the "modelMetadata" in the multipart form with the content-type of "application/json":
#PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
fun saveModel(#RequestPart("modelMetadata") monoModelMetadata: Mono<ModelMetadata>,
#RequestPart("model") monoModel: Mono<FilePart>,
#RequestHeader headers: HttpHeaders) : Mono<ResponseEntity<ModelMetadata>> {
return modelService.saveModel(monoModelMetadata, monoModel, headers)
}
But we can't seem to figure out how to do the same thing in Webflux's functional router definition. Below are the relevant code snippets we have:
#Bean
fun modelRouter() = router {
accept(MediaType.MULTIPART_FORM_DATA).nest {
POST(ROOT, handler::saveModel)
}
}
fun saveModel(r: ServerRequest): Mono<ServerResponse> {
val headers = r.headers().asHttpHeaders()
val monoModelPart = r.multipartData().map { multiValueMap ->
it["model"] // What do we do with this List<Part!> to get a Mono<FilePart>
it["modelMetadata"] // What do we do with this List<Part!> to get a Mono<ModelMetadata>
}
From everything we've read, we should be able to replicate the same functionality found in the annotation controller syntax with the router functional syntax, but this particular aspect doesn't seem to be well documented. Our goal was to move over to use the new functional router syntax since this is a new application we're developing and there are some nice forward thinking features/benefits as described here.
What we've tried
Googling to the ends of the Earth for a relevant example
this is a similar question, but hasn't gained any traction and doesn't relate to our need to create an object from one piece of the multipart request data
this may be close to what we need for uploading the file component of our multipart request data, but doesn't handle the object creation from JSON
Tried looking at the #RequestPart annotation code to see how things are done on that side, there's a nice comment that seems to hint at how they are converting the parts to objects, but we weren't able to figure out where that code lives or any relevant example of how to use an HttpMessageConverter on the ``
the content of the part is passed through an {#link HttpMessageConverter} taking into consideration the 'Content-Type' header of the request part.
Any and all help would be appreciated! Even just some links for us to better understand Part/FilePart types and there role in multipart requests would be helpful!
I was able to come up with a solution to this issue using an autowired ObjectMapper. From the below solution I could turn the modelMetadata and modelPart into Monos to mirror the #RequestPart return types, but that seems ridiculous.
I was also able to solve this by creating a MappingJackson2HttpMessageConverter and turning the metadataDataBuffer into a MappingJacksonInputMessage, but this solution seemed better for our needs.
fun saveModel(r: ServerRequest): Mono<ServerResponse> {
val headers = r.headers().asHttpHeaders()
return r.multipartData().flatMap {
// We're only expecting one Part of each to come through...assuming we understand what these Parts are
if (it.getOrDefault("modelMetadata", listOf()).size == 1 && it.getOrDefault("model", listOf()).size == 1) {
val modelMetadataPart = it["modelMetadata"]!![0]
val modelPart = it["model"]!![0] as FilePart
modelMetadataPart
.content()
.map { metadataDataBuffer ->
// TODO: Only do this if the content is JSON?
objectMapper.readValue(metadataDataBuffer.asInputStream(), ModelMetadata::class.java)
}
.next() // We're only expecting one object to be serialized from the buffer
.flatMap { modelMetadata ->
// Function was updated to work without needing the Mono's of each type
// since we're mapping here
modelService.saveModel(modelMetadata, modelPart, headers)
}
}
else {
// Send bad request response message
}
}
Although this solution works, I feel like it's not as elegant as the one alluded to in the #RequestPart annotation comments. Thus I will accept this as the solution for now, but if someone has a better solution please let us know and I will accept it!

Can I access the request/response body on an ExchangeFilterFunction?

Given an exchange using WebClient, filtered by a custom ExchangeFilterFunction:
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return next.exchange(request)
.doOnSuccess(response -> {
// ...
});
}
Trying to access the response body more than once using response.bodyToMono() will cause the underlying HTTP client connector to complain that only one receiver is allowed. AFAIK, there's no way to access the body's Publisher in order to cache() its signals (and I'm not sure it'd be a good idea, resource-wise), as well as no way to mutate or decorate the response object in a manner that allows access to its body (like it's possible with ServerWebExchange on the server side).
That makes sense, but I am wondering if there are any ways I could subscribe to the response body's publisher from a form of filter such as this one. My goal is to log the request/response being sent/received by a given WebClient instance.
I am new to reactive programming, so if there are any obvious no-nos here, please do explain :)
Only for logging you could add a wiretap to the HttpClient as desribed in this answer.
However, your question is also interesting in a more general sense outside of logging.
One possible way is to create a duplicate of the ClientResponse instance with a copy of the previous request body. This might go against reactive principles, but it got the job done for me and I don't see big downsides given the small size of the response bodies in my client.
In my case, I needed to do so because the server sending the request (outside of my control) uses the HTTP status 200 Ok even if requests fail. Therefore, I need to peek into the response body in order to find out if anything went wrong and what the cause was. In my case I evict a session cookie in the request headers from the cache if the error message indicates that the session expired.
These are the steps:
Get the response body as a Mono of a String (cf (1)).
Return a Mono.Error in case an error is detected (cf (2)).
Use the String of the response body to build a copy of the original response (cf (3)).
You could also use a dependency on the ObjectMapper to parse the String into an object for analysis.
Note that I wrote this in Kotlin but it should be easy enough to adapt to Java.
#Component
class PeekIntoResponseBodyExchangeFilterFunction : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return next.exchange(request)
.flatMap { response ->
// (1)
response.bodyToMono<String>()
.flatMap { responseBody ->
if (responseBody.contains("Error message")) {
// (2)
Mono.error(RuntimeException("Response contains an error"))
} else {
// (3)
val clonedResponse = response.mutate().body(responseBody).build()
Mono.just(clonedResponse)
}
}
}
}
}

Resources