Scatter & Gather using Spring Webclient - spring

I am new to reactive programming concepts and trying to build one service that sends requests to a two backend service in parallel and combine those results.
Those two backend service has a different response structure and i have created a mapper method to convert all that into a common Response structure.
This is what i have right now and it is working when both the services return results.
public Mono<List<Response>> getRecords(String input){
List<Response> response = new ArrayList<>();
Mono<FirstApiResponse> gResp = this.firstWebClient.get().uri(uriBuilder -> uriBuilder
.path("/")
.queryParam("q", input)
.build()).retrieve()
.bodyToMono(FirstApiResponse.class).log()
.timeout(Duration.ofSeconds(50L));
Mono<SecondApiResponse> iResp = this.secondWebClient.get().uri(uriBuilder -> uriBuilder
.path("/search")
.queryParam("term", input)
.build()).retrieve()
.bodyToMono(SecondApiResponse.class).log().timeout(Duration.ofSeconds(50L));
return Mono.zip(firstResp,secResp).map(objects ->{
if(firstResp != null)
response.addAll(Mapper.convert(objects.getT1()));
if(secResp != null);
response.addAll(Mapper.convert(objects.getT2()));
return response;
});
}
public List<Response> convert(FirstApiResponse resp){
////
Mapping to Response object
////
return response;
}
public List<Response> convert(SecondApiResponse resp){
////
Mapping to Response object
////
return response;
}
I don't know if this is the right way to do it. Moreover, i want to make it in such a way that if there is any errors from any of this service, then it should still return the results from the other service. Right now it throws the exception and I am not able to figure out how to handle it properly
How to handle these errors in a proper way ?

This is a pretty valid scenario and there are many ways to handle it. One crude way would be to use onErrorReturn a new Model which you can handle. It could be either an empty response or a wrapper around your model whichever seems fit for your scenario.
Mono<Wrapper<FirstApiResponse>> gResp = this.firstWebClient.get().uri(uriBuilder -> uriBuilder
.path("/")
.queryParam("q", input)
.build()).retrieve()
.bodyToMono(FirstApiResponse.class).log()
.map( response -> new Wrapper().withResponse(response))
.timeout(Duration.ofSeconds(50L))
.doOnError(throwable -> logger.error("Failed", throwable))
.onErrorReturn(new Wrapper().withError( YourDefaultErrorReponse(...));
Mono<SecondApiResponse> iResp = this.secondWebClient.get().uri(uriBuilder -> uriBuilder
.path("/search")
.queryParam("term", input)
.build())
.retrieve()
.bodyToMono(SecondApiResponse.class).log()
.map( response -> new Wrapper().withResponse(response))
.timeout(Duration.ofSeconds(50L))
..doOnError(throwable -> logger.error("Failed", throwable))
.onErrorReturn(new Wrapper().withError( YourDefaultErrorReponse(...))
Again there are ways to return a default response. A simple one would be to use something like a wrapper
public final class Wrapper<T> {
private T response ;
private Error error;
public Wrapper<T> withResponse ( T response ){
this.response = response;
return this;
}
public Wrapper<T> withError( Error error) {
this.error = error;
return this;
}
public Boolean hasError(){
return error != null ;
}
public T getResponse(){
return response;
}
}

Related

Return mono object with response entity webflux

I have the below controller
#RestController
#RequestMapping("/view")
public class ViewController {
#GetMapping(value = "/{channelId}/**")
public Mono<ResponseEntity<ViewResponse>> viewObject(#PathVariable(value = "channelId") String channelId) {
return redisController.getChannelData(channelInfoset, channelId).map(response -> {
Mono<ViewResponse> processOutput = processViewUrl(channelId); // returns object of Mono.just
return new ResponseEntity<>(processOutput, responseHeaders, HttpStatus.OK);
}}
The method which returns the mono object
private Mono<ViewResponse> processViewUrl(String channelId){
return Mono.just(new ViewResponse("val","temp",false));
}
This gives me an error saying
Incompatible types. Found: 'reactor.core.publisher.Mono<java.lang.Object>', required: 'reactor.core.publisher.Mono<org.springframework.http.ResponseEntity<com.api.model.ViewResponse>>'
What is wrong over here?
processOutput is a Mono<ViewResponse>, not a ViewResponse. To obtain a Mono<ResponseEntity<ViewResponse>>, you should map processOutput to a ResponseEntity:
return redisController.getChannelData(channelInfoset, channelId)
.map(response -> {
Mono<ViewResponse> processOutput = processViewUrl(channelId);
return processOutput.map(value -> new ResponseEntity(value, response.getHeaders(), HttpStatus.OK));
}}
Note that if there's no constraint over your processViewUrl method signature (API compliance, potential other async/complex implementation, etc.), I would recommend to change it to return directly a ViewResponse instead of a mono. Mono.just is not necessary here.
In general cases, Mono.just is required only when you want to provide a value to an operation that requires a publisher as input.
If you return directly a ViewResponse instead of a Mono<ViewResponse>, your initial code should work.

How to extract path variable in citrus simulator framework for multiple scenario?

#Scenario("MyRestServiceScenario")
#RequestMapping(value = "/services/rest/simulator/{StudentId}", method = RequestMethod.GET)
public class MyRestServiceSimulator extends AbstractSimulatorScenario {
#Override
public void run(ScenarioDesigner scenario) {
scenario
.http()
.receive()
.get()
scenario.conditional().when(/*StudentId == something*/).actions(
scenario.http()
.send()
.response(HttpStatus.OK)
.payload(new ClassPathResource(template1.json))
);
scenario.conditional().when(/*StudentId == something*/).actions(
scenario.http()
.send()
.response(HttpStatus.NOT_FOUND)
.payload(new ClassPathResource(template2.json))
);
scenario.conditional().when(/*StudentId == something*/).actions(
scenario.http()
.send()
.response(HttpStatus.CREATED)
.payload(new ClassPathResource(template3.json))
);
}
}
On the basis of path variable of URI I need to return some responses. I am not getting any way to that. Same thing for Delete request. Is there any way I can fetch that variable or that URL so I can use that variable in scenario and return responses according to Path variable?

Spring webflux interact with Mono object

I have a simple CRUD controller. When updating I want merge the updated model with the model that has to be updated, this is what I have at the moment:
#PutMapping("{id}")
public Mono<Map> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
// Update the server here.
// server.setName(updatedServer.getName());
// server.setHost(updatedServer.getHost());
server.flatMap(this.serverRepository::save).subscribe();
return Mono.just(Collections.singletonMap("success", true));
}
How can I edit the server variable before the save? When subscribing on the Mono it will be executed after the save.
I know this is a pretty simple question, but I just can't find a way to do it.
I always find the answer just after asking the question...
I just used another flatmap to edit the object:
#PutMapping("{id}")
public Mono<Server> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
return server.flatMap(value -> {
value.setName(updatedServer.getName());
value.setHost(updatedServer.getHost());
return Mono.just(value); // Can this be done cleaner?
}).flatMap(this.serverRepository::save);
}
Or when still returning success: true:
#PutMapping("{id}")
public Mono<Map> update(#PathVariable UUID id, #RequestBody Server updatedServer) {
Mono<Server> server = this.serverRepository.findById(id);
return server.flatMap(value -> {
value.setName(updatedServer.getName());
value.setHost(updatedServer.getHost());
return Mono.just(value); // Can this be done cleaner?
}).flatMap(this.serverRepository::save).subscribe();
return Mono.just(Collections.singletonMap("success", true));
}

Get JSON string from Android JSONException

My Volley request can come back as either JSONArray (valid) or JSONObject (error message) and in order to correctly display the error response, I want to parse the failed JSONArray string as JSONObject. It appears that JSONException objects wrap the original text. Is it possible to get just the failed text in order to parse it differently?
Example:
org.json.JSONException: Value {"error":"User has not signed up to be a customer"} of type org.json.JSONObject cannot be converted to JSONArray
and I want to get just the JSON string component because it's a valid JSONObject.
Because your response is either JSONArray (valid) or JSONObject (error message), so you can refer to the following code:
// Check the response if it is JSONObject or JSONArray
Object json = new JSONTokener(response).nextValue();
if (json instanceof JSONObject) {
// do something...
} else if (json instanceof JSONArray) {
// do something...
}
Hope it helps!
I don't think it's actually possible to retrieve just the JSON string from inside a JSONException so in the end I took the answer from BNK and did what is probably the easiest solution in this situation.
The trick seems to be to receive a StringRequest and do the JSON processing once you know there's a valid string response. Here's how that looks in my project.
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
activity.hideProgress();
try {
Object json = new JSONTokener(response).nextValue();
if (json instanceof JSONArray) {
// an array is a valid result
dataModel.loadData((JSONArray)json);
} else if (json instanceof JSONObject) {
// this is an error
showErrorMessageIfFound((JSONObject)json);
}
} catch (JSONException error) {
error.printStackTrace();
}
refreshTable();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
activity.hideProgress();
showVolleyError(error);
// check for a JSONObject parse error
}
});
First there's a StringRequest to retrieve the response. The error response displays the error with my custom error processor. The success response parses the JSON and uses the end result to display the correct thing to the end user.

