This is my first time testing RabbitMQ with node.js, and I utilized amqplib.
First, I run the
node ./messages/consumer.js
and out put as follow -:
Connected to RabbitMQ
Channel created
Waiting for messages...
Second, I run the
node ./messages/producer.js
and out put as follow -:
Connected to RabbitMQ
Channel created
Message sent: Hello, world!
Connection to RabbitMQ closed
From the RabbitMQ management console, I observed the presence of test_exchange, test_queue, and test_key, but there was no information regarding any messages. And the consumer terminal did not log any indication of receiving a message. It still displays the message "Waiting for message.". Could you kindly inform me of where I may have overlooked this information?
//config.js
module.exports = {
rabbitmq: {
host: 'localhost',
port: 5672,
username: 'guest',
password: 'guest',
vhost: '/',
exchange: 'test_exchange',
queue: 'test_queue',
routingKey: 'test_key'
}
}
//rabbitmq.js
const amqp = require("amqplib");
const config = require("../config/config");
class RabbitMQ {
constructor() {
this.connection = null;
this.channel = null;
}
async connect() {
try {
const { host, port, username, password, vhost } = config.rabbitmq;
this.connection = await amqp.connect(
`amqp://${username}:${password}#${host}:${port}/${vhost}`
);
console.log("Connected to RabbitMQ");
return this.connection;
} catch (error) {
console.error("Error connecting to RabbitMQ", error);
}
}
async createChannel() {
try {
if (!this.connection) {
await this.connect();
}
this.channel = await this.connection.createChannel();
console.log("Channel created");
return this.channel;
} catch (error) {
console.error("Error creating channel", error);
}
}
async close() {
try {
await this.connection.close();
console.log("Connection to RabbitMQ closed");
} catch (error) {
console.error("Error closing connection to RabbitMQ", error);
}
}
}
module.exports = new RabbitMQ();
//producer.js
const rabbitmq = require('../lib/rabbitmq');
const config = require('../config/config');
async function produceMessage(message) {
try {
const channel = await rabbitmq.createChannel();
const exchange = config.rabbitmq.exchange;
const queue = config.rabbitmq.queue;
const key = config.rabbitmq.routingKey;
await channel.assertExchange(exchange, 'direct', { durable: true });
await channel.assertQueue(queue, { durable: true });
await channel.bindQueue(queue, exchange, key);
const messageBuffer = Buffer.from(message);
await channel.publish(exchange, key, messageBuffer);
console.log(`Message sent: ${message}`);
await rabbitmq.close();
} catch (error) {
console.error('Error producing message', error);
}
}
produceMessage('Hello, world!');
//consumer.js
const rabbitmq = require('../lib/rabbitmq');
const config = require('../config/config');
async function consumeMessage() {
try {
const channel = await rabbitmq.createChannel();
const exchange = config.rabbitmq.exchange;
const queue = config.rabbitmq.queue;
const key = config.rabbitmq.routingKey;
await channel.assertExchange(exchange, 'direct', { durable: true });
await channel.assertQueue(queue, { durable: true });
await channel.bindQueue(queue, exchange, key);
channel.consume(queue, (msg) => {
console.log(`Message received: ${msg.content.toString()}`);
channel.ack(msg);
}, { noAck: false });
console.log('Waiting for messages...');
} catch (error) {
console.error('Error consuming message', error);
}
}
consumeMessage();
Problem:
Your message is failing to send because you are closing the connection immediately after executing the publish command. You can try this by commenting the line await rabbitmq.close(); in producer.js.
Solution:
If you want to close the connection after sending the message. You can create confirm channel instead of a normal channel, which will allow you to receive send acknowledgment.
1. Channel Creation
Change the channel creation line in rabbitmq.js file:
this.channel = await this.connection.createConfirmChannel();
2. Producer:
In the producer.js, call waitForConfirms function before closing the connection:
await channel.waitForConfirms();
await rabbitmq.close();
Related
I'm using the following Nodejs script to publish messages in my RabbitMq queue:
const amqp = require("amqplib");
[...]
const publishMsgToQueue = async (job, res) => {
const queue = job.queueName;
const msg = { url: job.msg };
try {
const connection = await amqp.connect(Rabbitmq.host);
const channel = await connection.createChannel();
await channel.assertQueue(queue, { durable: true });
await channel.sendToQueue(queue, Buffer.from(JSON.stringify(msg)));
await channel.close();
await connection.close();
} catch (error) {
logger.error(
"[AMQP] Error publishing to RabbitMQ",
job.msg,
"queue name:",
job.queueName
);
}
};
This function is called in a for loop to publish an array of messages.
When all the messages are published, I have many many connections and channels open... while it should be closed thanks to channel.close and connection.close ... but it's not...
I check a lot on other posts, but I didn't succeed so far...
Where am I wrong ?
I am trying to create a bot in Slack called "Chochon", the problem is that when I receive the event "app_mention", chochon responds more than once several times in a row, instead of sending 1 message and stopping until they mention it again.
This is my code, chochon function:
socketModeClient.on('app_mention', async ({ event }) => {
try {
console.log(event);
let userBox = await Chochon.users.info({ user: event.user });
let userProfile = userBox.user.profile;
console.log(cli.green(`Event received : [ ${event.type} ] from [ ${userProfile.display_name} ]`));
// Respond to the event
Chochon.chat.postMessage({
channel: event.channel,
text: `Hello <#${event.user}>, I'm Chochon!, I'm a bot that can help you to manage your team.`
});
} catch (error) {
console.error(error);
}
});
The slack client:
Full code:
// Dependencies :
const dotenv = require('dotenv').config();
const path = require('path');
const cli = require('cli-color');
// Web client [CLI]
const { WebClient } = require('#slack/web-api');
const Chochon = new WebClient(process.env.SLACK_BOT_TOKEN.trim());
// Socket IO
const { SocketModeClient } = require('#slack/socket-mode');
const appToken = process.env.SLACK_APP_TOKEN;
const socketModeClient = new SocketModeClient({ appToken });
socketModeClient.start();
// Internal functions
//const eventManager = require(path.resolve(__dirname, './utils/events/manager'));
socketModeClient.on('app_mention', async ({ event }) => {
try {
console.log(event);
let userBox = await Chochon.users.info({ user: event.user });
let userProfile = userBox.user.profile;
console.log(cli.green(`Event received : [ ${event.type} ] from [ ${userProfile.display_name} ]`));
// Respond to the event
Chochon.chat.postMessage({
channel: event.channel,
text: `Hello <#${event.user}>, I'm Chochon!, I'm a bot that can help you to manage your team.`
});
} catch (error) {
console.error(error);
}
});
socketModeClient.on('slash_commands', async ({ body, ack }) => {
if (body.command === "/ping") {
console.log(cli.green(`Event received : [ ${body.command} ]`));
await ack({"text": "I got it, pong!"});
}
});
I'm currently using RabbitMQ as a message broker. Recently, I see many error HeartBeat Timeout in my error log.
Also in RabbitMQ log, I see this log:
I don't know why there is too many connection from vary ranges of port. I use default setup without any further configuration.
Here is my code used to publish and consume:
import { connect } from 'amqplib/callback_api';
import hanlder from '../calculator/middleware';
import { logger } from '../config/logger';
async function consumeRabbitMQServer(serverURL, exchange, queue) {
connect('amqp://localhost', async (error0, connection) => {
if (error0) throw error0;
const channel = connection.createChannel((error1) => {
if (error1) throw error1;
});
channel.assertExchange(exchange, 'direct', {
durable: true
});
channel.assertQueue(
queue,
{
durable: true
},
(error2) => {
if (error2) throw error2;
logger.info(`Connect to ${serverURL} using queue ${queue}`);
}
);
channel.prefetch(1);
channel.bindQueue(queue, exchange, 'info');
channel.noAck = true;
channel.consume(queue, (msg) => {
hanlder(JSON.parse(msg.content.toString()))
.then(() => {
channel.ack(msg);
})
.catch((err) => {
channel.reject(msg);
});
});
});
}
export default consumeRabbitMQServer;
Code used to publish message:
import createConnection from './connection';
import { logger } from '../config/logger';
async function publishToRabbitMQServer(serverURL, exchange, queue) {
const connection = createConnection(serverURL);
const c = await connection.then(async (conn) => {
const channel = await conn.createChannel((error1) => {
if (error1) throw error1;
});
channel.assertExchange(exchange, 'direct', {
durable: true
});
channel.assertQueue(
queue,
{
durable: true
},
(error2) => {
if (error2) throw error2;
logger.info(`Publish to ${serverURL} using queue ${queue}`);
}
);
channel.bindQueue(queue, exchange, 'info');
return channel;
});
return c;
}
export default publishToRabbitMQServer;
Whenever I start my server, I run this piece of code to create a client consume to RabbitMQ:
const { RABBITMQ_SERVER } = process.env;
consumeRabbitMQServer(RABBITMQ_SERVER, 'abc', 'abc');
And this piece of code is used when ever a message in need published to RabbitMQ
const payloads = call.request.payloads;
const { RABBITMQ_SERVER } = process.env;
const channel = await publishToRabbitMQServer(RABBITMQ_SERVER, 'abc', 'abc');
for (let i = 0; i < payloads.length; i++) {
channel.publish('abc', 'info', Buffer.from(JSON.stringify(payloads[i])));
}
I'm reusing code from RabbitMQ document, and it seem that this problem happen whenever there are too many user publish message. Thanks for helping.
Update: I think the root cause is when I need to publish a message, I create a new connection. I'm working to improve it, any help is appreciate. Many thanks.
I have a service in FeathersJS that initiates a connection to RabbitMQ, the issue is how to await for a channel to be ready before receiving requests:
class Service {
constructor({ amqpConnection, queueName }) {
this.amqpConnection = amqpConnection;
this.queueName = queueName;
this.replyQueueName = queueName + "Reply"
}
async create(data, params) {
new Promise(resolve => {
if (!this.channel) await this.createChannel();
channel.responseEmitter.once(correlationId, resolve);
channel.sendToQueue(this.queueName, Buffer.from(data), {
correlationId: asyncLocalStorage.getStore(),
replyTo: this.replyQueueName,
});
});
}
async createChannel() {
let connection = this.amqpConnection();
let channel = await connection.createChannel();
await channel.assertQueue(this.queueName, {
durable: false,
});
this.channel = channel;
channel.responseEmitter = new EventEmitter();
channel.responseEmitter.setMaxListeners(0);
channel.consume(
this.replyQueueName,
(msg) => {
channel.responseEmitter.emit(
msg.properties.correlationId,
msg.content.toString("utf8")
);
},
{ noAck: true }
);
}
....
}
Waiting for the channel to be created during a request seems like a waste. How should this be done "correctly"?
Feathers services can implement a setup method which will be called when the server is started (or you call app.setup() yourself):
class Service {
async setup () {
await this.createChannel();
}
}
I am trying to migrate our subscribers from OneSignal. I exported the endpoint, the keys (auth and P256DH) and I configured the VAPID keys of my OS account on my server.
When I try to send a notification from OS then remove the service worker of OS and use my own, it's sending the same notification that I previously sent through OS (quite odd), and when I programmatically remove the service worker of OS (through the console) and register my own service worker, it's responding with 410 error from chrome ("NotRegistered") and 401 from Firefox ("Request did not validate missing authorization header").
app.js file:
let isSubscribed = false;
let swRegistration = null;
let applicationKey = "PUBLIC_VAPID_KEY_HERE";
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
navigator.serviceWorker.register('sw.js')
.then(function (swReg) {
console.log('service worker registered');
swRegistration = swReg;
swRegistration.pushManager.getSubscription()
.then(function (subscription) {
isSubscribed = !(subscription === null);
if (isSubscribed) {
console.log('User is subscribed');
} else {
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(applicationKey)
})
.then(function (subscription) {
console.log(subscription);
console.log('User is subscribed');
saveSubscription(subscription);
isSubscribed = true;
})
.catch(function (err) {
console.log('Failed to subscribe user: ', err);
})
}
})
})
.catch(function (error) {
console.error('Service Worker Error', error);
});
} else {
console.warn('Push messaging is not supported');
}
function saveSubscription(subscription) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", "/subscribe");
xmlHttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState != 4) return;
if (xmlHttp.status != 200 && xmlHttp.status != 304) {
console.log('HTTP error ' + xmlHttp.status, null);
} else {
console.log("User subscribed to server");
}
};
xmlHttp.send(JSON.stringify(subscription));
}
sw.js file:
let notificationUrl = '';
self.addEventListener('push', function (event) {
console.log('Push received: ', event);
let _data = event.data ? JSON.parse(event.data.text()) : {};
notificationUrl = _data.url;
event.waitUntil(
self.registration.showNotification(_data.title, {
body: _data.message,
icon: _data.icon,
tag: _data.tag
})
);
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
clients.matchAll({
type: "window"
})
.then(function (clientList) {
if (clients.openWindow) {
return clients.openWindow(notificationUrl);
}
})
);
});
push.js file which pushes notifications:
const express = require('express');
const router = express.Router();
const q = require('q');
const webPush = require('web-push');
const keys = require('./../config/keys');
const mysql = require("mysql");
const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: 'root',
database: 'webpush',
multipleStatements: true,
dateStrings: true
});
router.post('/push', (req, res) => {
const payload = {
title: req.body.title,
message: req.body.message,
url: req.body.url,
ttl: req.body.ttl,
icon: req.body.icon,
image: req.body.image,
badge: req.body.badge,
tag: req.body.tag
};
pool.query('SELECT * FROM subscriber', (err, subscriptions) => {
if (err) {
return console.log(err);
console.error(`Error occurred while getting subscriptions`);
return res.status(500).json({
error: 'Technical error occurred'
});
}
if (!subscriptions.length) {
console.error(`No subscribers found`);
return res.status(500).json({
error: 'Subscribers not found'
});
}
let parallelSubscriptionCalls = subscriptions.map(subscription => {
return new Promise((resolve, reject) => {
const pushSubscription = {
endpoint: subscription.endpoint,
keys: {
p256dh: subscription.p256dh,
auth: subscription.auth
}
};
const pushPayload = JSON.stringify(payload);
const pushOptions = {
vapidDetails: {
subject: 'https://www.mydomainhere.com',
privateKey: keys.privateKey,
publicKey: keys.publicKey
},
TTL: payload.ttl,
headers: {}
};
webPush.sendNotification(pushSubscription, pushPayload, pushOptions)
.then((value) => {
resolve({
status: true,
endpoint: subscription.endpoint,
data: value
});
}).catch((err) => {
reject({
status: false,
endpoint: subscription.endpoint,
data: err
});
});
});
});
q.allSettled(parallelSubscriptionCalls).then((pushResults) => {
console.info(pushResults);
});
res.json({
data: 'Push triggered'
});
})
});
module.exports = router;
subscribe.js file which does the subscription:
const express = require('express');
const router = express.Router();
const mysql = require("mysql");
const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: 'root',
database: 'webpush',
multipleStatements: true,
dateStrings: true
});
router.post('/subscribe', (req, res) => {
const endpoint = req.body.endpoint;
const auth = req.body.keys.auth;
const p256dh = req.body.keys.p256dh;
const subscriptionSet = { endpoint, auth, p256dh }
pool.getConnection((err, connection) => {
if (err) {
console.error(`Error occurred while saving subscription. Err: ${err}`);
return res.status(500).json({
error: 'Technical error occurred'
});
};
connection.query('INSERT INTO subscriber SET ?', subscriptionSet, (err, subscription) => {
if (err) {
console.error(`Error occurred while saving subscription. Err: ${err}`);
return res.status(500).json({
error: 'Technical error occurred'
});
}
res.json({
data: 'Subscription saved.'
})
})
});
});
module.exports = router;
Im trying to do the same, how and where did you export the subscribers auth and P256DH from onesignal? because in the csv export, onesignal provides the push_token (endpoint) and not the auth and P256DH. Where would i get the all the users auth and P256DH?
From my understanding you cannot re register the service worker unless the subscriber comes back and visits your domain. However, the service worker updates every day, so what you can do is edit the existing One signal service worker files - delete their content and add your own code or import scripts. Make sure not to change file location or rename the one signal service worker file, it needs to be the same filename. This will count as an 'update' and browsers should automatically replace the contents without any action from users. If there's two OneSignal service worker files then change both.