How to keep STOMP connection longer? - java

I'm currently writing a test echo client for STOMP over Websocket server in Java. However I noticed that the connection is somehow unpredictable as usually it's closed before message received thus client produces exception
java.io.IOException: java.util.concurrent.ExecutionException: java.io.IOException: Unable to write the complete message as the WebSocket connection has been closed
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.startMessageBlock(WsRemoteEndpointImplBase.java:282) ~[tomcat-embed-websocket-8.0.30.jar:8.0.30]
at org.apache.tomcat.websocket.WsSession.sendCloseMessage(WsSession.java:584) [tomcat-embed-websocket-8.0.30.jar:8.0.30]
And here is my simple test client
#Slf4j
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = SpringApplicationContextLoader.class, classes = Application.class)
#WebIntegrationTest(randomPort = true)
public class WebSocketConfigurationIT {
#Value("${local.server.port}")
private int port;
private SockJsClient sockJsClient;
#Before
public void setUp() {
final WebSocketTransport webSocketTransport = new WebSocketTransport(new StandardWebSocketClient());
final RestTemplateXhrTransport restTemplateXhrTransport = new RestTemplateXhrTransport(new RestTemplate());
sockJsClient = new SockJsClient(Lists.newArrayList(webSocketTransport, restTemplateXhrTransport));
}
#Test
public void testEcho() throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
StompSessionHandler stompSessionHandler = new StompSessionHandlerAdapter() {};
WebSocketStompClient webSocketStompClient = new WebSocketStompClient(sockJsClient);
webSocketStompClient.setDefaultHeartbeat(new long[]{0, 0});
webSocketStompClient.setMessageConverter(new MappingJackson2MessageConverter());
ListenableFuture<StompSession> connect = webSocketStompClient.connect("ws://localhost:{port}/api/ws/media/socket", stompSessionHandler, port);
StompSession session = connect.get();
String message = UUID.randomUUID().toString();
log.debug("sending {}", message);
session.send("/echo/" + message, null);
session.subscribe("/topic/echo/" + message, new StompFrameHandler() {
#Override
public Type getPayloadType(final StompHeaders headers) {
return String.class;
}
#Override
public void handleFrame(final StompHeaders headers, final Object payload) {
log.debug("received {}", payload);
assertEquals(message, payload);
countDownLatch.countDown();
}
});
// wait for messange being echoed
if (!countDownLatch.await(15, TimeUnit.SECONDS)) {
fail("message not received");
}
}
#Controller
public static class EchoController {
#MessageMapping("/echo/{message}")
public String echo(SimpMessageHeaderAccessor simpMessageHeaderAccessor, #DestinationVariable("message") String message) {
log.debug("header {}", simpMessageHeaderAccessor);
log.debug("echoed {}", message);
return message;
}
}
}
And configuration class
#Configuration
#EnableScheduling
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/api/ws/media/socket").withSockJS();
}
#Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
super.configureMessageBroker(registry);
//registry.setApplicationDestinationPrefixes("/");
registry.enableSimpleBroker("/topic", "/queue");
}
}

Related

Client hangs on sending messages to RabbitMQ with Java client