How to post a file along with parameter to webapi method?

I am new to ASP.NET Web API. I have a sample FileUpload web api (from some site) to upload files to the server.
Following works fine for uploading a file.
public async Task<HttpResponseMessage> FileUpload()
{
// Check whether the POST operation is MultiPart?
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
// Prepare CustomMultipartFormDataStreamProvider in which our multipart form
// data will be loaded.
//string fileSaveLocation = HttpContext.Current.Server.MapPath("~/App_Data");
string fileSaveLocation = HttpContext.Current.Server.MapPath("~/UploadedFiles");
CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation);
List<string> files = new List<string>();
try
{
// Read all contents of multipart message into CustomMultipartFormDataStreamProvider.
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
files.Add(Path.GetFileName(file.LocalFileName));
}
// Send OK Response along with saved file names to the client.
return Request.CreateResponse(HttpStatusCode.OK, files);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
// We implement MultipartFormDataStreamProvider to override the filename of File which
// will be stored on server, or else the default name will be of the format like Body-
// Part_{GUID}. In the following implementation we simply get the FileName from
// ContentDisposition Header of the Request Body.
public class CustomMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public CustomMultipartFormDataStreamProvider(string path) : base(path) { }
public override string GetLocalFileName(HttpContentHeaders headers)
{
return headers.ContentDisposition.FileName.Replace("\"", string.Empty);
}
}
But, I want to send a parameter called as 'token' of type string to the following method using [FromBody] is it possible?
Required:
public async Task<HttpResponseMessage> FileUpload([FromBody] string token)
{
//somecode here
}
So, basically can we send multiple Content-Type data to the web api? Please suggest. I am using Fiddler for testing webapi.
Eg:
Request Body(json):
{"token":"FV00VYAP"}
You can pass extra content in through query string and then read it from your CustomMultipartFormDataStreamProvider.
// Read all contents of multipart message into CustomMultipartFormDataStreamProvider.
await Request.Content.ReadAsMultipartAsync(provider);
Then you can use provider.FormData to read the extra values you passed along.
// Show all the key-value pairs.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
See http://www.asp.net/web-api/overview/advanced/sending-html-form-data,-part-2 for more details.

Resources