diff --git a/index.js b/index.js index 04eb982..17132fa 100644 --- a/index.js +++ b/index.js @@ -1,99 +1,3 @@ -import Rcon from './rcon.js'; -import express from 'express'; +import { bootstrapServer } from './server' -import { POOL_SIZE, webOptions, rconOptions, enableLogging, LOG_LEVEL, logger } from './config.js'; - -const app = express(); - -// Middleware for logging HTTP requests -app.use((req, res, next) => { - logger( - 'HTTP', - 1, - `${req.method} request to ${req.path} from ${req.ip}` - ); - next(); -}); - -const authenticateMiddleware = (req, res, next) => { - const token = req.header('Authorization'); - - if (token === webOptions.secretToken) { - next(); - } else { - res.status(401).send('Unauthorized'); - logger( - 'HTTP', - 1, - `Unauthorized request from ${req.ip}` - ); - } -}; - -app.use(express.text({ defaultCharset: 'utf-8' })); - -// Create pool connection -const rconPool = []; -const poolSize = POOL_SIZE; -let currentRconIndex = 0; - -for (let i = 0; i < poolSize; i++) { - const rconInstance = new Rcon(rconOptions); - rconInstance.connect().then(() => { - logger( - 'RCON', - 0, - `Connected to RCON instance ${i}` - ); - }).catch((error) => { - logger( - 'RCON', - 0, - `Error connecting to RCON instance ${i}: ${error}` - ); - }); - rconPool.push(rconInstance); -} - -app.post('/rcon', authenticateMiddleware, async (req, res) => { - const command = req.body; - - // Get the next RCON instance from the pool - const rconInstance = rconPool[currentRconIndex]; - currentRconIndex = (currentRconIndex + 1) % rconPool.length; - - try { - const response = await rconInstance.execute(command); - res.status(200).send({ response }); - } catch (error) { - res.status(500).send(`Error: ${error.message}`); - } -}); - -const server = app.listen(webOptions.port_web, webOptions.ip_web, () => { - logger( - 'HTTP', - 0, - `HTTP server listening on ${webOptions.ip_web}:${webOptions.port_web}` - ); -}); - -// Add an event listener to close the RCON connections when the HTTP server is stopped -server.on('close', async () => { - for (let i = 0; i < rconPool.length; i++) { - try { - await rconPool[i].disconnect(); - logger( - 'RCON', - 0, - `RCON instance ${i} disconnected` - ); - } catch (error) { - logger( - 'RCON', - 0, - `Error disconnecting RCON instance ${i}: ${error}` - ); - } - } -}); \ No newline at end of file +bootstrapServer() diff --git a/rcon.js b/rcon/client.js similarity index 99% rename from rcon.js rename to rcon/client.js index 089d05c..d5c72b2 100644 --- a/rcon.js +++ b/rcon/client.js @@ -5,7 +5,7 @@ import { createWriteStream } from 'fs'; import { join } from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; -import { enableLogging, LOG_LEVEL, logger } from './config.js'; +import { enableLogging, LOG_LEVEL, logger } from '../config.js'; EventEmitter.defaultMaxListeners = 100; // Increase the default max listeners to prevent memory leaks diff --git a/rcon/index.js b/rcon/index.js new file mode 100644 index 0000000..b14b9b2 --- /dev/null +++ b/rcon/index.js @@ -0,0 +1 @@ +export default new PoolManager() \ No newline at end of file diff --git a/rcon/pool-manager.js b/rcon/pool-manager.js new file mode 100644 index 0000000..5dd0e21 --- /dev/null +++ b/rcon/pool-manager.js @@ -0,0 +1,66 @@ + +import { POOL_SIZE, rconOptions, logger } from '../config'; + +import Rcon from './client' + +export class PoolManager { + #poolMaxSize = POOL_SIZE; + #pool = []; + #connections = []; + #executionQueue = []; + + constructor() { + this.setupPool() + } + + setupPool() { + for (let i = 0; i < this.#poolMaxSize; i++) { + const rconInstance = new Rcon(rconOptions); + rconInstance.connect() + .then(() => { + logger( + 'RCON', + 0, + `Connected to RCON instance ${i}` + ); + }).catch((error) => { + logger( + 'RCON', + 0, + `Error connecting to RCON instance ${i}: ${error}` + ); + }); + this.#connections.push(rconInstance); + this.#pool.push(rconInstance); + } + } + + async execute(command) { + return new Promise((resolve, fail) => { + this.#executionQueue.push({ command, resolve, fail }); + this.#tick(); + }); + } + + #tick() { + const hasFreeConnections = this.#pool.length > 0; + const isHasTask = this.#executionQueue.length > 0; + + if (hasFreeConnections && isHasTask) { + const connection = this.#pool.shift(); + const task = this.#executionQueue.shift(); + + connection.execute(task.command) + .then(task.resolve) + .catch(task.fail) + .finally(() => { + this.#pool.push(connection); + this.#tick(); + }); + } + } + + disconnect() { + return Promise.all(this.#connections.map(c => c.disconnect())) + } +} \ No newline at end of file diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..614c28e --- /dev/null +++ b/server/index.js @@ -0,0 +1,73 @@ +import express from 'express'; + +import Rcon from '../rcon'; + +import { webOptions, logger } from '../config'; + +export const bootstrapServer = () => { + const app = express(); + + // Middleware for logging HTTP requests + app.use((req, res, next) => { + logger( + 'HTTP', + 1, + `${req.method} request to ${req.path} from ${req.ip}` + ); + next(); + }); + + const authenticateMiddleware = (req, res, next) => { + const token = req.header('Authorization'); + + if (token === webOptions.secretToken) { + next(); + } else { + res.status(401).send('Unauthorized'); + logger( + 'HTTP', + 1, + `Unauthorized request from ${req.ip}` + ); + } + }; + + app.use(express.text({ defaultCharset: 'utf-8' })); + + app.post('/rcon', authenticateMiddleware, async (req, res) => { + const command = req.body; + + try { + const response = await Rcon.execute(command); + res.status(200).send({ response }); + } catch (error) { + res.status(500).send(`Error: ${error.message}`); + } + }); + + const server = app.listen(webOptions.port_web, webOptions.ip_web, () => { + logger( + 'HTTP', + 0, + `HTTP server listening on ${webOptions.ip_web}:${webOptions.port_web}` + ); + }); + + // Add an event listener to close the RCON connections when the HTTP server is stopped + server.on('close', async () => { + try { + await Rcon.disconnect(); + logger( + 'RCON', + 0, + `RCON disconnected` + ); + } catch (error) { + logger( + 'RCON', + 0, + `Error disconnecting RCON instance: ${error}` + ); + } + }); +}