I've implemented RabbitMQ publisher and consumer in reactive manner with Java, but my publishing functionality hangs channel. The queue itself, declaring a queue and consuming however works fine, I've tested it with admin's management UI. When attempting to send more messages I don't see any more of logs like "queue declare success" or "delivering message to exchange...". By the way I know I do not need declareQueue in deliver(), but I added it to verify if communication in this particular channel works.
My code is:
#Slf4j
#Component
public class RabbitConfigurator {
private TasksQueueConfig cfg;
private ReceiverOptions recOpts;
private List<Address> addresses;
private Utils.ExceptionFunction<ConnectionFactory, Connection> connSupplier;
public RabbitConfigurator(TasksQueueConfig cfg) {
this.cfg = cfg;
addresses = cfg
.getHosts()
.stream()
.map(Address::new)
.collect(Collectors.toList());
connSupplier = cf -> {
LOG.info("initializing new RabbitMQ connection");
return cf.newConnection(addresses, "dmTasksProc");
};
}
#Bean
public ConnectionFactory rabbitMQConnectionFactory() {
ConnectionFactory cf = new ConnectionFactory();
cf.setHost(cfg.getHosts().get(0));
cf.setPort(5672);
cf.setUsername(cfg.getUsername());
cf.setPassword(cfg.getPassword());
return cf;
}
#Bean
public Sender sender(ConnectionFactory connFactory) {
SenderOptions sendOpts = new SenderOptions()
.connectionClosingTimeout(Duration.parse(cfg.getConnectionTimeout()))
.connectionFactory(connFactory)
.connectionSupplier(connSupplier)
.connectionSubscriptionScheduler(Schedulers.elastic());
return RabbitFlux.createSender(sendOpts);
}
#Bean
public Receiver receiver(ConnectionFactory connFactory) {
ReceiverOptions recOpts = new ReceiverOptions()
.connectionClosingTimeout(Duration.parse(cfg.getConnectionTimeout()))
.connectionFactory(connFactory)
.connectionSupplier(connSupplier)
.connectionSubscriptionScheduler(Schedulers.elastic());
return RabbitFlux.createReceiver(recOpts);
}
#Bean
public Flux<Delivery> deliveryFlux(Receiver receiver) {
return receiver.consumeAutoAck(cfg.getName(), new ConsumeOptions().qos(cfg.getPrefetchCount()));
}
#Bean
public AmqpAdmin rabbitAmqpAdmin(ConnectionFactory connFactory) {
return new RabbitAdmin(new CachingConnectionFactory(connFactory));
}
}
and the consumer/publisher:
#Slf4j
#Service
public class TasksQueue implements DisposableBean {
private TasksQueueConfig cfg;
private ObjectMapper mapper;
private Flux<Delivery> deliveryFlux;
private Receiver receiver;
private Sender sender;
private Disposable consumer;
public TasksQueue(TasksQueueConfig cfg, AmqpAdmin amqpAdmin, ObjectMapper mapper, Flux<Delivery> deliveryFlux,
Receiver receiver, Sender sender) {
this.cfg = cfg;
this.mapper = mapper;
this.deliveryFlux = deliveryFlux;
this.receiver = receiver;
this.sender = sender;
amqpAdmin.declareQueue(new Queue(cfg.getName(), false, false, false));
consumer = consume();
}
public Mono<Void> deliver(Flux<Task> tasks) {
var pub = sender.sendWithPublishConfirms(
tasks.map(task -> {
try {
String exchange = "";
LOG.debug("delivering message to exchange='{}', routingKey='{}'", exchange, cfg.getName());
return new OutboundMessage(exchange, cfg.getName(), mapper.writeValueAsBytes(task));
} catch(JsonProcessingException ex) {
throw Exceptions.propagate(ex);
}
}));
return sender.declareQueue(QueueSpecification.queue(cfg.getName()))
.flatMap(declareOk -> {
LOG.info("queue declare success");
return Mono.just(declareOk);
})
.thenMany(pub)
.doOnError(JsonProcessingException.class, ex -> LOG.error("Cannot prepare queue message:", ex))
.doOnError(ex -> LOG.error("Failed to send task to the queue:", ex))
.map(res -> {
if(res.isAck()) {
LOG.info("Message {} sent successfully", new String(res.getOutboundMessage().getBody()));
return res;
} else {
LOG.info("todo");
return res;
}
})
.then();
}
private Disposable consume() {
return deliveryFlux
.retryWhen(Retry.fixedDelay(10, Duration.ofSeconds(1)))
.doOnError(err -> {
LOG.error("tasks consumer error", err);
})
.subscribe(m -> {
LOG.info("Received message {}", new String(m.getBody()));
});
}
#Override
public void destroy() throws Exception {
LOG.info("Cleaning up tasks queue resources");
consumer.dispose();
receiver.close();
sender.close();
}
}
Five minutes after attempting to send message I get log:
r.r.ChannelCloseHandlers$SenderChannelCloseHandler:47: closing channel 1 by signal cancel
r.r.ChannelCloseHandlers$SenderChannelCloseHandler:53: Channel 1 didn't close normally: null
Big thanks for input in advance!

Receiving only Headers after connecting Stomp over SockJS and Subscribing for messages

