-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from CS3219-AY2425S1/d4-make-producer-consumer…
…-queues Merge D4 code into main branch
- Loading branch information
Showing
21 changed files
with
712 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}; |
Oops, something went wrong.