Partially consuming a Publisher<DataBuffer> - java

I am writing a org.springframework.cloud.gateway.filter.GatewayFilter (spring-cloud-gateway), that attempts to consume a response that follow a certain arbitrary protocol. The response body goes along the lines of:
200\n
header1:value1\n
header2:header2\n
\n
<the actual body>
The idea is, the entire response metadata from the downstream service is in the response body (much like how a SOAP envelope encloses the real body). The client of the gateway should receive a modified unwrapped response.
I've been following how ModifyRequestBodyGatewayFilterFactory and ModifyResponseBodyGatewayFilterFactory as guide, but I don't think they fit my use case.
I believe I can achieve this by returning a subclass of ServerHttpResponseDecorator, but I can't wrap my head yet on how to go about implementing:
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
// unwrap status code
// unwrap headers
// let it continue as usual
}

I was able to achieve this with:
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return Mono.from(body).flatMap { buffer ->
val size = readUtf8Line(buffer)
// recompute 'Content-Length', if applicable
super.writeWith(Mono.just(buffer))
}
}
The readUtf8Line is just my own method, that consumes the DataBuffer until it reaches a line break. It is also important to return the number of bytes read (i.e., size), because the Content-Length needs to be recomputed, if present.

Related

Decoding 202 Accepted response with Spring SOAP