I am writing a client to test WebSocket connections and receive data.
After establishing the Websocket over Stomp, I am only receiving headers
after subscription, I am not getting the message content/body (its not invoking handleFrame() at all)
I am seeing MessageConversionExceptions. Please look into the below snippets. I have highlighted and commented few important pieces.
Snippets (Web Socket client) :
public class StompService {
public static final String WS_URL_SUFFIX = "/monitoring-channel";
public StompSession createStompWSConnection(String socketURL) throws Exception {
List<Transport> transports = new ArrayList<Transport>();
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
SockJsClient sockJsClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
stompClient.setMessageConverter(new StringMessageConverter());
//socketURL is "http://localhost:8090/monitoring-channel".. This works in the Node client
StompSession session = stompClient.connect(socketURL, new SessionHandler()).get();
System.out.println("Session : " + session);
return session;
}
public boolean subscribeStreamData(StompSession wsSession, String watchId, String type, StompFrameHandler handler) {
type = type.toUpperCase()
String topic = "/topic/" + type + "/" + watchId;
wsSession.subscribe(topic, handler);
return true;
}
}
public class DefaultStompFrameHandler implements StompFrameHandler {
private static final Logger log = LogManager.getLogger();
BlockingQueue<String> blockingQueue;
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
log.debug("getPayloadType() :: stompHeaders = " + stompHeaders); //This is printing
return String.class;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
//THIS BLOCK IS NOT GETTING INVOKED AT ALL
System.out.println("Object Recieved : " + new String((byte[]) o));
blockingQueue.offer(new String((byte[]) o));
}
}
public void main(String... args) throws Exception {
//...
StompSession session = stompService.createStompWSConnection("http://localhost:8090" + StompService.WS_URL_SUFFIX);
System.out.println("Session = " + session.getSessionId());
String watchId = "123";
System.out.println("Watch Id : " + watchId);
stompService.subscribeStreamData(session, watchId, "WAVEFORMS", new DefaultStompFrameHandler());
}
public class SessionHandler implements StompSessionHandler {
#Override
public void afterConnected(StompSession stompSession, StompHeaders stompHeaders) {
System.out.println("Connected!"); //GETTING INVOKED
}
#Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println("~~~~~");
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
Throwable exception) {
System.out.println("~~~~~~~~~~~~~~~");
//GETTING EXCEPTION : org.springframework.messaging.converter.MessageConversionException: No suitable converter for payload type [class [B] from handler type [class com.test.kymaloadtest.service.DefaultStompFrameHandler]
//No suitable converter for payload type [class [B] from handler type [class com.test.kymaloadtest.service.DefaultStompFrameHandler]
}
#Override
public void handleTransportError(StompSession session, Throwable exception){
System.out.println("~~~~~~~~~~~~~~~");
}
}
But the simulating the same client works in NodeJS.
I tried my best but was not able to figure out why I am only getting headers but not the message content/data.
This is what I see in console :
[DEBUG] 2019-07-25 20:09:19.144 [WebSocketClient-AsyncIO-1] DefaultStompFrameHandler - getPayloadType() :: stompHeaders = {destination=[/topic/WAVEFORMS/d7e0b709-0150-4b93-b9d3-0c279c69c929], content-type=[application/json;charset=UTF-8], subscription=[0], message-id=[c60b1afba030419887768d39353f2b45-27320], content-length=[1794]}
[DEBUG] 2019-07-25 20:09:19.393 [WebSocketClient-AsyncIO-1] DefaultStompFrameHandler - getPayloadType() :: stompHeaders = {destination=[/topic/WAVEFORMS/d7e0b709-0150-4b93-b9d3-0c279c69c929], content-type=[application/json;charset=UTF-8], subscription=[0], message-id=[c60b1afba030419887768d39353f2b45-27321], content-length=[1748]}
[DEBUG] 2019-07-25 20:09:19.645 [WebSocketClient-AsyncIO-1] DefaultStompFrameHandler - getPayloadType() :: stompHeaders = {destination=[/topic/WAVEFORMS/d7e0b709-0150-4b93-b9d3-0c279c69c929], content-type=[application/json;charset=UTF-8], subscription=[0], message-id=[c60b1afba030419887768d39353f2b45-27322], content-length=[1723]}
Thank you.
The issue is resolved by making the following edits :
[1] - By using SimpleMessageConverter : stompClient.setMessageConverter(new SimpleMessageConverter());
[2] - Changing the return type in SessionHandler : return byte[].class;
public StompSession createStompWSConnection(String socketURL, StompSessionHandler sessionHandler) throws Exception {
List<Transport> transports = new ArrayList<Transport>();
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
// transports.add(new RestTemplateXhrTransport());
SockJsClient sockJsClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
stompClient.setMessageConverter(new SimpleMessageConverter());
StompSession session = stompClient.connect(socketURL,sessionHandler).get();
System.out.println("Session : " + session);
return session;
}
#Override
public Type getPayloadType(StompHeaders headers) {
// log.info("getPayloadType() :: stompHeaders = " + headers);
return byte[].class;
}

