docker-redis - local refactors

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-10-03 11:59:42 -04:00
parent 05e5545466
commit 7e7e109cfe
2 changed files with 128 additions and 79 deletions

116
server.js
View File

@@ -1,27 +1,30 @@
const express = require("express");
const cors = require("cors"); const cors = require("cors");
const bodyParser = require("body-parser");
const path = require("path"); const path = require("path");
const http = require("http");
const Redis = require("ioredis");
const express = require("express");
const bodyParser = require("body-parser");
const compression = require("compression"); const compression = require("compression");
const cookieParser = require("cookie-parser"); const cookieParser = require("cookie-parser");
const http = require("http");
const { Server } = require("socket.io"); const { Server } = require("socket.io");
// const { createClient } = require("redis");
const Redis = require("ioredis");
const { createAdapter } = require("@socket.io/redis-adapter"); const { createAdapter } = require("@socket.io/redis-adapter");
const logger = require("./server/utils/logger"); const { instrument } = require("@socket.io/admin-ui");
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
const { instrument, RedisStore } = require("@socket.io/admin-ui");
const { isString, isEmpty } = require("lodash"); const { isString, isEmpty } = require("lodash");
const logger = require("./server/utils/logger");
const applyRedisHelpers = require("./server/utils/redisHelpers"); const applyRedisHelpers = require("./server/utils/redisHelpers");
const applyIOHelpers = require("./server/utils/ioHelpers"); const applyIOHelpers = require("./server/utils/ioHelpers");
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
// Load environment variables // Load environment variables
require("dotenv").config({ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
}); });
const CLUSTER_RETRY_BASE_DELAY = 100;
const CLUSTER_RETRY_MAX_DELAY = 5000;
const CLUSTER_RETRY_JITTER = 100;
/** /**
* CORS Origin for Socket.IO * CORS Origin for Socket.IO
* @type {string[][]} * @type {string[][]}
@@ -50,11 +53,11 @@ const SOCKETIO_CORS_ORIGIN = [
"https://old.imex.online", "https://old.imex.online",
"https://www.old.imex.online", "https://www.old.imex.online",
"https://wsadmin.imex.online", "https://wsadmin.imex.online",
"https://www.wsadmin.imex.online", "https://www.wsadmin.imex.online"
"http://localhost:3333",
"https://localhost:3333"
]; ];
const SOCKETIO_CORS_ORIGIN_DEV = ["http://localhost:3333", "https://localhost:3333"];
/** /**
* Middleware for Express app * Middleware for Express app
* @param app * @param app
@@ -103,29 +106,52 @@ const applyRoutes = (app) => {
}); });
}; };
/**
* Connect to Redis Cluster
* @returns {Promise<unknown>}
*/
const connectToRedisCluster = () => { const connectToRedisCluster = () => {
const redisCluster = new Redis.Cluster( if (isEmpty(process.env?.REDIS_URL) || !isString(process.env?.REDIS_URL)) {
process.env.REDIS_URL logger.log(`[${process.env.NODE_ENV}] No or Malformed REDIS_URL present.`, "ERROR", "redis", "api");
? JSON.parse(process.env.REDIS_URL) process.exit(1);
: [ }
{
host: "localhost", let redisServers;
port: 6379
} try {
], redisServers = JSON.parse(process.env.REDIS_URL);
{ } catch (error) {
clusterRetryStrategy: function (times) { logger.log(
const delay = Math.min(100 + times * 50, 2000); `[${process.env.NODE_ENV}] Failed to parse REDIS_URL: ${error.message}. Exiting...`,
logger.log( "ERROR",
`[${process.env.NODE_ENV}] Redis cluster not yet ready. Retrying in ${delay}ms`, "redis",
"ERROR", "api"
"redis", );
"api" process.exit(1);
); }
return delay;
} const clusterRetryStrategy = (times) => {
const delay =
Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER;
logger.log(
`[${process.env.NODE_ENV}] Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`,
"ERROR",
"redis",
"api"
);
return delay;
};
const redisCluster = new Redis.Cluster(redisServers, {
clusterRetryStrategy,
enableAutoPipelining: true,
enableReadyCheck: true,
redisOptions: {
// connectTimeout: 10000, // Timeout for connecting in ms
// idleTimeoutMillis: 30000, // Close idle connections after 30s
// maxRetriesPerRequest: 5 // Retry a maximum of 5 times per request
} }
); });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
redisCluster.on("ready", () => { redisCluster.on("ready", () => {
@@ -171,9 +197,11 @@ const applySocketIO = async (server, app) => {
const ioRedis = new Server(server, { const ioRedis = new Server(server, {
path: "/wss", path: "/wss",
adapter: createAdapter(pubClient, subClient), adapter: createAdapter(pubClient, subClient),
store: new RedisStore(pubClient, subClient),
cors: { cors: {
origin: SOCKETIO_CORS_ORIGIN, origin:
process.env?.NODE_ENV === "development"
? [...SOCKETIO_CORS_ORIGIN, ...SOCKETIO_CORS_ORIGIN_DEV]
: SOCKETIO_CORS_ORIGIN,
methods: ["GET", "POST"], methods: ["GET", "POST"],
credentials: true, credentials: true,
exposedHeaders: ["set-cookie"] exposedHeaders: ["set-cookie"]
@@ -188,6 +216,7 @@ const applySocketIO = async (server, app) => {
username: "admin", username: "admin",
password: process.env.REDIS_ADMIN_PASS password: process.env.REDIS_ADMIN_PASS
}, },
mode: process.env.REDIS_ADMIN_MODE || "development" mode: process.env.REDIS_ADMIN_MODE || "development"
}); });
} }
@@ -202,19 +231,22 @@ const applySocketIO = async (server, app) => {
} }
}); });
const api = {
pubClient,
io,
ioRedis,
redisCluster
};
app.use((req, res, next) => { app.use((req, res, next) => {
Object.assign(req, { Object.assign(req, api);
pubClient,
io,
ioRedis
});
next(); next();
}); });
Object.assign(module.exports, { io, pubClient, ioRedis }); Object.assign(module.exports, api);
return { pubClient, io, ioRedis }; return api;
}; };
/** /**

View File

@@ -1,8 +1,8 @@
const logger = require("./logger");
/** /**
* Apply Redis helper functions * Apply Redis helper functions
* @param pubClient * @param pubClient
* @param app * @param app
* @param logger
*/ */
const applyRedisHelpers = (pubClient, app, logger) => { const applyRedisHelpers = (pubClient, app, logger) => {
// Store session data in Redis // Store session data in Redis
@@ -60,7 +60,7 @@ const applyRedisHelpers = (pubClient, app, logger) => {
// Use Redis multi/pipeline to batch the commands // Use Redis multi/pipeline to batch the commands
const multi = pubClient.multi(); const multi = pubClient.multi();
keyValueArray.forEach(([key, value]) => { keyValueArray.forEach(([key, value]) => {
multi.hSet(`socket:${socketId}`, key, JSON.stringify(value)); multi.hset(`socket:${socketId}`, key, JSON.stringify(value));
}); });
await multi.exec(); // Execute all queued commands await multi.exec(); // Execute all queued commands
} catch (error) { } catch (error) {
@@ -143,70 +143,87 @@ const applyRedisHelpers = (pubClient, app, logger) => {
next(); next();
}); });
// // Demo to show how all the helper functions work // Demo to show how all the helper functions work
// const demoSessionData = async () => { // const demoSessionData = async () => {
// const socketId = "testSocketId"; // const socketId = "testSocketId";
// //
// // Store session data using setSessionData // // 1. Test setSessionData and getSessionData
// await exports.setSessionData(socketId, "field1", "Hello, Redis!"); // await setSessionData(socketId, "field1", "Hello, Redis!");
// // const field1Value = await getSessionData(socketId, "field1");
// // Retrieve session data using getSessionData
// const field1Value = await exports.getSessionData(socketId, "field1");
// console.log("Retrieved single field value:", field1Value); // console.log("Retrieved single field value:", field1Value);
// //
// // Store multiple session data using setMultipleSessionData // // 2. Test setMultipleSessionData and getMultipleSessionData
// await exports.setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" }); // await setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" });
// // const multipleFields = await getMultipleSessionData(socketId, ["field2", "field3"]);
// // Retrieve multiple session data using getMultipleSessionData
// const multipleFields = await exports.getMultipleSessionData(socketId, ["field2", "field3"]);
// console.log("Retrieved multiple field values:", multipleFields); // console.log("Retrieved multiple field values:", multipleFields);
// //
// // Store multiple session data using setMultipleFromArraySessionData // // 3. Test setMultipleFromArraySessionData
// await exports.setMultipleFromArraySessionData(socketId, [ // await setMultipleFromArraySessionData(socketId, [
// ["field4", "Fourth Value"], // ["field4", "Fourth Value"],
// ["field5", "Fifth Value"] // ["field5", "Fifth Value"]
// ]); // ]);
// //
// // Retrieve and log all fields // // Retrieve and log all fields
// const allFields = await exports.getMultipleSessionData(socketId, [ // const allFields = await getMultipleSessionData(socketId, ["field1", "field2", "field3", "field4", "field5"]);
// "field1",
// "field2",
// "field3",
// "field4",
// "field5"
// ]);
// console.log("Retrieved all field values:", allFields); // console.log("Retrieved all field values:", allFields);
// //
// // 4. Test list functions
// // Add item to the end of a Redis list // // Add item to the end of a Redis list
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() }); // await addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() });
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() }); // await addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() });
// //
// // Add item to the beginning of a Redis list // // Add item to the beginning of a Redis list
// await exports.addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() }); // await addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() });
// //
// // Retrieve the entire list (using lRange) // // Retrieve the entire list
// const logEvents = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1); // const logEventsData = await pubClient.lrange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List:", logEvents.map(JSON.parse)); // const logEvents = logEventsData.map((item) => JSON.parse(item));
// console.log("Log Events List:", logEvents);
// //
// // **Add the new code below to test clearList** // // 5. Test clearList
// // await clearList(socketId, "logEvents");
// // Clear the list using clearList
// await exports.clearList(socketId, "logEvents");
// console.log("Log Events List cleared."); // console.log("Log Events List cleared.");
// //
// // Retrieve the list after clearing to confirm it's empty // // Retrieve the list after clearing to confirm it's empty
// const logEventsAfterClear = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1); // const logEventsAfterClear = await pubClient.lrange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array // console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array
// //
// // Clear session data // // 6. Test clearSessionData
// await exports.clearSessionData(socketId); // await clearSessionData(socketId);
// console.log("Session data cleared."); // console.log("Session data cleared.");
// };
// //
// // 7. Test room functions
// const roomName = "testRoom";
// const user1 = { id: 1, name: "Alice" };
// const user2 = { id: 2, name: "Bob" };
//
// // Add users to room
// await addUserToRoom(roomName, user1);
// await addUserToRoom(roomName, user2);
//
// // Get users in room
// const usersInRoom = await getUsersInRoom(roomName);
// console.log(`Users in room ${roomName}:`, usersInRoom);
//
// // Remove a user from room
// await removeUserFromRoom(roomName, user1);
//
// // Get users in room after removal
// const usersInRoomAfterRemoval = await getUsersInRoom(roomName);
// console.log(`Users in room ${roomName} after removal:`, usersInRoomAfterRemoval);
//
// // Clean up: remove remaining users from room
// await removeUserFromRoom(roomName, user2);
//
// // Verify room is empty
// const usersInRoomAfterCleanup = await getUsersInRoom(roomName);
// console.log(`Users in room ${roomName} after cleanup:`, usersInRoomAfterCleanup); // Should be empty
// };
// if (process.env.NODE_ENV === "development") { // if (process.env.NODE_ENV === "development") {
// demoSessionData(); // demoSessionData();
// } // }
// "th1s1sr3d1s" (BCrypt)
return api; return api;
}; };
module.exports = applyRedisHelpers; module.exports = applyRedisHelpers;
// "th1s1sr3d1s" (BCrypt)