Spring integration handle exception in publisherSubscribeChannel - spring

I am quite new to spring integration. I am evaluating the Spring Integration for our project. I am run into one issue on how to handle the Exception.
I am using the publishSubscribeChannel for handling the message. I am not sure if this is a correct approach or not. When a exception is thrown inside publishSubscribeChannel, I would like it to route to a different channel so I can reply to different HTTP status code.
How do I route the exception inside the publishSubscribeChannel to the errorChannel.
I have the following code. I have tried to use routeException in a different area of the code but no luck. Can someone please help me with how to solve this?
#Configuration
#EnableIntegration
public class IntegrationConfiguration {
// curl http://localhost:8080/tasks --data '{"username":"xyz","password":"xyz"}' -H 'Content-type: application/json'
#Bean
MessageChannel directChannel() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow httpGateway() {
return IntegrationFlows.from(
Http.inboundGateway("/tasks")
.requestMapping(m -> m.methods(HttpMethod.POST))
.requestPayloadType(String.class)
.requestChannel(directChannel())
.get()
)
.transform(t -> {
return "transofrm " + t;
})
.channel("queueChannel")
.routeByException(r -> {
r.channelMapping(RuntimeException.class, "errorChannel");
r.defaultOutputToParentFlow();
})
.get();
}
#Bean
public IntegrationFlow handleMessage() {
return IntegrationFlows.from("queueChannel")
.wireTap(flow -> flow.handle(System.out::println))
.routeByException(r -> {
r.channelMapping(RuntimeException.class, "errorChannel");
r.defaultOutputToParentFlow();
})
.publishSubscribeChannel(publisher -> {
publisher.errorHandler(var1 -> {
var1.printStackTrace();
})
.subscribe(flow -> flow
.handle(m -> {
if (m.getPayload().toString().contains("user")) {
throw new RuntimeException("user found");
}
System.out.println("subscribed " + m.getPayload());
})
);
}
)
.transform(t -> "")
.wireTap(flow -> flow.handle(m -> {
System.out.println(m.getHeaders().get("status"));
}))
.enrichHeaders( c -> c.header(HttpHeaders.STATUS_CODE, HttpStatus.OK))
.get();
}
#Bean
IntegrationFlow exceptionOrErrorFlow() {
return IntegrationFlows.from("errorChannel")
.wireTap(f -> f.handle(m -> System.out.println("failed badly")))
.enrichHeaders(c -> c.header(HttpHeaders.STATUS_CODE, HttpStatus.BAD_REQUEST))
.get();
}
}

It is not entirely clear what you are trying to do. You generally should not use a queue channel in a flow like this.
Simply add an .errorChannel to the inbound gateway and any downstream exceptions will be sent to that channel, to which you can add a subscriber to handle the exceptions.
Also, you should not call get() on internally declared specs (those that are not beans) like that, use the form of .from() that takes a ...Spec directly. Otherwise the bean won't be initialized properly.
return IntegrationFlows.from(
Http.inboundGateway("/tasks")
.requestMapping(m -> m.methods(HttpMethod.POST))
.requestPayloadType(String.class)
.requestChannel(directChannel())
.errorChannel("myErrors")
)
...

Related

WebServerFactoryCustomizer is not hit in a Springboot Webflux app

Following Configure the Web Server , I add a NettyWebServerFactoryCustomizer
#Configuration
public class NettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(httpServer -> {
return httpServer
.wiretap(true)
.metrics(true, s->s)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
});
});
}
}
I have two questions:
When I run the app, the customize function is not hit. Where do I miss?
My purpose is to enable the Netty metrics, I can't find any documents about config the metrics in the application.yml file. so I add the NettyWebServerFactoryCustomizer.
The second parameter of .metrics(true, s->s) is a uriTagValue, Are there any example about how to pass in value? I just use s->s because I refer this, but this maybe can't avoid cardinality explosion, Are there any function like ServerWebExchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE) simple give us the templated URL?
I found the workaround of the question 1: define a bean instead of implementing WebServerFactoryCustomizer
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(builder -> builder
.wiretap(true)
.metrics(true,s->s)
.accessLog(true)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
}));
return factory;
}
About your question 2 : The second parameter of .metrics(true, s->s) is a uriTagValue, Are there any example about how to pass in value?
private static final Pattern URI_TEMPLATE_PATTERN = Pattern.compile("/test/.*");
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(builder -> builder
.wiretap(true)
.metrics(true,
uriValue ->
{
Matcher matcher = URI_TEMPLATE_PATTERN .matcher(uriValue);
if (matcher.matches()) {
return "/test/";
}
return "/";
}
.accessLog(true)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
}));
return factory;
}

anyone can explain how does this code send message to specified user

