Apache Camel based Udp server - spring-boot

I'm trying to use Apache Camel to create udp server which consumes syslog messages.
There are no examples how to do it correctly.
I wrote following route, which use custom serverInitializerFactory.
#Component
public class MainRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("netty4:udp://{{app.server.host}}:{{app.server.port}}?serverInitializerFactory=#udpSyslogFlowFactory&sync=false&textline=true")
.to("seda:rowLogs");
from("seda:rowLogs?concurrentConsumers={{app.concurrent-processors}}")
.to("bean:logParser");
}
}
Code of factory:
#Component
public class UdpSyslogFlowFactory extends ServerInitializerFactory {
private int maxLineSize = 1024;
private NettyConsumer consumer;
public UdpSyslogFlowFactory() {
super();
}
public UdpSyslogFlowFactory(NettyConsumer consumer) {
this();
this.consumer = consumer;
}
#Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline channelPipeline = ch.pipeline();
channelPipeline.addLast("encoder-SD", new StringEncoder(StandardCharsets.UTF_8));
channelPipeline.addLast("decoder-DELIM",
new DelimiterBasedFrameDecoder(maxLineSize, true, Delimiters.lineDelimiter()));
channelPipeline.addLast("decoder-SD", new StringDecoder(StandardCharsets.UTF_8));
channelPipeline.addLast("handler", new ServerChannelHandler(consumer));
}
#Override
public ServerInitializerFactory createPipelineFactory(NettyConsumer consumer) {
return new UdpSyslogFlowFactory(consumer);
}
}
It looks like incoming udp messages don't processed by references StringDecoder.
Anybody can provide full example of UDP Server with Camel which use simple text decoding of all incoming messages?

Instead of building the syslog-consumer and decoder by yourself, have a look at the Camel syslog DataFormat.
On the linked documentation page you can find syslog-consumer examples with netty and mina components.

Related

Apache camel dynamic routing

I have following Apache camel rest service(/sales) that internally calls another rest service(/getOrders) and get list of objects. Am able to print JSON response in the processor but getting java objects in response while trying from postman. Could anyone pls help me to resolve the issue. Attaching the response log for ref..
#Component
public class ApplicationResource extends RouteBuilder {
#Autowired
private OrderService service;
#BeanInject
private OrderProcessor processor;
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").port(9090).host("localhost");
rest().get("/getOrders").produces(MediaType.APPLICATION_JSON_VALUE).route().setBody(() -> service.getOrders());
rest().get("/sales").produces(MediaType.APPLICATION_JSON_VALUE).route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true").convertBodyTo(String.class).marshal()
.json(JsonLibrary.Jackson, Order.class).to("log:foo?showHeaders=true");;
;
}
}
You should remove the last .endRest() on "direct:bye" route.
I think you get the rest response before calling your Processor.
This works for me.
First, I needed to set the bindingMode as RestBindingMode.json in the restConfiguration.
Secondly, instead of marshal(), you need to use unmarshal().
Third, since you are returning a list of orders, .json(JsonLibrary.Jackson, Order.class) will not be sufficient to unmarshal the list of orders. You need to use a custom format which will be able to unmarshal the list of orders into a json array. This you need to do using JacksonDataFormat format = new ListJacksonDataFormat(Order.class);
#Override
public void configure() {
JacksonDataFormat format = new ListJacksonDataFormat(Order.class);
restConfiguration().component("servlet").port(9090).host(localhost).bindingMode(RestBindingMode.json);
rest()
.get("/getOrders")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getMessage().setBody(service.getOrders());
}})
.to("log:getOrders?showHeaders=true&showBody=true");
rest()
.get("/sales")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true")
.unmarshal(format)
.to("log:sales?showHeaders=true&showBody=true");
}
Solvedddd !!! i did two things as follows,May be use full for some one
1,bindingMode(RestBindingMode.auto) - RestBindingMode changes to auto
from json
2, Added this in the main
service(/getOrders).marshal().json(JsonLibrary.Jackson);
#Component
public class ApplicationResource extends RouteBuilder {
#Autowired
private OrderService service;
#BeanInject
private OrderProcessor processor;
#Override
public void configure() throws Exception {
restConfiguration().component("servlet").port(9090).host("localhost").bindingMode(RestBindingMode.auto);
rest().get("/getOrders").produces(MediaType.APPLICATION_JSON_VALUE).route().setBody(() -> service.getOrders())
.marshal().json(JsonLibrary.Jackson);
rest().get("/sales").produces(MediaType.APPLICATION_JSON_VALUE).route()
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.toD("http://localhost:9090/getOrders?bridgeEndpoint=true").convertBodyTo(String.class)
.log("body = ${body}");
;
;
}
}

Catching exception Feign

