How can I convert a Stream of Mono to Flux - spring-webflux

I have a method that try use WebClient to return a Mono
#GetMapping("getMatch")
public Mono<Object> getMatch(#RequestParam Long matchId) {
return WebClient.create(OpenDotaConstant.BASE_URL).get()
.uri("/matches/{matchId}", matchId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Object.class);
}
It can return result that I expected.
Then I try to create another method to support List as params
#GetMapping("getMatches")
public Flux<Object> getMatches(#RequestParam String matchesId) {
List<Long> matchesList = JSON.parseArray(matchesId, Long.class);
return Flux.fromStream(matchesList.parallelStream().map(this::getMatch));
}
But this time return a weird result.
[
{
"scanAvailable": true
},
{
"scanAvailable": true
}
]
I'm new to reactive-programming, What is the correct way to combine Stream and Mono,and then convert to the Flux?

Probably, what you need is the following:
#GetMapping("getMatches")
public Flux<Object> getMatches(#RequestParam String matchesId) {
List<Long> matchesList = JSON.parseArray(matchesId, Long.class);
return Flux.fromStream(matchesList.stream())
.flatMap(this::getMatch);
}
Instead of:
#GetMapping("getMatches")
public Flux<Object> getMatches(#RequestParam String matchesId) {
List<Long> matchesList = JSON.parseArray(matchesId, Long.class);
return Flux.fromStream(matchesList.parallelStream().map(this::getMatch));
}
Notes:
Basically, you expect getMatches endpoint to return Flux<Object>. However, as it is written - it actually returns Flux<Mono<Object>>, therefore you see the strange output. To get Flux<Object>, I suggest, first, create Flux<Long> that are match ids, and then flatMap the result of calling getMatch (that returns Mono<Object>), this finally gives Flux<Object>.
Also, there is no need to use parallelStream(). Because you're already using reactor, everything will be performed concurrently on reactor scheduler.

Related

Spring WebFlux App, endpoint return List of Mono<?>

I have two methods, method A returns a Mono, Method B calls method A x number of times, and need to return all the results from calling method A
i tried:
`
public Mono<String> methodA() {
return Mono.just(Math.random()+"");
}
#GetMapping("/methodB/{amount}")
public Mono<List<String>> methodB(#PathVariable("amount") int x) {
Mono<List<String>> strs = Mono.just(new ArrayList<>());
for(int i=0;i<x;i++) {
strs.zipWith(methodA())
.map(t->{
t.getT1().add(t.getT2());
return t.getT1();
}
}
return strs;
}
`
when i hit the endpoint with browser, all i get is
[]
what should methodB return? how do I combine the results of calling A so that the calling Client of /methodB will get a list of String?
Thanks in advance.
Change Mono.just to Mono.fromCallable to generate new random number for each subscription. Use Mono.repeat to generate x number of random numbers.
public Mono<String> methodA() {
return Mono.fromCallable(() -> String.valueOf(Math.random()));
}
#GetMapping("/methodB/{amount}")
public Mono<List<String>> methodB(#PathVariable("amount") int x) {
return methodA()
.repeat(x)
.collectList();
}

webClient response using bodyToFlux method merging all the received response instead separating it out

I am using ParallelFlux for running many task.
but when i am receiving webClient response using bodyToFlux method its merging all the output response instead of getting one by one.
i want the output should be one by one not single string, is there any other method instead of bodyToFLux need to use.
request method:
Flux<String> responsePost = webClient.build()
//removed get,url and retrieve here
.bodyToFlux(String.class);
responsePost.subscribe(s -> {
//display response
});
response method:
public ParallelFlux<String> convertListToMap() {
//created list of string str
return Flux.fromIterable(str)
.parallel(3)
.runOn(Schedulers.parallel())
.map( s -> {
//some logic here
});
}
output:
parallel fulx reponse: springwebfluxparellelExample
Here instead of returning ParallelFlux create an
ResponseBody class with variable you want to return, assign your response to
variable inside ResponseBody and return it to caller of API function.
public ParallelFlux<ResponseBody> convertListToMap() {
//created list of string str
return Flux.fromIterable(str)
.parallel(3)
.runOn(Schedulers.parallel())
.map( s -> {
//some logic here
});
}
Flux<String> responsePost = webClient.build()
//removed get,url and retrieve here
.bodyToFlux(ResponseBody.class);

spring webflux, convert Flux to arraylist (or iterable list) [duplicate]