#Bean
public WebSocketHandler webSocketHandler() {
TopicProcessor<String> messageProcessor = this.messageProcessor();
Flux<String> messages = messageProcessor.replay(0).autoConnect();
Flux<String> outputMessages = Flux.from(messages);
return (session) -> {
System.out.println(session);
session.receive().map(WebSocketMessage::getPayloadAsText).subscribe(messageProcessor::onNext, (e) -> {
e.printStackTrace();
});
return session.getHandshakeInfo().getPrincipal().flatMap((p) -> {
session.getAttributes().put("username", p.getName());
return session.send(outputMessages.filter((payload) -> this.filterUser(session, payload))
.map((payload) -> this.generateMessage(session, payload)));
}).switchIfEmpty(Mono.defer(() -> {
return Mono.error(new BadCredentialsException("Bad Credentials."));
})).then();
};
}
I am trying to build a online chating system with webflux,and have found a example through github.as a beginner in reactor development,I am confused about how does this code send a message to single user.
this is the way i think of in springmvc
put all the active websocketsession into map
check every message if the field username in message equals the username stored in session,use this session send msg
private static Map clients = new ConcurrentHashMap();
public void sendMessageTo(String message, String ToUserName) throws IOException {
for (WebSocket item : clients.values()) {
if (item.username.equals(ToUserName) ) {
item.session.sendText(message);
break;
}
}
}
can you explain how does the code in the webflux code above works?
i know all the messages are stored in the outputMessages and subcribed.
when a new message be emitted,how does it find the correct session ?
My guess is that the WebSocketHandler is an interface containing only one method handle WebSocketHandler
which in turn i believe makes it a FunctionalInterface that can be used as a lambda.
(session) -> { ... }
So when a session is established with a client, and the client sends a websocket event. The server will look for the WebSocketHandler and populate it with the session from the client that sent the event.
If you find this confusing you can just implement the interface.
class ExampleHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> input = session.receive()
.doOnNext(message -> {
// ...
})
.concatMap(message -> {
// ...
})
.then();
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage));
return Mono.zip(input, output).then();
}
}
#Bean
public WebSocketHandler webSocketHandler() {
return new ExampleHandler();
}

Correlate messages between 2 JMS queues using Spring integration components