How to use AmazonSQS listener with two accounts

I have application with two worker classes. I want them to pull from AWS SQS ,but from two different accounts.
I am using #SQSListener to achive this. I am having trouble to set the right AmazonSQS client for each queue.Tried to use custom destionationResolver but again it cannot access the right amazonSQS client bean.
I'm using AmazonSQSAsync maybe this is part of the problem. Whit the custom destination resolver i am getting access denied for one of the queues.
My config code:
#Bean(destroyMethod = "shutdown")
#Primary
public AmazonSQSAsync amazonSQS() {
AmazonSQSAsync amazonSQSAsyncClient = new AmazonSQSAsyncClient(new AWSCredentialsProvider() {
public void refresh() {}
public AWSCredentials getCredentials() {
return new AWSCredentials() {
public String getAWSSecretKey() {return secretKey;}
public String getAWSAccessKeyId() {return accessKey;}
};
}
});
QueueBufferConfig config = new QueueBufferConfig();
config.setMaxBatchOpenMs(maxBatchOpenMs);
config.setMaxBatchSize(maxBatchSize);
LOGGER.info("SQS Client Initialized Successfully");
return new AmazonSQSBufferedAsyncClient(amazonSQSAsyncClient, config);
}
#Bean(destroyMethod = "shutdown")
#Qualifier("workerSQS")
public AmazonSQSAsync workerSQS() {
final ClientConfiguration cc = new ClientConfiguration();
cc.setConnectionTimeout(listenerConnectionTimeout);
cc.setSocketTimeout(listenerSocketTimeout);
cc.setMaxConnections(listenerMaxConnection);
cc.setRequestTimeout(listenerRequestTimeout);
cc.setUseReaper(true);
//cc.setConnectionMaxIdleMillis();
AWSCredentialsProvider awsCredentialsProvider = new AWSCredentialsProvider() {
public void refresh() {}
public AWSCredentials getCredentials() {
return new AWSCredentials() {
public String getAWSSecretKey() {return routingSecretKey;}
public String getAWSAccessKeyId() {return routingAccessKey;}
};
}
};
AmazonSQSAsync amazonSQSAsyncClient = AmazonSQSAsyncClientBuilder.standard()
.withCredentials(awsCredentialsProvider)
.withRegion(Regions.US_EAST_1)
.withClientConfiguration(cc)
.build();
// See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-client-side-buffering-request-batching.html
// for QueueBufferConfig Configuration Parameters
QueueBufferConfig config = new QueueBufferConfig();
config.setLongPoll(true);
return new AmazonSQSBufferedAsyncClient(amazonSQSAsyncClient, config);
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory() {
SimpleMessageListenerContainerFactory msgListenerContainerFactory = new SimpleMessageListenerContainerFactory();
msgListenerContainerFactory.setBackOffTime(listenerBackOffTime);
msgListenerContainerFactory.setWaitTimeOut(listenerWaitTimeOut);
msgListenerContainerFactory.setVisibilityTimeout(listenerVisibilityTimeOut);
msgListenerContainerFactory.setMaxNumberOfMessages(listenerMaxMessagesPerPoll);
msgListenerContainerFactory.setDestinationResolver(destinationResolver());
return msgListenerContainerFactory;
}
#Bean
public CustomDestinationResolver destinationResolver(){
return new CustomDestinationResolver();
}
#Component
public static class CustomDestinationResolver implements DestinationResolver{
#Autowired
private AmazonSQS amazonSQS;
#Autowired
#Qualifier("workerSQS")
private AmazonSQSAsync amazonSQSAsync;
#Override
public String resolveDestination(String name) throws DestinationResolutionException {
String queueName = name;
if (queueName.startsWith("tl")) {
try {
GetQueueUrlResult getQueueUrlResult = amazonSQSAsync.getQueueUrl(new GetQueueUrlRequest(name));
return getQueueUrlResult.getQueueUrl();
} catch (QueueDoesNotExistException var4) {
throw new DestinationResolutionException(var4.getMessage(), var4);
}
} else {
try {
GetQueueUrlResult getQueueUrlResult = amazonSQS.getQueueUrl(new GetQueueUrlRequest(name));
return getQueueUrlResult.getQueueUrl();
} catch (QueueDoesNotExistException var4) {
throw new DestinationResolutionException(var4.getMessage(), var4);
}
}
}
}
I was not able to do it with SQS Listener,so i tried with JMS listener and it worked.
I simply created two JMS listenerContainerFactory and used them. Each listener have different AWS account

Manualy NACK message from AmqpInboundChannelAdapter

This is my current code:
#Bean
public IntegrationFlow someFlow() {
return IntegrationFlows
.from(someInboundAdapter())
.transform(new JsonToObjectTransformer(SomeObject.class))
.filter((SomeObject s) -> s.getId()!=null && s.getId().isRealId(), f -> f.discardChannel(manualNackChannel()))
.channel(amqpInputChannel())
.get();
}
#ServiceActivator(inputChannel = "manualNackChannel")
public void manualNack(#Header(AmqpHeaders.CHANNEL) Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) Long tag) throws IOException {
channel.basicNack(tag, false, false);
}
#Bean
public AmqpInboundChannelAdapter someInboundAdapter() {
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(someListenerContainer());
adapter.setErrorChannel(manualNackChannel()); //NOT WORKING
return adapter;
}
#Bean
public SimpleMessageListenerContainer someListenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(commonConfig.connectionFactory());
listenerContainer.setQueues(someQueue());
listenerContainer.setConcurrentConsumers(4);
listenerContainer.setMessageConverter(jackson2JsonConverter());
listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
listenerContainer.setConsumerTagStrategy(consumerTagStrategy());
listenerContainer.setAfterReceivePostProcessors(new GUnzipPostProcessor());
listenerContainer.setAdviceChain(commonConfig.retryInterceptor()); //reties 3 times and RejectAndDontRequeueRecoverer
return listenerContainer;
}
Here I use MANUAL ACK-ing, since I want to ACK/NACK message only if processed sucesfully in last part of IntegrationFlow.
Here, in case that message cannot be deserialized, retryInterceptor is invoked, but after exausting all the retries, I need to be able to manually NACK the message. I expected to do it with setErrorChannel method on adapter, but I cannot get AMQP channel headers in manualNack.
Is this proper way to manually NACK message from AmqpInboundChannelAdapter?
UPDATE
I guess this is my current solution, but don't know if good enough:
private ErrorMessageStrategy nackStrategy(){
return (throwable, attributes) -> {
Object inputMessage = attributes.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY);
return new ErrorMessage(throwable, ((Message)inputMessage).getHeaders());
};
}
#Bean
public AmqpInboundChannelAdapter someInboundAdapter() {
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(someListenerContainer());
adapter.setRecoveryCallback(new ErrorMessageSendingRecoverer(manualNackChannel(), nackStrategy()));
adapter.setRetryTemplate(commonConfig.retryTemplate());
return adapter;
}
in case that message cannot be deserialized
Since AMQP message cannot be deserialized, the Spring Message isn't created and therefore no AmqpHeaders.CHANNEL header.
I'm not sure though how that ErrorMessageSendingRecoverer can help you here because deserialization really happens on the SimpleMessageListenerContainer level a bit earlier than onMessage() in the AmqpInboundChannelAdapter.
Not sure yet how to help you but maybe you can share some simply Spring Boot project to play from our side? Thanks
Here is the full working code for this example. You can test ACK/NACK on 3 REST endpoints:
http://localhost:8080/sendForAck -> will send Object SomeObject to queue proba, transform it, forward to exchange probaEx and ACK it after that
http://localhost:8080/sendForNack -> will send malformed byte[] message which cannot be deserialized and will be NACK-ed.
http://localhost:8080/sendForNack2 -> will create malformed json message and will be NACK-ed with InvalidFormatException
#Controller
#EnableAutoConfiguration
#Configuration
public class SampleController {
#Autowired
public RabbitTemplate rabbitTemplate;
#RequestMapping("/sendForAck")
#ResponseBody
String sendForAck() {
SomeObject s = new SomeObject();
s.setId(2);
rabbitTemplate.convertAndSend("", "proba", s);
return "Sent for ACK!";
}
#RequestMapping("/sendForNack")
#ResponseBody
String sendForNack() {
rabbitTemplate.convertAndSend("", "proba", new byte[]{1,2,3});
return "Sent for NACK!";
}
#RequestMapping("/sendForNack2")
#ResponseBody
String sendForNack2() {
MessageProperties p = new MessageProperties();
p.getHeaders().put("__TypeId__", "SampleController$SomeObject");
p.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
p.setPriority(0);
p.setContentEncoding("UTF-8");
p.setContentType("application/json");
rabbitTemplate.send("", "proba", new org.springframework.amqp.core.Message("{\"id\":\"abc\"}".getBytes(), p));
return "Sent for NACK2!";
}
static class SomeObject{
private Integer id;
public Integer getId(){return id;}
public void setId(Integer id){ this.id=id; }
#Override
public String toString() {
return "SomeObject{" +
"id=" + id +
'}';
}
}
#Bean
public IntegrationFlow someFlow() {
return IntegrationFlows
.from(someInboundAdapter())
.transform(new JsonToObjectTransformer(SomeObject.class))
.filter((SomeObject s) -> s.getId()!=null, f -> f.discardChannel(manualNackChannel()))
.transform((SomeObject s) -> {s.setId(s.getId()*2); return s;})
.handle(amqpOutboundEndpoint())
.get();
}
#Bean
public MessageChannel manualNackChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel manualAckChannel() {
return new DirectChannel();
}
#ServiceActivator(inputChannel = "manualNackChannel")
public void manualNack(#Header(AmqpHeaders.CHANNEL) Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) Long tag, #Payload Object p) throws IOException {
channel.basicNack(tag, false, false);
System.out.println("NACKED " + p);
}
#ServiceActivator(inputChannel = "manualAckChannel")
public void manualAck(#Header(AmqpHeaders.CHANNEL) Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) Long tag, #Payload Object p) throws IOException {
channel.basicAck(tag, false);
System.out.println("ACKED " + p);
}
private ErrorMessageStrategy nackStrategy() {
return (throwable, attributes) -> {
Message inputMessage = (Message)attributes.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY);
return new ErrorMessage(throwable, inputMessage.getHeaders());
};
}
#Bean
public AmqpInboundChannelAdapter someInboundAdapter() {
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(someListenerContainer());
adapter.setRecoveryCallback(new ErrorMessageSendingRecoverer(manualNackChannel(), nackStrategy()));
adapter.setRetryTemplate(retryTemplate());
return adapter;
}
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(10);
backOffPolicy.setMaxInterval(5000);
backOffPolicy.setMultiplier(4);
template.setBackOffPolicy(backOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(4);
template.setRetryPolicy(retryPolicy);
return template;
}
#Bean
public AmqpOutboundEndpoint amqpOutboundEndpoint() {
AmqpOutboundEndpoint outboundEndpoint = new AmqpOutboundEndpoint(ackTemplate());
outboundEndpoint.setConfirmAckChannel(manualAckChannel());
outboundEndpoint.setConfirmCorrelationExpressionString("#root");
outboundEndpoint.setExchangeName("probaEx");
return outboundEndpoint;
}
#Bean
public MessageConverter jackson2JsonConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public RabbitTemplate ackTemplate() {
RabbitTemplate ackTemplate = new RabbitTemplate(connectionFactory());
ackTemplate.setMessageConverter(jackson2JsonConverter());
return ackTemplate;
}
#Bean
public Queue someQueue() {
return QueueBuilder.nonDurable("proba").build();
}
#Bean
public Exchange someExchange(){
return ExchangeBuilder.fanoutExchange("probaEx").build();
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost("10.10.121.137");
factory.setPort(35672);
factory.setUsername("root");
factory.setPassword("123456");
factory.setPublisherConfirms(true);
return factory;
}
#Bean
public SimpleMessageListenerContainer someListenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory());
listenerContainer.setQueues(someQueue());
listenerContainer.setMessageConverter(jackson2JsonConverter());
listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return listenerContainer;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleController.class, args);
}
}
Still, the question remains if this private ErrorMessageStrategy nackStrategy() could be written in a better way?