I want to handle any exception from feign client, even if service is not available. However I can not catch them using try/catch. This is my feign client:
#FeignClient(name = "api-service", url ="localhost:8888")
public interface ClientApi extends SomeApi {
}
Where api is:
#Path("/")
public interface SomeApi {
#GET
#Path("test")
String getValueFromApi();
}
Usage of client with try/catch:
#Slf4j
#Service
#AllArgsConstructor
public class SampleController implements SomeApi {
#Autowired
private final ClientApi clientApi;
#Override
public String getValueFromApi() {
try {
return clientApi.getValueFromApi();
} catch (Throwable e) {
log.error("CAN'T CATCH");
return "";
}
}
}
Dependencies are in versions:
spring-boot 2.2.2.RELEASE
spring-cloud Hoxton.SR1
Code should work according to How to manage Feign errors?.
I received few long stack traces among them exceptions are :
Caused by: java.net.ConnectException: Connection refused (Connection refused)
Caused by: feign.RetryableException: Connection refused (Connection refused) executing GET http://localhost:8888/test
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: ClientApi#getValueFromApi() failed and no fallback available.
How to properly catch Feign exeptions, even if client service (in this case localhost:8888) is not available?
Ps. When feign client service is available it works, ok. I am just focused on the exceptions aspect.
A better way to handle the situation where your service is not available is to use a circuit breaker pattern. Fortunately, it is easy using Netflix Hystrix as an implementation of the circuit breaker pattern.
First of all, you need to enable Hystrix for feign clients in application configuration.
application.yml
feign:
hystrix:
enabled: true
Then you should write a fallback class for the specified feign client interface.
In this case getValueFormApi method in fallback class will act mostly like catch block that you wrote(with exception when circuit will be in open state and original method will not be attempted).
#Component
public class ClientApiFallback implements ClientApi {
#Override
public String getValueFromApi(){
return "Catch from fallback";
}
}
Lastly, you just need to specify the fallback class for your feign client.
#FeignClient(name = "api-service", url ="localhost:8888", fallback = ClientApiFallback.class)
public interface ClientApi extends SomeApi {
}
That way your method getValueFromApi is fail safe. If,
for any reason, any uncaught exceptions escape from getValueFromApi the ClientApiFallback method will be called.
To enable circuit breaker and also configure your application to deal with unexpected errors, you need to:
1.- Enable the circuit breaker itself
#SpringBootApplication
#EnableFeignClients("com.perritotutorials.feign.client")
#EnableCircuitBreaker
public class FeignDemoClientApplication {
2.- Create your fallback bean
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
#Setter
private Throwable cause;
#Override
public void savePet(#RequestBody Map<String, ?> pet) {
log.error("You are on fallback interface!!! - ERROR: {}", cause);
}
}
Some things you must keep in mind for fallback implementations:
Must be marked as #Component, they are unique across the application.
Fallback bean should have a Prototype scope because we want a new one to be created for each exception.
Use constructor injection for testing purposes.
3.- Your ErrorDecoder, to implement fallback startegies depending on the HTTP error returned:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyCustomBadRequestException();
}
if (response.status() >= 500) {
return new RetryableException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
4.- In your configuration class, add the Retryer and the ErrorDecoder into the Spring context:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default();
}
You can also add customization to the Retryer:
class CustomRetryer implements Retryer {
private final int maxAttempts;
private final long backoff;
int attempt;
public CustomRetryer() {
this(2000, 5); //5 times, each 2 seconds
}
public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
#Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}
}
If you want to get a functional example about how to implement Feign in your application, read this article.

WebSocket messages are not delivered all the times

I have an application with WebSockets using spring-boot application as backend and Stomp/SockJS in the client side, the spring-boot application consume JMS queue messages and notify the changes to the right user. What is the problem? Sometimes works and sometimes doesn't work, same code and users could work or not.
The client side code is a bit more difficult to copy here because it's integrate over react/redux application but basically is a subscription to two different channels, both defined in the configuration of Spring. The sessions are created correctly according to debug information but just sometimes the message is processed to send it to connected sessions.
This is the configuration class for Spring.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/stomp")
.setAllowedOrigins("*")
.withSockJS();
}
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry
.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/xxxx/yyyy", "/ccccc");
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Object raw = message
.getHeaders()
.get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
Object name = ((Map<?,?>) raw).get("email");
if (name instanceof LinkedList) {
String user = ((LinkedList<?>) name).get(0).toString();
accessor.setUser(new User(user));
}
}
}
return message;
}
});
}
}
This is the JMS listener to process queue message and send it to specific user.
#Component
public class UserEventListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final SimpMessagingTemplate template;
#Autowired
public UserEventListener(SimpMessagingTemplate pTemplate) {
this.template = pTemplate;
}
#JmsListener(destination="user/events")
public void onStatusChange(Map<String, Object> props) {
if (props.containsKey("userEmail")) {
logger.debug("Event for user received: {}", props.get("userEmail"));
template.convertAndSendToUser((String)props.get("userEmail"), "/ccccc", props);
}
}
}
Edit 1:
After more debugging the times when doesn't work the "session" for WebSocket seems to be lost by Spring configuration. I don't see any log information about "Disconnected" messages or something similar, besides if I debug remotely the server when this happens the problem doesn't appears during debugging session. Some idea? The class from Spring where session disappear is DefaultSimpUserRegistry.
After more research I found a question with the same problem and the solution here. Basically the conclusion is this:
Channel interceptor is not the right place to authenticate user, we need to change it with a custom handshake handler.

