RabbitMQ doesn't choose a right consumer - java

I took the example from here http://www.rabbitmq.com/tutorials/tutorial-six-java.html, added one more RPC call from RPCClient and added some logging into stdout. As a result, when the second call is executed, rabbitmq uses the consumer with wrong correlation id which is not expected behavior. Is it a bug or am I getting anything wrong?
RPCServer:
package com.foo.rabbitmq;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RPCServer {
private static final String RPC_QUEUE_NAME = "sap-consume";
private static int fib(int n) {
if (n ==0) return 0;
if (n == 1) return 1;
return fib(n-1) + fib(n-2);
}
public static void main(String[] argv) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
Connection connection = null;
try {
connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
channel.basicQos(1);
System.out.println(" [x] Awaiting RPC requests");
Consumer consumer = new DefaultConsumer(channel) {
#Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
AMQP.BasicProperties replyProps = new AMQP.BasicProperties
.Builder()
.correlationId(properties.getCorrelationId())
.build();
String response = "";
try {
String message = new String(body,"UTF-8");
int n = Integer.parseInt(message);
System.out.println(" [.] fib(" + message + ")");
response += fib(n);
}
catch (RuntimeException e){
System.out.println(" [.] " + e.toString());
}
finally {
channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
// RabbitMq consumer worker thread notifies the RPC server owner thread
synchronized(this) {
this.notify();
}
}
}
};
channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
// Wait and be prepared to consume the message from RPC client.
while (true) {
synchronized(consumer) {
try {
consumer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
finally {
if (connection != null)
try {
connection.close();
} catch (IOException _ignore) {}
}
}
}
RPCCLient:
package com.bar.rabbitmq;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
public class RPCClient {
private Connection connection;
private Channel channel;
private String requestQueueName = "sap-consume";
private String replyQueueName;
public RPCClient() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
connection = factory.newConnection();
channel = connection.createChannel();
replyQueueName = channel.queueDeclare().getQueue();
}
public String call(String message) throws IOException, InterruptedException {
final String corrId = UUID.randomUUID().toString();
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
#Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if (properties.getCorrelationId().equals(corrId)) {
System.out.println("Correlation Id" + properties.getCorrelationId() + " corresponds to expected one.");
response.offer(new String(body, "UTF-8"));
} else {
System.out.println("Correlation Id" + properties.getCorrelationId() + " doesn't correspond to expected one " + corrId);
}
}
});
return response.take();
}
public void close() throws IOException {
connection.close();
}
public static void main(String[] argv) {
RPCClient rpc = null;
String response = null;
try {
rpc = new RPCClient();
System.out.println(" [x] Requesting fib(30)");
response = rpc.call("30");
System.out.println(" [.] Got '" + response + "'");
System.out.println(" [x] Requesting fib(40)");
response = rpc.call("40");
System.out.println(" [.] Got '" + response + "'");
} catch (IOException | TimeoutException | InterruptedException e) {
e.printStackTrace();
} finally {
if (rpc != null) {
try {
rpc.close();
} catch (IOException _ignore) {
}
}
}
}
}

Yes you found a bug in the tutorial code. I have opened a pull request to fix it here and you can find the explanation of what's happening as well:
https://github.com/rabbitmq/rabbitmq-tutorials/pull/174
NOTE: the RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.

This example is simplistic: it uses one queue for the reply. By sending a second request, you register a new consumer to the reply, but the consumer of the first request is still listening and actually steals the response of the second request. That's why the client seems to use the same correlation ID.
We updated the client code to use an exclusive, auto-delete queue for each request. This queue will be auto-deleted by the server because its only consumer is unsubscribed after the response has been received. This is a bit more involved but closer to a real-world scenario.
Note the best way to deal with the reply queue with RabbitMQ is to use direct reply-to. This uses pseudo-queues which are lighter than real queues. We don't mention direct reply-to in the tutorial to keep it as simple as possible, but this is the preferred feature to use in production.

Related

Google Cloud Pub/Sub, Java client not publishing messages

