Skip to content

Commit

Permalink
Merge pull request #34 from CS3219-AY2425S1/d4-make-producer-consumer…
Browse files Browse the repository at this point in the history
…-queues

Merge D4 code into main branch
  • Loading branch information
yuxuanleong authored Oct 20, 2024
2 parents ba763ec + 4d84d23 commit d88dd8a
Show file tree
Hide file tree
Showing 21 changed files with 712 additions and 162 deletions.
11 changes: 11 additions & 0 deletions Backend/MatchingService/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:20

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3003
CMD ["npm", "start"]
20 changes: 12 additions & 8 deletions Backend/MatchingService/app.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
const express = require('express');
const cors = require("cors");
const dotenv = require("dotenv");
//const matchmakingRouter = require("./controllers/matchmaking");
//const { consumeQueue } = require('./rabbitmq/subscriber');
//const { setupRabbitMQ } = require('./rabbitmq/setup');
//const { publishToQueue } = require('./rabbitmq/publisher')
const matchmakingRouter = require("./controllers/matchmaking");
const { consumeQueue, consumeDLQ } = require('./rabbitmq/subscriber');
const { setupRabbitMQ } = require('./rabbitmq/setup');

dotenv.config();

const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.options("*", cors());

app.use('/api/match', matchmakingRouter);

// TODO: Start consuming RabbitMQ queues
/*

setupRabbitMQ().then(() => {

consumeQueue().catch(console.error);
publishToQueue("user_234", "easy", "python")
publishToQueue("user_100", "easy", "java")
consumeDLQ().catch(console.error);

// publishToQueue({userId: "user_1", difficulty: "easy", language: "java"})
// publishToQueue({userId: "user_2", difficulty: "easy", language: "python"})
// publishToQueue({userId: "user_3", difficulty: "easy", language: "java"})

})
*/


module.exports = app;
28 changes: 21 additions & 7 deletions Backend/MatchingService/controllers/matchmaking.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
// TODO: WRITE API FOR MATCHING USER, REMEMBER TO DEAL WITH CORS ALLOW ACCESS ORIGIN ERROR
// WRITE API FOR MATCHING USER, REMEMBER TO DEAL WITH CORS ALLOW ACCESS ORIGIN ERROR
// Cors settled in app.js


/*
const express = require('express');
const router = express.Router();
const { publishToQueue } = require('../rabbitmq/publisher');

// Route for frontend to send user matching info
router.post('/match', async (req, res) => {
const { userId, language, difficulty } = req.body;
router.post('/enterMatchmaking', async (req, res) => {
const { userId, difficulty, language } = req.body;

try {
// Publish user info to RabbitMQ
await publishToQueue(userId, language, difficulty);
await publishToQueue({userId: userId, difficulty: difficulty, language: language});
res.status(200).send('User info sent for matching.');
} catch (error) {
console.error('Error publishing user info:', error);
res.status(500).send('Error in matchmaking process.');
}
});

module.exports = router;
*/
// This is for the alternative where the player also listens to a queue after entering matchmaking
/*
router.post('/waitMatch', async (req, res) => {
try {
// Start consuming RabbitMQ queues
// await consumeQueue();
res.status(200).send('Waiting for match...');
} catch (error) {
console.error('Error consuming RabbitMQ queue:', error);
res.status(500).send('Error in matchmaking process.');
}
})
*/

module.exports = router;
23 changes: 22 additions & 1 deletion Backend/MatchingService/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Backend/MatchingService/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"amqplib": "^0.10.4",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.1"
"express": "^4.21.1",
"ws": "^8.18.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
}
}
71 changes: 49 additions & 22 deletions Backend/MatchingService/rabbitmq/publisher.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,63 @@
const amqp = require('amqplib');
// TODO: Write function to publish to rabbitMQ
const { matching_exchange_name } = require('./setup.js');

let channel = null; // Store a persistent channel connection

async function connectToRabbitMQ() {
if (!channel) {
try {
const connection = await amqp.connect(process.env.RABBITMQ_URL);
channel = await connection.createChannel();
console.log("RabbitMQ channel created");
} catch (error) {
console.error('Error creating RabbitMQ channel:', error);
}
}
return channel;
}

/*
async function publishToQueue(userId, difficulty, language) {
async function publishToQueue({userId, difficulty, language}) {
try {
const connection = await amqp.connect(process.env.RABBITMQ_URL);
const channel = await connection.createChannel();
const matching_exchange_name = 'matching_exchange';
const channel = await connectToRabbitMQ(); // Reuse persistent connection
const routingKey = `${difficulty}.${language}`;
const queueName = `${difficulty}.${language}`;

if (queueInfo) {
channel.publish(matching_exchange_name, routingKey, Buffer.from(JSON.stringify({ userId, language, difficulty })));
// Publish the message to the exchange
const messageSent = channel.publish(
matching_exchange_name,
routingKey,
Buffer.from(JSON.stringify({ userId, difficulty, language }))
);

console.log(`Published user: ${userId} with routing key: ${routingKey}`);
if (messageSent) {
console.log(`Message sent: ${userId} -> ${routingKey}`);
} else {
console.log(`Cannot publish message: Queue ${queueName} does not exist`);
console.error(`Message NOT sent: ${userId} -> ${routingKey}`);
}
await channel.close();
await connection.close();
} catch (error) {
console.error('Error publishing to RabbitMQ:', error);
}
}

module.exports = { publishToQueue };
*/