I'm trying to write a Spring-WS client for a preexisting service. The endpoint offers two very similar actions, they both consume my data and respond with a simple object status; I need to use both. The difference is, one of them responds with HTTP status code 200 and the other with 202. In the first case the status object is decoded correctly and returned from WebServiceTemplate.marshallSendAndReceive(), but in the second case it's not decoded at all, and the method returns null.
According to the HTTP spec, the HTTP status 202 is Accepted, and its purpose is to indicate that the request has been received but not yet acted upon. However, the response may still contain useful information, like current request status or a completion estimate. I want to get this response.
I tried to debug the exact process and noticed my program executing the following code in the org.springframework.ws.transport.http.AbstractHttpSenderConnection.hasResponse() method:
protected final boolean hasResponse() throws IOException {
int responseCode = getResponseCode();
if (HttpTransportConstants.STATUS_ACCEPTED == responseCode ||
HttpTransportConstants.STATUS_NO_CONTENT == responseCode) {
return false;
}
...
}
This code fragment seems responsible for never getting the status object from the 202 Accepted response. (Or a 204 No Content response, but that's obviously acceptable.)
Is there a way around this? It doesn't seem possible to override this method in a bean, it's marked final.
The closest thing to an answer I could find was the following SWS JIRA ticket. It's marked "Resolved" since August 2012, but it's really not, as the comment from 2015 says.
my workaround:
Implement a custom HttpResponseInterceptor to handle a HTTP202:
public class MyInterceptor implements HttpResponseInterceptor {
#Override
public void process(HttpResponse httpResponse, HttpContext arg1) throws HttpException, IOException {
if (202 == httpResponse.getStatusLine().getStatusCode()) {
httpResponse.setStatusLine(new BasicStatusLine(httpResponse.getStatusLine().getProtocolVersion(),200,httpResponse.getStatusLine().getReasonPhrase()));
}
}
}
Now, add the interceptor to my http client builder when creating the webServiceTemplate
public CloseableHttpClient httpClient() throws Exception {
return HttpClientBuilder.create().addInterceptorLast(new MyInterceptor()).setSSLSocketFactory(sslConnectionSocketFactory()).build();
}

Dependant webclient calls - Spring Reactive

I am trying to do two API calls, the second API call is dependent on the first API response. The following piece of code gives response for first weblient call.Here I am not getting the response from second API call. On log I could see that the request for the second webclient call is not even started with onSubscribe(). Can you please tell me what mistake am I doing.
#Autowired
Issue issue;
List issueList = new ArrayList<>();
public Mono<Response> getResponse(Request request) {
return webClient.post()
.uri("myURI")
.body(Mono.just(request),Request.class)
.retrieve()
.bodyToMono(Response.class)
.flatMap(resp->{
resp.getIssues().stream()
.forEach(issueTemp -> {
issue = issueTemp;
webClient.get()
.uri("mySecondURI" + issueTemp.getId())
.retrieve()
.bodyToMono(Issue.class)
.flatMap(issueTemp2-> {
issue.setSummary(issueTemp2.getSummary());
return Mono.just(issue);
}).log();
issueList.add(issue);
});
Response responseFinal = new Response();
responseFinal.setIssues(issueList);
return Mono.just(responseFinal);
}).log();
}
UPDATE 2:
I have changed my code to Functions and used Flux instead of stream iterations.What I am facing now is , all the iterations are get filtered out in doSecondCall method. Please refer my comment in doSecondCall method. Due to which the second call is not triggered. If i dont apply the filter, there are requests triggered like "issue/null" which also causes my service to go down.
public Mono<Response> getResponse(Request request) {
return webClient.post()
.uri("myURI")
.body(Mono.just(request),Request.class)
.retrieve()
.bodyToMono(Response.class)
.flatMap(r->
doSecondCall(r).flatMap(issueList->{
r.setIssues(issueList);
return Mono.just(r);
})
);
}
public Mono<Issue> doSecondCall(Response r) {
return Flux.fromIterable(r.getIssues())
.filter(rf->rf.getId()!=null) //everything gets filtered out
.flatMap(issue->getSummary(issue.getId()))
.collectList();
}
public Mono<Issue> getSummary(Response r) {
return webClient.get()
.uri("issue/"+id)
.retrieve()
.bodyToMono(Issue.class).log();
}
[ How does Reactive programming using WebFlux handles dependent external api calls ] #Thomas- Also ,Just found this thread. He basically says unless you block the first call, there is no way to declare the second one. Is that the case?
Why you are not triggering the second calls is because you are breaking the chain as i have mentioned in this answer (with examples).
Stop breaking the chain
// here...
.forEach(issueTemp -> {
issue = issueTemp; // and this is just silly? why?
webClient.get() // Here you are calling the webClient but ignoring the return value, so you are breaking the chain.
.uri("mySecondURI" + issueTemp.getId())
.retrieve()
.bodyToMono(Issue.class)
.flatMap(issueTemp2-> {
issue.setSummary(issueTemp2.getSummary());
return Mono.just(issue); // Return here but you are ignoring this return value
}).log();
issueList.add(issue);
});
You should use more functions to divide up your code. Make it a habit by writing a function and always start with the return statement. You code is very hard to read.
I think you should instead use a FLux instead of iterating a stream.
// something like the following i'm writing by free hand without IDE
// i have no idea what your logic looks like but you should get the point.
Flux.fromIterable(response.getIssues())
.flatMap(issue -> {
return getIssue(issue.getId())
.flatMap(response -> {
return issue.setSummary(reponse.getSummary());
});
}).collectList();

Spring Integration JAVA DSL Using original payload in a subsequent call

I am using spring integration to define a flow that will do two things - firstly execute http call with given payload and then use the response provided and original payload to make another http call.
How can this be achieved? In the code below I am able to use and modify the first payload and use it in the firstHttpRequest but then how can I use the original payload with the response from the firstHttpRequest?
Any good practices?
#Bean
public IntegrationFlow makeHttpCalls(){
return message -> message
.transform(new GenericTransformer<Message<String>, String>() {
#Override
public String transform(Message<String> message){
return message.getPayload() + " first call";
}
})
.handle(makeFirstHttpRequest())
.transform(new GenericTransformer<Message<String>, String>() {
#Override
public String transform(Message<String> message) {
logger.debug("Response from transform: " + message);
return message.getPayload();
}
})
.handle(makeSecondHttpRequest())
.channel("entrypoint");
}
One approach is .enrichHeaders() and place that original payload into one custom header. Therefore downstream, after response, get deal with that header to restore an original payload.
Another approach is .enrich() when you make an external call and enrich original message with info from reply.
There is other tools, like .routeToRecipients() and .publishSubscribeChannel() where you send the same message to different sub-flows for different logics.
But that's it what you have to choose.

Splitter returning empty array list spring integration

We have a case where in our spring integration splitter logic where it could return an empty ArrayList thus providing no data to be routed. How should we handle this scenario and reroute it to a different channel when there is an empty ArrayList returned from the splitter.
UPDATE :
As shown by Artem I have created a custom advice and have got the rerouting to be done successfully however there are still two issues, where the second one describe is the blocker issue listed
The current class that I have is as follows
public class EmptyListRequestHandlerAdvice extends AbstractRequestHandlerAdvice {
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
public EmptyListRequestHandlerAdvice(MessageChannel emptyListChannel) {
this.messagingTemplate.setDefaultChannel(emptyListChannel);
}
#Override
protected Object doInvoke(ExecutionCallback callback, Object target,
Message<?> message) throws Exception {
Object result = callback.execute();
if (result==null) {
this.messagingTemplate.convertAndSend(new ArrayList<InventoryHotel>());
}
return result;
}
Whenever the splitter returns a new empty array list the result of "callback.execute() is null. However whenever the returned array list from the splitter is not empty the returned result is of type org.springframework.integration.util.FunctionIterator and not a collection type. Any idea on if it is possible to get a collection returned in both cases instead of null or function iterator? Atleast avoid getting a function iterator and get an array list.
Additionally when a valid channel name is set to the constructor and a message is sent through that channel to the transformer method the transformer method is successfully executed however when it returned a value the following error occurs
org.springframework.messaging.MessagingException: org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
at org.springframework.integration.dispatcher.AbstractDispatcher.wrapExceptionIfNecessary(AbstractDispatcher.java:133)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:120)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:255)
Any suggestions on why this occurs and how to resolve it.
Regards,
Milinda
Well, you can overcome it with <request-handler-advice-chain> for the <splitter> and of course some custom AbstractRequestHandlerAdvice:
public class EmptyListRequestHandlerAdvice extends AbstractRequestHandlerAdvice {
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
public EmptyListRequestHandlerAdvice(MessageChannel emptyListChannel) {
this.messagingTemplate.setDefaultChannel(emptyListChannel);
}
#Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
Collection<?> result = (Collection<?>) callback.execute();
if (result.isEmpty) {
this.messagingTemplate.convertAndSend(result);
}
return result;
}
}
UPDATE
Having that class you can use it like this:
<splitter>
<request-handler-advice-chain>
<beans:bean class="com.my.proj.int.EmptyListRequestHandlerAdvice">
<beans:constructor-arg ref="emptyListChannel"/>
</beans:bean>
</request-handler-advice-chain>
</splitter>
<channel id="emptyListChannel"/>
UPDATE2
Any idea on if it is possible to get a collection returned in both cases instead of null or function iterator?
No, it isn't possible. It is an internal logic of the splitter, and of cause its handleRequestMessage produces an Iterator to have a streaming split process gain.
It's isn't clear what is the reason for you to have Collection, since splitter is alwasy produces several messages and an iteration logic is hidden from you.
From your first requirement, you asked for the solution to send an empty list to different channel. So, I don't see why the iterator logic should impact you.
no output-channel or replyChannel header available
You really should do this:
this.messagingTemplate.send(MessageBuilder.withPayload(new ArrayList<InventoryHotel>())
.copyHeaders(message.getHeaders().build());
Having a new message based on that requestMessage you'll copy all its header, so the replyChannel header is there too.

Returning 200 response code instead of 204

This is my method for creating Response with header parameters and body:
public Response sendOKResponse(request req)
{
ResponseBuilderImpl builder = new ResponseBuilderImpl();
// set the header params.
for(int index =0; index<req.headerParameters.size(); index++)
{
builder.header(req.headerParameters.get(index).getName(), req.headerParameters.get(index).getBody());
}
// set the body and response code
builder.status(Response.Status.OK).entity(req.getBody());
Response r = builder.build();
return r;
}
And this is how i return the Response:
Response response;
response = sendBadMesseage();
return response;
This code returns code 204(No content) instead of 200.
Any ideas why?
You shouldn't be instantiating your response builder with new, the whole point of the JAX-RS abstraction layer is to hide implementation details away from calling clients. This is what makes it possible to have various vendor implementations which can be interchanged at will. Also, if you are using JEE6, or hope to migrate to it, this code will almost certainly fail. Most JEE6 vendor implementations utilize CDI, which is concept-incompatible with usage of new. But, closer to the topic, the JAX-RS implementation specifies that a 204 status code be returned if a responses wrapped entity is null. You might want to verify this is not the case in any of your methods. Also, you might want to make some changes to your code:
public Response sendOKResponse(request req) {
ResponseBuilder response = Response.ok();
// set the header params.
for(Header h: req.headerParameters()) {
builder = builder.header(h.getName(), h.getValue());
}
// set the body and response code
builder = builder.entity(req.getBody());
return builder.build();
}
Your sendBadMessage method should also look similar to above. You can log your entity before adding it to the builder, to verify that you only get a 204 when it's null.

Categories