I'm trying to publish messages to a topic, using Google Cloud Pub/Sub. I've been following this tutorial. I've successfully managed to create Topic, with the following method.
public static Topic createTopic(String topic) throws IOException {
try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) {
ProjectTopicName topicName = ProjectTopicName.of(projectId, topic);
return topicAdminClient.createTopic(topicName);
}
}
The following method returns the topic-id from the newly created Topic.
public static String getTopicId(String topicName) throws IOException {
String topic_id = null;
try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) {
ListTopicsRequest listTopicsRequest =
ListTopicsRequest.newBuilder().setProject(ProjectName.format(projectId)).build();
ListTopicsPagedResponse response = topicAdminClient.listTopics(listTopicsRequest);
Iterable<Topic> topics = response.iterateAll();
for (Topic topic : topics) {
// return the topic id for the given topic
if (topic.getName().toLowerCase().contains(topicName.toLowerCase())) {
topic_id = topic.getName();
}
}
}
return topic_id;
}
But when I try to publish messages, using the following method
public static void publishMessages(String topic) throws Exception {
String topicId = getTopicId(topic);
ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId);
Publisher publisher = null;
try {
// Create a publisher instance with default settings bound to the topic
publisher = Publisher.newBuilder(topicName).build();
List<String> messages = Arrays.asList("first message", "second message");
for (final String message : messages) {
ByteString data = ByteString.copyFromUtf8(message);
PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build();
// Once published, returns a server-assigned message id (unique within the topic)
ApiFuture<String> future = publisher.publish(pubsubMessage);
// Add an asynchronous callback to handle success / failure
ApiFutures.addCallback(
future,
new ApiFutureCallback<String>() {
#Override
public void onFailure(Throwable throwable) {
if (throwable instanceof ApiException) {
ApiException apiException = ((ApiException) throwable);
// details on the API exception
System.out.println(apiException.getStatusCode().getCode());
System.out.println(apiException.isRetryable());
}
System.out.println("Error publishing message : " + message);
}
#Override
public void onSuccess(String messageId) {
// Once published, returns server-assigned message ids (unique within the topic)
System.out.println(messageId);
}
},
MoreExecutors.directExecutor());
}
} finally {
if (publisher != null) {
// When finished with the publisher, shutdown to free up resources.
publisher.shutdown();
publisher.awaitTermination(1, TimeUnit.MINUTES);
}
}
}
I get the following exception
Exception in thread "main" com.google.api.pathtemplate.ValidationException: Invalid character "/" in path section "projects/deft-idiom-234709/topics/test-topic".
at com.google.api.pathtemplate.PathTemplate.encodeUrl(PathTemplate.java:924)
at com.google.api.pathtemplate.PathTemplate.instantiate(PathTemplate.java:721)
at com.google.api.pathtemplate.PathTemplate.instantiate(PathTemplate.java:646)
at com.google.api.pathtemplate.PathTemplate.instantiate(PathTemplate.java:657)
at com.google.pubsub.v1.ProjectTopicName.toString(ProjectTopicName.java:119)
at com.google.cloud.pubsub.v1.Publisher.newBuilder(Publisher.java:460)
at pubsub.TopicAndPubSub.publishMessages(TopicAndPubSub.java:73)
at pubsub.TopicAndPubSub.main(TopicAndPubSub.java:121)
This is the entire class
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutureCallback;
import com.google.api.core.ApiFutures;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.rpc.ApiException;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.pubsub.v1.Publisher;
import com.google.cloud.pubsub.v1.TopicAdminClient;
import com.google.cloud.pubsub.v1.TopicAdminClient.ListTopicsPagedResponse;
import com.google.cloud.pubsub.v1.TopicAdminSettings;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.pubsub.v1.ListTopicsRequest;
import com.google.pubsub.v1.ProjectName;
import com.google.pubsub.v1.ProjectTopicName;
import com.google.pubsub.v1.PubsubMessage;
import com.google.pubsub.v1.Topic;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TopicAndPubSub {
private static ServiceAccountCredentials creds;
private static TopicAdminSettings topicAdminSettings;
private static String projectId;
static {
try {
creds = ServiceAccountCredentials.fromStream(new FileInputStream("C:/cred/Key.json"));
topicAdminSettings = TopicAdminSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(creds)).build();
projectId = creds.getProjectId();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Topic createTopic(String topic) throws IOException {
try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) {
ProjectTopicName topicName = ProjectTopicName.of(projectId, topic);
return topicAdminClient.createTopic(topicName);
}
}
public static String getTopicId(String topicName) throws IOException {
String topic_id = null;
try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) {
ListTopicsRequest listTopicsRequest =
ListTopicsRequest.newBuilder().setProject(ProjectName.format(projectId)).build();
ListTopicsPagedResponse response = topicAdminClient.listTopics(listTopicsRequest);
Iterable<Topic> topics = response.iterateAll();
for (Topic topic : topics) {
// return the topic id for the given topic
if (topic.getName().toLowerCase().contains(topicName.toLowerCase())) {
topic_id = topic.getName();
}
}
}
return topic_id;
}
public static void publishMessages(String topic) throws Exception {
String topicId = getTopicId(topic);
ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId);
Publisher publisher = null;
try {
// Create a publisher instance with default settings bound to the topic
publisher = Publisher.newBuilder(topicName).build();
List<String> messages = Arrays.asList("first message", "second message");
for (final String message : messages) {
ByteString data = ByteString.copyFromUtf8(message);
PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build();
// Once published, returns a server-assigned message id (unique within the topic)
ApiFuture<String> future = publisher.publish(pubsubMessage);
// Add an asynchronous callback to handle success / failure
ApiFutures.addCallback(
future,
new ApiFutureCallback<String>() {
#Override
public void onFailure(Throwable throwable) {
if (throwable instanceof ApiException) {
ApiException apiException = ((ApiException) throwable);
// details on the API exception
System.out.println(apiException.getStatusCode().getCode());
System.out.println(apiException.isRetryable());
}
System.out.println("Error publishing message : " + message);
}
#Override
public void onSuccess(String messageId) {
// Once published, returns server-assigned message ids (unique within the topic)
System.out.println(messageId);
}
},
MoreExecutors.directExecutor());
}
} finally {
if (publisher != null) {
// When finished with the publisher, shutdown to free up resources.
publisher.shutdown();
publisher.awaitTermination(1, TimeUnit.MINUTES);
}
}
}
public static void main(String[] args) throws Exception {
publishMessages("test-topic");
}
}
I can't seem to get it fixed. Can somebody please help?!
Turns out, I needed to build the Publisher using setCredentialsProvider. Had to change the following in publishMessagesmethod, from
publisher = Publisher.newBuilder(topicName).build();
To
publisher = Publisher.newBuilder(
topicName)
.setCredentialsProvider(FixedCredentialsProvider.create(creds))
.build();
Works as expected now!