async function publishCancelRequest({ userId }) {
try {
const channel = await connectToRabbitMQ(); // Reuse persistent connection
const routingKey = 'cancel'; // Define a routing key for cancellation

// Publish the cancel message to the exchange
const messageSent = channel.publish(
matching_exchange_name,
routingKey,
Buffer.from(JSON.stringify({ userId }))
);

if (messageSent) {
console.log(`Cancel request sent: ${userId}`);
} else {
console.error(`Cancel request NOT sent: ${userId}`);
}
} catch (error) {
console.error('Error publishing cancel request to RabbitMQ:', error);
}
}

module.exports = { publishToQueue, publishCancelRequest };
95 changes: 44 additions & 51 deletions Backend/MatchingService/rabbitmq/setup.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,71 @@
const amqp = require("amqplib");

const matching_exchange_name = "matching_exchange";
const dead_letter_exchange_name = "dead_letter_exchange";
const dead_letter_queue_name = "dead_letter_queue";
const cancel_queue_name = "cancel_queue";
const queueNames = [
'easy.python',
'easy.java',
'easy.cplusplus',
'medium.python',
'medium.java',
'medium.cplusplus',
'hard.python',
'hard.java',
'hard.cplusplus',
];

async function setupRabbitMQ() {
try {
const connection = await amqp.connect(process.env.RABBITMQ_URL)
.catch((error) => {
console.error("Error connecting to RabbitMQ:", error);
return null;
});
const connection = await amqp.connect(process.env.RABBITMQ_URL);

if (!connection) {
return;
}

const channel = await connection.createChannel();

// Declare matching exchange to be bind to queues
const matching_exchange_name = "matching_exchange";
await channel.assertExchange(matching_exchange_name, "topic", { durable: false });
// Declare the matching exchange (topic)
await channel.assertExchange(matching_exchange_name, "topic", { durable: true });

// Declare dead letter exchange
const dead_letter_exchange_name = "dead_letter_exchange";
await channel.assertExchange(dead_letter_exchange_name, "fanout", { durable: false });
// Declare the dead-letter exchange (fanout)
await channel.assertExchange(dead_letter_exchange_name, "fanout", { durable: true });

const queueNames = [
'easy.python',
'easy.java',
'easy.cplusplus',
'medium.python',
'medium.java',
'medium.cplusplus',
'hard.python',
'hard.java',
'hard.cplusplus'
]
// Declare and bind all main queues with TTL and DLQ bindings
for (let queueName of queueNames) {
await channel.deleteQueue(queueName); // Ensure we start fresh for each setup

// Create and bind queues to exchange with the routing keys
for (let name of queueNames) {
/*
try {
await channel.deleteQueue(name);
} catch (err) {
console.log(`Queue ${name} does not exist or could not be deleted: ${err.message}`);
}
*/
await channel.assertQueue(name,
{ durable: false, // durable=false ensures queue will survive broker restarts
arguments: {
'x-dead-letter-exchange': dead_letter_exchange_name // set dead letter exchange
}

});
await channel.assertQueue(queueName, {
durable: true,
arguments: {
'x-message-ttl': 10000, // 60 seconds TTL
'x-dead-letter-exchange': dead_letter_exchange_name // Bind to dead-letter exchange
}
});

await channel.bindQueue(name, matching_exchange_name, name); // e.g. messages with routing key easy.python goes to easy.python queue
await channel.bindQueue(queueName, matching_exchange_name, queueName); // Bind to exchange
}

// Create and bind queue to exchange (if we want only 1 queue)
// await channel.assertQueue(name, { durable: false })
// await channel.bindQueue(name, matching_exchange_name, '#') // all messages go to this queue because of a wildcard pattern
// Delete DLQ before asserting it
await channel.deleteQueue(dead_letter_queue_name);

// Create and bind dead letter queue
// const dead_letter_queue_name = "dead_letter_queue";
// await channel.assertQueue(deadLetterQueueName, { durable: false });
// await channel.bindQueue(deadLetterQueueName, deadLetterExchangeName, ''); // Bind all messages to this queue
// Declare the dead-letter queue and bind it to the dead-letter exchange
await channel.assertQueue(dead_letter_queue_name, { durable: true });
await channel.bindQueue(dead_letter_queue_name, dead_letter_exchange_name, ''); // Bind with no routing key

// Declare and bind the cancel queue
await channel.deleteQueue(cancel_queue_name); // Delete any existing cancel queue
await channel.assertQueue(cancel_queue_name, { durable: true }); // Declare the cancel queue
await channel.bindQueue(cancel_queue_name, matching_exchange_name, 'cancel'); // Bind with the "cancel" routing key

console.log("RabbitMQ setup complete with queues and bindings.")
console.log("RabbitMQ setup complete with queues, DLQ, and bindings.");

await channel.close();
await connection.close();
} catch (error) {
console.log('Error setting up RabbitMQ:', error);
console.error("Error setting up RabbitMQ:", error);
}
}

module.exports = { setupRabbitMQ };

setupRabbitMQ()
module.exports = { setupRabbitMQ, matching_exchange_name, queueNames, dead_letter_queue_name , cancel_queue_name};
Loading

0 comments on commit d88dd8a

Please sign in to comment.