I have 2 JMS queues and my application subscribes to both of them with Jms.messageDrivenChannelAdapter(...) component.
First queue receives messages of type Paid. Second queue receives messages of type Reversal.
Business scenario defines correlation between messages of type Paid and type Reversal.
Reversal should wait for Paid in order to be processed.
How can I achieve such "wait" pattern with Spring Integration?
Is it possible to correlate messages between 2 JMS queues?
See the documentation about the Aggregator.
The aggregator correlates messages using some correlation strategy and releases the group based on some release strategy.
The Aggregator combines a group of related messages, by correlating and storing them, until the group is deemed to be complete. At that point, the aggregator creates a single message by processing the whole group and sends the aggregated message as output.
The output payload is a list of the grouped message payloads by default, but you can provide a custom output processor.
EDIT
#SpringBootApplication
public class So55299268Application {
public static void main(String[] args) {
SpringApplication.run(So55299268Application.class, args);
}
#Bean
public IntegrationFlow in1(ConnectionFactory connectionFactory) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(connectionFactory)
.destination("queue1"))
.channel("aggregator.input")
.get();
}
#Bean
public IntegrationFlow in2(ConnectionFactory connectionFactory) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(connectionFactory)
.destination("queue2"))
.channel("aggregator.input")
.get();
}
#Bean
public IntegrationFlow aggregator() {
return f -> f
.aggregate(a -> a
.correlationExpression("headers.jms_correlationId")
.releaseExpression("size() == 2")
.expireGroupsUponCompletion(true)
.expireGroupsUponTimeout(true)
.groupTimeout(5_000L)
.discardChannel("discards.input"))
.handle(System.out::println);
}
#Bean
public IntegrationFlow discards() {
return f -> f.handle((p, h) -> {
System.out.println("Aggregation timed out for " + p);
return null;
});
}
#Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> {
send(template, "one", "two");
send(template, "three", null);
};
}
private void send(JmsTemplate template, String one, String two) {
template.convertAndSend("queue1", one, m -> {
m.setJMSCorrelationID(one);
return m;
});
if (two != null) {
template.convertAndSend("queue2", two, m -> {
m.setJMSCorrelationID(one);
return m;
});
}
}
}
and
GenericMessage [payload=[two, one], headers={jms_redelivered=false, jms_destination=queue://queue1, jms_correlationId=one, id=784535fe-8861-1b22-2cfa-cc2e67763674, priority=4, jms_timestamp=1553290921442, jms_messageId=ID:Gollum2.local-55540-1553290921241-4:1:3:1:1, timestamp=1553290921457}]
2019-03-22 17:42:06.460 INFO 55396 --- [ask-scheduler-1] o.s.i.a.AggregatingMessageHandler : Expiring MessageGroup with correlationKey[three]
Aggregation timed out for three

spring webflux: purely functional way to attach websocket adapter to reactor-netty server

I am not able to figure out a way to attach a WebSocketHandlerAdapter to a reactor netty server.
Requirements:
I want to start a reactor netty server and attach http (REST) endpoints and websocket endpoints to the same server. I have gone through the documentation and some sample demo application mentioned in the documentation. They show how to attach a HttpHandlerAdapter to the the HttpServer using newHandler() function. But when it comes to websockets they switch back to using spring boot and annotation examples. I am not able to find how to attach websockets using functional endpoints.
Please point me in the right direction on how to implement this.
1. how do I attach the websocket adapter to the netty server?
2. Should I use HttpServer or TcpServer?
Note:
1. I am not using spring boot.
2. I am not using annotations.
3. Trying to achieve this only using functional webflux end points.
Sample code:
public HandlerMapping webSocketMapping()
{
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/echo", new EchoTestingWebSocketHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1);
return mapping;
}
public WebSocketHandlerAdapter wsAdapter()
{
HandshakeWebSocketService wsService = new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
return new WebSocketHandlerAdapter(wsService);
}
protected void startServer(String host, int port)
{
HttpServer server = HttpServer.create(host, port);
server.newHandler(wsAdapter()).block(); //how do I attach the websocket adapter to the netty server
}
Unfortunately, there is no easy way to do that without running up whole SpringBootApplication. Otherwise, you will be required to write whole Spring WebFlux handlers hierarchy by your self. Consider to compose your functional routing with SpringBootApplication:
#SpringBootApplication
public class WebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class, args);
}
#Bean
public RouterFunction<ServerResponse> routing() {
return route(
POST("/api/orders"),
r -> ok().build()
);
}
#Bean
public HandlerMapping wsHandlerMapping() {
HashMap<String, WebSocketHandler> map = new HashMap<>();
map.put("/ws", new WebSocketHandler() {
#Override
public Mono<Void> handle(WebSocketSession session) {
return session.send(
session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map(tMessage -> "Response From Server: " + tMessage)
.map(session::textMessage)
);
}
});
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1);
return mapping;
}
#Bean
HandlerAdapter wsHandlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
Incase if SpringBoot infra is not the case
try to consider direct interaction with ReactorNetty instead. Reactor Netty Provides pritty good abstraction around native Netty and you may interacti with it in the same functional maner:
ReactorHttpHandlerAdapter handler =
new ReactorHttpHandlerAdapter(yourHttpHandlers);
HttpServer.create()
.startRouterAndAwait(routes -> {
routes.ws("/pathToWs", (in, out) -> out.send(in.receive()))
.file("/static/**", ...)
.get("**", handler)
.post("**", handler)
.put("**", handler)
.delete("**", handler);
}
);
I deal with it this way. and use native reactor-netty
routes.get(rootPath, (req, resp)->{
// doFilter check the error
return this.doFilter(request, response, new RequestAttribute())
.flatMap(requestAttribute -> {
WebSocketServerHandle handleObject = injector.getInstance(GameWsHandle.class);
return response
.header("content-type", "text/plain")
.sendWebsocket((in, out) ->
this.websocketPublisher3(in, out, handleObject, requestAttribute)
);
});
})
private Publisher<Void> websocketPublisher3(WebsocketInbound in, WebsocketOutbound out, WebSocketServerHandle handleObject, RequestAttribute requestAttribute) {
return out
.withConnection(conn -> {
// on connect
handleObject.onConnect(conn.channel());
conn.channel().attr(AttributeKey.valueOf("request-attribute")).set(requestAttribute);
conn.onDispose().subscribe(null, null, () -> {
conn.channel().close();
handleObject.disconnect(conn.channel());
// System.out.println("context.onClose() completed");
}
);
// get message
in.aggregateFrames()
.receiveFrames()
.map(frame -> {
if (frame instanceof TextWebSocketFrame) {
handleObject.onTextMessage((TextWebSocketFrame) frame, conn.channel());
} else if (frame instanceof BinaryWebSocketFrame) {
handleObject.onBinaryMessage((BinaryWebSocketFrame) frame, conn.channel());
} else if (frame instanceof PingWebSocketFrame) {
handleObject.onPingMessage((PingWebSocketFrame) frame, conn.channel());
} else if (frame instanceof PongWebSocketFrame) {
handleObject.onPongMessage((PongWebSocketFrame) frame, conn.channel());
} else if (frame instanceof CloseWebSocketFrame) {
conn.channel().close();
handleObject.disconnect(conn.channel());
}
return "";
})
.blockLast();
});
}

Convert #JmsListener code to String Integration DSL

#JmsListener(destination = "myListener")
public void receive(Event even) {
if (event.myObj().isComp()) {
service1.m1(even);
}
if (event.myObj2().isdone()) {
service2.m2(event);
}
}
I tried various combinations, and one of them is below
#Bean
public IntegrationFlow flow1() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("incomingQueue"))
.<Event>filter(e -> ((Event)e).myObj().isComp()).handle(service1, "m1")
.<Event>filter(e -> ((Event)e).myObj2().isdone()).handle(service2, "m2")//looks like its not called
.get();
}
But it does not executes on 2nd filter/condition. Please suggest what I am missing here
It worked, after I put #ServiceActivator annotation on m1 as well as m2. My bad, I missed this annotation while converting code to SI

Resources