How to pause #JmsListener in my spring boot application?

Here are my Hornetq configuration in spring boot.
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.persistent=true
spring.hornetq.port=5445
spring.hornetq.embedded.queues=jms.testqueue
Here is my Producer
public class Producer {#Inject
private JmsTemplate jmsTemplate;
public void resolveError( String message) {
try{
jmsTemplate.convertAndSend(DATA_QUEUE, message);
}catch(Exception e){
//log error
}
}}
Here is my Consumer
#JmsListener(destination = DATA_QUEUE)
public void consume(String message) throws InterruptedException {
log.info("Receiving event: {}", message);
try {
//do stuff with message
}catch (Exception e){
log.error(e.toString());
}
}
Here is my config file
#Configuration#EnableJms public class JmsConfig {
public static final String LOGGING_SCRAPPER_KEY ="DATA_SYNC_ERROR";
public static final String DATA_QUEUE = "jms.testqueue"; }
I want to slow down the consuming process of #JMSlistener, I don't want to the JMS listener hit the queue all the time any help is appreciated, thanks!!
The listeners that are created under the covers for each #JmsListener annotated method are held in a registry as explained in the documentation
If you want to pause your listener, it is very easy to look it up and stop it. Let's assume you have a way to invoke the following bean (JMX endpoint, secure rest mapping, whatever):
static class YourService {
private final JmsListenerEndpointRegistry registry;
#Autowired
public YourService(JmsListenerEndpointRegistry registry) {
this.registry = registry;
}
public void stopListener() {
this.registry.getListenerContainer("myListener").stop();
}
public void startListener() {
this.registry.getListenerContainer("myListener").start();
}
}
Then you need to associate the proper id to your listener (myListener) in the example above.
#JmsListener(id = "myListener", destination = DATA_QUEUE)
public void consume(String message) throws InterruptedException { ... }
I'm not able to set the consuming time of JmsListener but I found an alternative where I'm able to set delivery delay time limit on jmsTemplate instead, use jmsTemplate setDeliveryDelay which will delay sending it to the queue. Either way, it is delayed only if you go with delaying the consuming process of JMS listener you will have the message in the queue in my approach it won't be in the queue until the delay delivery time.

Spring SimpMessagingTemplate

I have an application which receive some data from RabbitMQ. Everything works fine, I mean in class where I have annotation #EnableScheduling.
#Scheduled(fixedDelay = 5000)
public void volumeGraphData() {
Random r = new Random();
Graph graph = new Graph();
graph.setVolume(r.nextInt(500));
String json = gson.toJson(graph);
MessageBuilder<byte[]> messageBuilder = MessageBuilder.withPayload(json.getBytes());
simpMessagingTemplate.send("/" + volumeGraph, messageBuilder.build());
}
But when I would like to process messages received by Queue Listener from RabbitMQ (this works too) and pass them through to specific context for Stomp WebSocket using SimpMessagingTemplate I cannot do that. SimpMessagingTemplate is defined in dispatcher-servlet.xml, but configuration related with RabbitMQ is in root context. I tried to move everything to one context, but it does not work. Anyone has similar case that one I have ?
I finally managed to fix this. So, basically you need move your beans related with Spring Messaging/WebSocket to one common bean.
That's why in my root context I have such lines :
<!-- Fix for IntelliJ 13.1 https://youtrack.jetbrains.com/issue/IDEA-123964 -->
<context:component-scan base-package="org.springframework.web.socket.config"/>
where in package pl.garciapl.program.service.config is located class responsible for configuration of WebSockets :
#Configuration
#EnableWebSocketMessageBroker
#Component("messageBroker")
public class MessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/test").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
}
#Override
public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
}
#Override
public void configureClientOutboundChannel(ChannelRegistration channelRegistration) {
}
#Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add(new MappingJackson2MessageConverter());
return false;
}
}
Remember to store your beans which use SimpMessagingTemplate in the same context where you defined this above class.

Resources