Why does netty dont send my own POJO over network?

i try to send the POJO "SecureMessageServerClientMessage" over network to an client. Both are created with the netty-Framework for Async. Socket communications. I am really new to netty, so i take on of the Example code and want to change it, so that it send my POJO over network. I know that is a lot of code but its nearly the sample of them but it dont work .. It would be really nice if some one could tell my why it does not work ..
Here are my classes: Server Main:
public final class SecureChatServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "8992"));
public static void main(String[] args) throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new SecureChatServerInitializer(sslCtx));
b.bind(PORT).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
SecureChatServerHandler:
public class SecureChatServerHandler extends SimpleChannelInboundHandler<SecureMessageServerClientMessage> {
static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private ArrayList<SecureMessageServerUser> userList = new ArrayList();
#Override
public void channelActive(final ChannelHandlerContext ctx) {
// Once session is secured, send a greeting and register the channel to the global channel
// list so the channel received the messages from others.
ctx.pipeline().get(SslHandler.class).handshakeFuture().addListener(
new GenericFutureListener<Future<Channel>>() {
#Override
public void operationComplete(Future<Channel> future) throws Exception {
channels.add(ctx.channel());
}
});
}
#Override
public void channelRead0(ChannelHandlerContext ctx, SecureMessageServerClientMessage msg ) throws Exception {
System.out.println("Nachricht empfangen!");
System.out.println("Typ: "+msg.getType());
//Send the received message to all channels but the current one.
for (Channel c: channels) {
if (c != ctx.channel()) {
c.writeAndFlush("[" + ctx.channel().remoteAddress() + "] " + msg + '\n');
} else {
c.writeAndFlush("[you] " + msg + '\n');
}
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
Initializer (Server):
public class SecureChatServerInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public SecureChatServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// Add SSL handler first to encrypt and decrypt everything.
// In this example, we use a bogus certificate in the server side
// and accept any invalid certificates in the client side.
// You will need something more complicated to identify both
// and server in the real world.
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
// On top of the SSL handler, add the text line codec.
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new ObjectEncoder());
// and then business logic.
pipeline.addLast(new SecureChatServerHandler());
}
}
Here is the code of my client:
public final class SecureChatClient {
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8992"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).handler(new SecureChatClientInitializer(sslCtx));
// Start the connection attempt.
Channel ch = b.connect(HOST, PORT).sync().channel();
// Read commands from the stdin.
ChannelFuture lastWriteFuture = null;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for (;;) {
String line = in.readLine();
if (line == null) {
break;
}
if("send".equals(line)){
System.out.println("Test");
lastWriteFuture = ch.writeAndFlush(new SecureMessageServerClientMessage(1, "test"));
if(lastWriteFuture.isSuccess()){
System.out.println("Success");
}
}
// Sends the received line to the server.
//lastWriteFuture = ch.writeAndFlush(line + "\r\n");
// If user typed the 'bye' command, wait until the server closes
// the connection.
if ("bye".equals(line.toLowerCase())) {
ch.closeFuture().sync();
break;
}
}
// Wait until all messages are flushed before closing the channel.
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
} finally {
// The connection is closed automatically on shutdown.
group.shutdownGracefully();
}
}
}
And here the handler:
public class SecureChatClientHandler extends SimpleChannelInboundHandler<SecureMessageServerClientMessage> {
#Override
public void channelRead0(ChannelHandlerContext ctx, SecureMessageServerClientMessage msg) throws Exception {
System.err.println(msg);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
And at last my POJO:
public class SecureMessageServerClientMessage implements Serializable{
private String message;
private int type;
static final int LISTUSER = 0, MESSAGE = 1, LOGOUT = 2, LOGIN = 3;
private String sender;
private String empfaenger;
private ArrayList<SecureMessageServerUser> userList = new ArrayList();
//Erste MSG, Login am Server
public SecureMessageServerClientMessage(int type, String sender){
this.type=type;
this.sender=sender;
}
//Nur für den Clienten, als Anfrage für die user-List
public SecureMessageServerClientMessage(int type){
this.type=type;
}
//Für MessageTyp 0 - LISTUSER, es wird nur die aktuelle User-Liste übertragen
public SecureMessageServerClientMessage(int type, ArrayList userList){
this.userList=userList;
}
//Für MessageTyp 1 - MESSAGE Es wird der Empfänger, und die Nachricht übertragen
public SecureMessageServerClientMessage(int type, String message, String empfaenger, String sender){
this.type=type;
this.message=message;
this.sender=sender;
this.empfaenger=empfaenger;
}
//Für MessageTyp 1 - Msg, die weitergeleitet werden
public SecureMessageServerClientMessage(int type, String message, String sender){
this.type=type;
this.message=message;
this.sender=sender;
}
public String getMessage(){
return this.message;
}
public int getType(){
return type;
}
public String getSender(){
return this.sender;
}
public ArrayList getList() {
return userList;
}
public ArrayList getUserList() {
return userList;
}
public String getEmpfaenger(){
return this.empfaenger;
}
}

Categories

Resources