Set timeOut for session/transaction to DefaultMessageListenerContainer

I want to receive chunk of messages from Queue within some timeLimit(Ex : 300 millisec after receiving the 1st message) using DefaultMessageListenerConatiner Of Spring (By overriding doReceiveAndExecute) as mentioned in the link.
I can group the messages of my batch size i.e 20 when the queue is having too many messages and I can receive less than 20 messages when there are very less messages in Queue.
Issue :
I see it takes too much time(sometimes 1 sec and sometime 2 secs and more) for sending the messages to Listener even when the queue is full.
When I try with DefaultMessageListenerConatiner as such to receive single messages concurrently, I see the messages are received in a delay of few milliseconds(like 1 millisec or max 30 to 60 millisec)
I didn't specify transactionTimeout or receiveTimeout and I didn't link any transactionManager as well.
Can Springers please help me to find where the timeOut can be specified or How can I redeuce the time delay?
BatchMessageListenerContainer :
package com.mypackage;
import javax.jms.Session;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import java.util.ArrayList;
import java.util.List;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.support.JmsUtils;
import org.springframework.transaction.TransactionStatus;
/**
* Listener Container that allows batch consumption of messages. Works only with transacted sessions
*/
public class BatchMessageListenerContainer extends DefaultMessageListenerContainer {
public static final int DEFAULT_BATCH_SIZE = 20;
private int batchSize = DEFAULT_BATCH_SIZE;
public BatchMessageListenerContainer() {
super();
setSessionTransacted(true);
}
/**
* #return The batch size on this container
*/
public int getBatchSize() {
return batchSize;
}
/**
* #param batchSize The batchSize of this container
*/
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
/**
* The doReceiveAndExecute() method has to be overriden to support multiple-message receives.
*/
#Override
protected boolean doReceiveAndExecute(Object invoker, Session session, MessageConsumer consumer,
TransactionStatus status) throws JMSException {
Connection conToClose = null;
MessageConsumer consumerToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
MessageConsumer consumerToUse = consumer;
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
List<Message> messages = new ArrayList<Message>();
int count = 0;
Message message = null;
// Attempt to receive messages with the consumer
do {
message = receiveMessage(consumerToUse);
if (message != null) {
messages.add(message);
}
}
// Exit loop if no message was received in the time out specified, or
// if the max batch size was met
while ((message != null) && (++count < batchSize));
if (messages.size() > 0) {
// Only if messages were collected, notify the listener to consume the same.
try {
doExecuteListener(sessionToUse, messages);
sessionToUse.commit();
}
catch (Throwable ex) {
handleListenerException(ex);
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
return true;
}
// No message was received for the period of the timeout, return false.
noMessageReceived(invoker, sessionToUse);
return false;
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}
protected void doExecuteListener(Session session, List<Message> messages) throws JMSException {
if (!isAcceptMessagesWhileStopping() && !isRunning()) {
if (logger.isWarnEnabled()) {
logger.warn("Rejecting received messages because of the listener container "
+ "having been stopped in the meantime: " + messages);
}
rollbackIfNecessary(session);
throw new JMSException("Rejecting received messages as listener container is stopping");
}
#SuppressWarnings("unchecked")
SessionAwareBatchMessageListener<Message> lsnr = (SessionAwareBatchMessageListener<Message>) getMessageListener();
try {
lsnr.onMessages(session, messages);
}
catch (JMSException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (RuntimeException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (Error err) {
rollbackOnExceptionIfNecessary(session, err);
throw err;
}
}
#Override
protected void checkMessageListener(Object messageListener) {
if (!(messageListener instanceof SessionAwareBatchMessageListener<?>)) {
throw new IllegalArgumentException("Message listener needs to be of type ["
+ SessionAwareBatchMessageListener.class.getName() + "]");
}
}
#Override
protected void validateConfiguration() {
if (batchSize <= 0) {
throw new IllegalArgumentException("Property batchSize must be a value greater than 0");
}
}
public void setSessionTransacted(boolean transacted) {
if (!transacted) {
throw new IllegalArgumentException("Batch Listener requires a transacted Session");
}
super.setSessionTransacted(transacted);
}
}
SessionAwareBatchMessageListener:
package com.mypackage;
import java.util.List;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
public interface SessionAwareBatchMessageListener<M extends Message> {
/**
* Perform a batch action with the provided list of {#code messages}.
*
* #param session JMS {#code Session} that received the messages
* #param messages List of messages
* #throws JMSException JMSException thrown if there is an error performing the operation.
*/
public void onMessages(Session session, List<M> messages) throws JMSException;
}
Bean in applicationContext.xml:
<bean id="myMessageListener" class="org.mypackage.MyMessageListener">
<bean id="jmsContainer" class="com.mypackage.BatchMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" ref="queue"/>
<property name="messageListener" ref="myMessageListener"/>
<property name ="concurrentConsumers" value ="10"/>
<property name ="maxConcurrentConsumers" value ="50"/>
</bean>
MyMessageListner :
package org.mypackage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.mypackage.service.MyService;
public class MyMessageListener implements SessionAwareBatchMessageListener<TextMessage> {
#Autowired
private MyService myService;
#Override
public void onMessage(Session session, List<TextMessage> messages) {
try {
for(TextMessage tm :messages) {
TextMessage textMessage = (TextMessage) message;
// parse the message and add to list
}
//process list of Objects to DB
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
i think that the time spent before sending the message to the consumer was caused by your while loop, because you're awaiting each time the list to be full but this one is only filled by the current thread since it is created inside the doReceiveAndExecute method!
// Exit loop if no message was received in the time out specified, or
// if the max batch size was met
while ((message != null) && (++count < batchSize));
maybe this can do it well :
...
List<Message> messages = Collections.synchronizedList(new ArrayList<Message>());
#Override
protected boolean doReceiveAndExecute(Object invoker, Session session, MessageConsumer consumer,
TransactionStatus status) throws JMSException {
Connection conToClose = null;
MessageConsumer consumerToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
MessageConsumer consumerToUse = consumer;
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
Message message = null;
// Attempt to receive messages with the consumer
do {
message = receiveMessage(consumerToUse);
if (message != null) {
messages.add(message);
}
}
if (messages.size() >= batchSize)) {
synchronized (messages) {
// Only if messages were collected, notify the listener to consume the same.
try {
doExecuteListener(sessionToUse, messages);
sessionToUse.commit();
// clear the list!!
messages.clear();
}
catch (Throwable ex) {
handleListenerException(ex);
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
}
return true;
}
// No message was received for the period of the timeout, return false.
noMessageReceived(invoker, sessionToUse);
return false;
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}

Shutdown connection to RabbitMQ

I have created a simple Console Application listening to messages on Rabbit MQ. Works fine. No problems there. But how do I close the connection. I've been googleing arround a lot, but I didn't find any clear answers. The closest I got to an answer is this SO question: What is the best way to safely end a java application with running RabbitMQ consumers, but the answer omits the most important part: The code!
Here is my code:
package com.company;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
interface ObjectRecievedListener {
void objectRecieved(Object obj);
}
public class RabbitMQReceiver extends RabbitMQBase
{
ArrayList<DefaultConsumer> consumers = new ArrayList<>();
private List<ObjectRecievedListener> listeners = new ArrayList<>();
private final Connection connection;
private final Channel channel;
public RabbitMQReceiver(RabbitMQProperties properties) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(properties.getHost());
factory.setPassword(properties.getPassword());
factory.setUsername(properties.getLogin());
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(properties.getInboundQueueName(), true, false, false, null);
channel.basicQos(1);
final Consumer consumer = new DefaultConsumer(channel) {
#Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
try {
doWork(message);
} finally {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
consumers.add((DefaultConsumer) consumer);
boolean autoAck = false;
channel.basicConsume(properties.getInboundQueueName(), autoAck, consumer);
}
public void addListener(ObjectRecievedListener listener) {
listeners.add(listener);
}
public void stop() {
try {
channel.close();
connection.close();
} catch (Exception e) {
}
}
private void doWork(String message) {
Object obj = getObjectFromXML(message);
for (ObjectRecievedListener l : listeners)
l.objectRecieved(obj);
stop();
}
}
But it doesn't stop just because I called stop()
So in short: How do I close the connection to Rabbit MQ?
The RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.
When you say "it doesn't stop" have you confirmed that the stop() method is actually called? Is your code hanging on one of the close() methods?
The channel instance will have a basicCancel method that you could call to cancel the current consumer before closing the channel and connection. To be honest, closing the channel will cancel your consumer so I doubt this is the root cause of the issue.
Try this:
let connection = null;
connection = await amqp.connect(`amqp://${config.username}:${config.password}#${config.host}:${config.port}`);
connection.close();

Establishing WebSocket connection with Java server and Javascript client

I'm trying to implement WebSockets with a Javascript-based client and a Java-based server. I think I've done all the correct steps, but for an unknown reason, I can't establish the connection with both.
When the server socket receives a connection, it handles to form a websocket-accept response, and it sends back to the client, but the connection in the client socket instantly close, weird that there's no handshake problem.
Does anyone have an idea what might be the problem?
Here's my server code implemented in java:
package server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import server.message.Message;
import server.message.SpeakMessage;
public class Server implements ConnectionListener {
private static final int PORT = 1509;
private MessageDispatcher dispatcher = new MessageDispatcher();
private List<ConnectionManager> clients = new ArrayList<>();
public void listen() {
try (ServerSocket server = new ServerSocket(PORT)) {
System.out.printf("Listening on port %d...%n", PORT);
while (true) {
System.out.println("Waiting for connection...");
Socket client = server.accept();
System.out.println("Incoming connection - Attempting to establish connection...");
ConnectionManager manager = new ConnectionManager(client, dispatcher, this);
manager.start();
}
} catch (IOException e) {
System.out.println("Unable to start server");
e.printStackTrace();
}
System.exit(0);
}
public void execute() {
try {
while (true) {
if (dispatcher.isEmpty()) {
Thread.sleep(100);
continue;
}
Message msg = dispatcher.read();
if (msg instanceof SpeakMessage)
broadcast(MessageEncoder.spoke(((SpeakMessage) msg).getText()));
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) {
final Server server = new Server();
new Thread(new Runnable() {
#Override
public void run() {
server.listen();
}
}).start();
server.execute();
}
public synchronized void broadcast(byte[] message) {
for (ConnectionManager client : clients) {
client.send(message);
}
}
#Override
public synchronized void clientConnected(ConnectionManager who) {
clients.add(who);
System.out.println("Connected client " + clients.size());
}
#Override
public synchronized void clientDisconnected(ConnectionManager who) {
clients.remove(who);
}
}
Heres subclass ConnectionManager of server:
package server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.Properties;
import server.message.HandshakeMessage;
import server.message.Message;
public class ConnectionManager {
private static final int CLIENT_VERSION = 1;
private Socket socket;
private MessageDecoder decoder = new MessageDecoder();
private MessageDispatcher dispatcher;
private ConnectionListener listener;
public ConnectionManager(Socket connection, MessageDispatcher dispatcher, ConnectionListener listener) {
socket = connection;
this.dispatcher = dispatcher;
this.listener = listener;
}
public void start() {
Thread t = new Thread(new ChannelReader());
t.setName("Client thread");
t.setDaemon(true);
t.start();
}
public void send(byte[] data) {
if (socket == null)
return;
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.write(data);
dos.flush();
} catch (IOException e) {
disconnect("Client closed the connection");
}
}
private class ChannelReader implements Runnable {
private boolean accepted = false;
private String ret = null;
#Override
public void run() {
try {
DataInputStream in = new DataInputStream(socket.getInputStream());
while (socket != null && socket.isConnected()) {
int len = in.readShort();
if (len < 0) {
disconnect("Invalid message length.");
}
String s;
readLine(in);
Properties props = new Properties();
while((s=readLine(in)) != null && !s.equals("")) {
String[] q = s.split(": ");
props.put(q[0], q[1]);
}
if(props.get("Upgrade").equals("websocket") && props.get("Sec-WebSocket-Version").equals("13")) { // check if is websocket 8
String key = (String) props.get("Sec-WebSocket-Key");
String r = key + "" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // magic key
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(r.getBytes());
byte[] sha1hash = md.digest();
String returnBase = base64(sha1hash);
ret = "HTTP/1.1 101 Switching Protocols\r\n";
ret+="Upgrade: websocket\r\n";
ret+="Connection: Upgrade\r\n";
ret+="Sec-WebSocket-Accept: "+returnBase;
} else {
disconnect("Client got wrong version of websocket");
}
Message msg = decoder.decode((String) props.get("Sec-WebSocket-Protocol"));
if (!accepted) {
doHandshake(msg);
} else if (dispatcher != null) {
dispatcher.dispatch(msg);
}
}
} catch (Exception e) {
disconnect(e.getMessage());
e.printStackTrace();
}
}
private void doHandshake(Message msg) {
if (!(msg instanceof HandshakeMessage)) {
disconnect("Missing handshake message");
return;
}
HandshakeMessage handshake = (HandshakeMessage) msg;
if (handshake.getVersion() != CLIENT_VERSION) {
disconnect("Client failed in handshake.");
return;
}
send(ret.getBytes());
accepted = true;
listener.clientConnected(ConnectionManager.this);
}
private String base64(byte[] input) throws ClassNotFoundException,
SecurityException, NoSuchMethodException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> c = Class.forName("sun.misc.BASE64Encoder");
java.lang.reflect.Method m = c.getMethod("encode", new Class<?>[]{byte[].class});
String s = (String) m.invoke(c.newInstance(), input);
return s;
}
private String readLine(InputStream in) {
try{
String line = "";
int pread;
int read = 0;
while(true) {
pread = read;
read = in.read();
if(read!=13&&read!=10)
line += (char) read;
if(pread==13&&read==10) break;
}
return line;
}catch(IOException ex){
}
return null;
}
}
public synchronized void disconnect(String message) {
System.err.println(message);
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
socket = null;
listener.clientDisconnected(ConnectionManager.this);
}
}
And the MessageDispatcher:
package server;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingDeque;
import server.message.Message;
public class MessageDispatcher {
Queue<Message> messageQueue = new LinkedBlockingDeque<>();
public void dispatch(Message message) {
messageQueue.offer(message);
}
public Message read() {
return messageQueue.poll();
}
public boolean isEmpty() {
return messageQueue.isEmpty();
}
}
And heres my client code implemented in javascript:
var canvas, // Canvas DOM element
ctx, // Canvas rendering context
socket; // Socket connection
function init() {
// Initialise the canvas
canvas = document.getElementById("gameCanvas");
ctx = canvas.getContext("2d");
// Maximise the canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Initialise socket connection
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:1509/", ["1", "YURI"]);
socket.onopen = onSocketConnected();
socket.onclose = onSocketDisconnect();
socket.onmessage = onSocketMessage();
socket.onerror = onSocketError();
} else {
alert("The browser does not support websocket.");
}
};
// Socket message
function onSocketMessage(message) {
console.log('Message: ' + message.data);
};
// Socket error
function onSocketError(error) {
console.log('Error: ' + error.data);
};
// Socket connected
function onSocketConnected() {
console.log("Connected to socket server");
};
// Socket disconnected
function onSocketDisconnect() {
console.log("Disconnected from socket server");
};
I think, it is because you are using the Socket Package on the Java Server Side and the WebSocket API on the Client Side. Your idea is really good but the wrong technology.
Keep the WebSocket on the Client Side (Javascript) becaue you don't have lots of other possibilities, but try JWebSocket on the Server side (Java). In Fact WebSocket is using TCP/IP but its own communication protocol over TCP/IP. The Java Socket Package is purely TCP/IP. Re-write your server with JWebSocket, all details about JWebSocket can be found at:
http://jwebsocket.org/.
I hope my answer will help you.
you must specify end of return packet with "\r\n\r\n"
ret = "HTTP/1.1 101 Switching Protocols\r\n";
ret+="Upgrade: websocket\r\n";
ret+="Connection: Upgrade\r\n";
ret+="Sec-WebSocket-Accept: "+returnBase + "\r\n\r\n";
and for create accept key i use
public class WSKeyGenerator {
private final static String MAGIC_KEY =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
public static String getKey(String strWebSocketKey) throws
NoSuchAlgorithmException {
strWebSocketKey += MAGIC_KEY;
MessageDigest shaMD = MessageDigest.getInstance("SHA-1");
shaMD.reset();
shaMD.update(strWebSocketKey.getBytes());
byte messageDigest[] = shaMD.digest();
BASE64Encoder b64 = new BASE64Encoder();
return b64.encode(messageDigest);
}
}
I recommend that use the http://websocket.org/echo.html to check the server's websocket functionality

PubSubHubBub in Java: can't subscribe

I'm trying to develop a simple Java PubSubHubBub application. I downloaded this sample code to test it, but when I try to subscribe I always get an error 409.
Following PuSH specifications, I created my own feed at this link: receivetemplate.eu01.aws.af.cm/feed/
This is the code in the Test class for the subscriber:
package sub;
import java.net.InetAddress;
import PubSubHubbub.Web;
import PubSubHubbub.Subscriber;
public class Test {
private static Web webserver;
private static Subscriber sbcbr;
private static String hostname = null;
private static Integer webserverPort = 8080;
private static void startServer(){
try {
webserver = new Web(webserverPort);
sbcbr = new Subscriber(webserver);
InetAddress addr = InetAddress.getByName("receivetemplate.eu01.aws.af.cm");
hostname = addr.getHostName();
System.out.println("http://" + hostname + "/");
hostname = "http://" + hostname + "/";
} catch (Exception e) {
e.printStackTrace();
System.out.println("WebServer can not start");
}
}
public static void main(String[] args) {
try {
String hub = "http://pubsubhubbub.appspot.com/subscribe";
String hub_topic = "http://receivetemplate.eu01.aws.af.cm/feed/";
startServer();
int statusCode = sbcbr.subscribe(hub, hub_topic, hostname, null, null);
if (statusCode == 204){
System.out.println("the status code of the subscription is 204: the request was verified and that the subscription is active");
} else if (statusCode == 202){
System.out.println("the status code of the subscription is 202: the subscription has yet to be verified (i.e., the hub is using asynchronous verification)");
} else{
System.out.println("the status code of the subscription is:" + statusCode);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
If I replace hub_topic with http://pubsubhubbub-subscriber.appspot.com/ and if I pass pubsubhubbub-subscriber.appspot.com to InetAddress.getByName(), the response I get is 204 and everything works.
Can you give me some informations on what I'm doing wrong? Is there any error in my feed?

Categories