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.
Related
I'm using a RouterFunction to define endpoints in my Spring Boot application. My service returns a Mono<Object> and I want to return the result of this when the endpoint is called. I also need to authenticate so I pass a UserPrinciple object through.
Router
#Bean
RouterFunction<ServerResponse> router() {
return route()
.GET("/api/endpoint-name", this::getExample)
.build();
}
private Mono<ServerResponse> getExample(ServerRequest request) {
return ServerResponse.ok().body(fromPublisher(getUserPrincipal().map(service::getSomething), Object.class)).log();
}
private Mono<UserPrincipal> getUserPrincipal() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getPrincipal())
.map(UserPrincipal.class::cast);
}
Service
public Mono<Object> getSomething(UserPrincipal userPrincipal) {
WebClient webClient = getWebClient(userPrincipal.getJwt());
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("another/server/endpoint").build())
.retrieve()
.bodyToMono(Object.class);
}
The endpoint is returning this:
{
"scanAvailable": true
}
which suggests that I'm passing the Mono into the body of the response instead of passing in the result. However I've used fromPublisher which I thought would resolve this.
I can't find any examples where the service returns a Mono and the route correctly returns the result of the Mono.
How can I correctly pass a Mono/Flux as the body of the response?
im not going to explain the difference between mapand flatMapsince i have already written a quite comprehensive explanation here:
Do you have a test to show differences between the reactor map() and flatMap()?
The problem in the above code is the return of Object. And input parameters of Object into certain functions. The first function is pretty straight forward
Mono<UserPrincipal> = getUserPrincipal();
While the second one gets a bit more hairy:
Mono<Mono<Object> value = getUserPrincipal().map(service::getSomething);
So why are we getting A nested Mono?, well the get something returns a Mono<Object> and the Map return according the the api is Mono<R> where R is what we return from getSomething.
We then stick it into the fromPublisher which will unrap the first Mono ending up trying to serialize the Mono<Object>resulting in the strange response.
{
"scanAvailable": true
}
The answer here is pay more close attention to the type system. The body function takes a Publisher (Mono or Flux) so you don't need the fromPublisher function.
And also changing map to flatMap since the return type from inside a flatMap is a publisher.
ServerResponse.ok()
.body(getUserPrincipal()
.flatMap(service::getSomething), Object.class));
In my application based on REST API, I need to return for all results of all requests, a custom field in the Response Headers. Now I use this approach:
response().setHeader("custom-field",valuateSender());
return ok(response.addData(body));
In this way, however, I am forced to call the result() method in all my actions, I'm looking for a more general and more intelligent approach.
You can add a Filter which will be global and applies to all of your routes.
then delegate the response and add your custom header before returning data to the client.
this could be a part of your filter:
public class AddCustomHeaderFilter extends Filter {
#Override
public CompletionStage<Result> apply(
Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
Http.RequestHeader requestHeader) {
return nextFilter
.apply(requestHeader)
.thenApply(
result -> {
return result.withHeader("custom-key", "custom-data");
});
}
}
more info on Filters:
https://www.playframework.com/documentation/2.8.x/JavaHttpFilters
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.
I'm using Reactive Spring Cloud Stream and I'm having trouble creating a StreamListener without an Output. The following code works as long as no malformed messages are received. When a malformed message is received, the flux closes.
#StreamListener
public void handleMessage(#Input(MessagingConfig.INPUT) Flux<String> payloads) {
payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave)).subscribe();
}
If I understand correctly, it is preferable to let the framework subscribe to the flux instead of subscribing to it manually. This isn't a problem when a listener has an output, because I can simply return the flux like so:
#StreamListener
#Output(MessagingConfig.OUTPUT)
public Flux<String> handleMessage(#Input(MessagingConfig.INPUT) Flux<String> payloads) {
return payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave));
}
The framework seems to handle bad messages in a way that doesn't close the flux when it is returned. Is there any way to let the framework handle the flux when the listener doesn't specify an output?
Consider switching to using Spring Cloud Function (SCF) programming model which we have recently adopted.
Basically, as long as you have the latest code base (2.1.0.RC4 is the latest and RELEASE is few days away) you're fine. Here is the example of your code using SCF programming model:
#SpringBootApplication
#EnableBinding(Sink.class)
public class SampleReactiveConsumer {
public static void main(String[] args) {
SpringApplication.run(SampleReactiveConsumer.class,
"--spring.cloud.stream.function.definition=consume");
}
#Bean
public Consumer<Flux<String>> consume(){
return payloads -> payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave)).subscribe();
}
}
You can also remove reactive module from your classpath as we're also considering deprecating it all together
If you really want to avoid SCF mentioned in Oleg's answer you could try below, hacky approach.
const val IN = "input"
const val OUT = "dummy-output"
interface Channels {
#Input(IN)
fun input(): MessageChannel
#Output(OUT)
fun output(): MessageChannel
}
#EnableBinding(Channels::class)
class MsgList {
#StreamListener
#Output(OUT)
fun receive(#Input(IN) messages: Flux<String>): Flux<Void> {
return messages
.doOnNext { if (it == "err") throw IllegalStateException("err") }
.doOnNext { println(it) }
.flatMap { Mono.empty<Void>() }
}
}
Output binding will be created but no messages will go through. In case of RabbitMQ that means - dummy exchange will appear, but queue won't get created.
Also errors would be handled as you expected. With above example, you may send 3 messages, "ok", "err", "ok2", and you will see "ok", then exception, then "ok2" on the screen. An "ok2" and any subsequent valid message will be handled properly.
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.