Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 2 additions & 98 deletions index.js
Original file line number Diff line number Diff line change
@@ -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}`
);
}
}
});
bootstrapServer()
2 changes: 1 addition & 1 deletion rcon.js → rcon/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions rcon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default new PoolManager()
66 changes: 66 additions & 0 deletions rcon/pool-manager.js
Original file line number Diff line number Diff line change
@@ -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()))
}
}
73 changes: 73 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -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}`
);
}
});
}