I have a repository that returns a flux and wanted to set the result to another object which is expecting a list. Is there any other way to get the results as a list without blocking?
The block is working but it is taking long time.
public class FluxToListTest {
#Autowired PostRepository postRepository;
public void setUserPosts(User user) {
user.setPostList(postRepository.findAllByOrderId(user.getId()).collectList().block());
}
}
interface PostRepository {
Flux<Post> findAllByOrderId(final UUID userId);
}
#Data
class User {
UUID id;
List<Post> postList;
}
class Post {
UUID id;
String content;
}
In short - NO.
You don't need to extract List from Flux.
If you've started use Reactor Streams - stay with it.
Try this code:
public void setUserPosts(User user) {
postRepository.findAllByOrderId(user.getId())
.collectList()
.doOnNext(user::setPostList)// (1)
.subscribe(); // (2)
}
if you set operation is blocking please use publishOn/subscribeOn to avoid blocking for the all stream.
it starts your stream performing

Converting String to Date when using functional WebFlux

When we send a URL with request parameters that needs to be converted to date, in SpringMVC we can do something like the code below in the controller and the fasterxml json library does the automatic conversion!
public String getFare(##RequestParam(value = "flightDate") #DateTimeFormat(iso = ISO.DATE) LocalDate date)
But how to achieve the same when we use the HandlerFunction (Spring webflux)? For example, in my HandlerFunction
public HandlerFunction<ServerResponse> getFare = serverRequest ->
{
Optional<String> flightDate = serverRequest.queryParam("flightDate");
}
The code serverRequest.queryParam("flightDate") gives a String. Is it possible to get the same automatic conversion here?
No. (you can look at Spring's source code and see that no other way to get the queryParams other than getting it as Optional<String>)
You must convert the field to Date yourself
Date flightDate = request.queryParam("flightDate ")
.map(date -> {
try {
return new SimpleDateFormat("dd-MMM-yyyy").parse(date);
} catch (ParseException e) {
return null;
}
}).orElse(null);

ServiceStack Redis client Get<T>(key) removes quotes from string data

I am using ServiceStack.Redis library to work with Redis. To start with, I have implemented this solution. The get/set methods work fine for plain text/string.
Now when I save a string with quotes (with escape char), it saves properly (I verify the same in redis-cli). But the Get method returns string having all the double quotes removed.
For example saving this string - "TestSample" is saved and get as expected. Also,
saving "TestSample \"with\" \"quotes\"" is fine and shows same in redis-cli. But the output of Get method becomes "TestSample with quotes"
public bool SetDataInCache<T>(string cacheKey, T cacheData)
{
try
{
using (_redisClient = new RedisClient(_cacheConfigs.RedisHost))
{
_redisClient.As<T>().SetValue(cacheKey, cacheData, new TimeSpan(0,0,300));
}
return true;
}
catch (Exception ex)
{
return false;
}
}
public T GetDataFromCacheByType<T>(string cacheKey)
{
T retVal = default(T);
try
{
using (_redisClient = new RedisClient(_cacheConfigs.RedisHost))
{
if (_redisClient.ContainsKey(cacheKey))
{
var wrapper = _redisClient.As<T>();
retVal = wrapper.GetValue(cacheKey);
}
return retVal;
}
}
catch (Exception ex)
{
return retVal;
}
}
Usage:
cacheObj.SetDataInCache("MyKey1","TestSample");
cacheObj.SetDataInCache("MyKey2","TestSample \"with\" \"quotes\"");
string result1 = Convert.ToString(cacheObj.GetDataFromCacheByType<string>("MyKey1"));
string result2 = Convert.ToString(cacheObj.GetDataFromCacheByType<string>("MyKey2"));
Actual : "TestSample with quotes"
Expected : "TestSample \"with\" \"quotes\""
The Typed Generic API is only meant for creating a generic Redis Client for serializing complex types. If you're implementing a generic cache you should use the IRedisClient APIs instead, e.g:
_redisClient.Set(cacheKey, cacheData, new TimeSpan(0,0,300));
Then retrieve back with:
var retVal = _redisClient.Get<T>(cacheKey);
Alternatively for saving strings or if you want to serialize the POCOs yourself you can use the IRedisClient SetValue/GetValue string APIs, e.g:
_redisClient.SetValue(cacheKey, cacheData.ToJson());
var retVal = _redisClient.GetValue(cacheKey).FromJson<T>();
Note: calling IRedisClient.ContainsKey() performs an additional unnecessary Redis I/O operation, since you're returning default(T) anyway you should just call _redisClient.Get<T>() which returns the default value for non-existing keys.