Add rate limit with Exponential Backoff
This commit is contained in:
@ -11,7 +11,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.11",
|
"@tailwindcss/cli": "^4.1.11",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.0"
|
"express": "^4.21.0",
|
||||||
|
"express-rate-limit": "^7.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.7",
|
"nodemon": "^3.1.7",
|
||||||
|
@ -49,7 +49,12 @@ document.getElementById('serverForm').addEventListener('submit', async (e) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/${edition}/${host}/${port}`);
|
const response = await fetch(`/${edition}/${host}/${port}`);
|
||||||
if (!response.ok) throw new Error('Request failed');
|
if (!response.ok) {
|
||||||
|
if (response.status === 429) {
|
||||||
|
throw new Error('RateLimit');
|
||||||
|
}
|
||||||
|
throw new Error('Request failed');
|
||||||
|
}
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
statusResult.classList.remove('hidden');
|
statusResult.classList.remove('hidden');
|
||||||
@ -91,10 +96,17 @@ document.getElementById('serverForm').addEventListener('submit', async (e) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
statusResult.classList.remove('hidden');
|
statusResult.classList.remove('hidden');
|
||||||
|
if (error.message === 'RateLimit') {
|
||||||
|
statusContent.innerHTML = `
|
||||||
|
<p><strong class="text-blue-400">Status:</strong> <span class="text-yellow-400">Rate Limited</span></p>
|
||||||
|
<p><strong class="text-blue-400">Message:</strong> You are sending requests too quickly. Please try again in a moment.</p>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
statusContent.innerHTML = `
|
statusContent.innerHTML = `
|
||||||
<p><strong class="text-blue-400">Status:</strong> <span class="text-red-400">Error</span></p>
|
<p><strong class="text-blue-400">Status:</strong> <span class="text-red-400">Error</span></p>
|
||||||
<p><strong class="text-blue-400">Error:</strong> An error occurred while checking the server status</p>
|
<p><strong class="text-blue-400">Error:</strong> An error occurred while checking the server status</p>
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Hide loading spinner and re-enable button
|
// Hide loading spinner and re-enable button
|
||||||
loadingSpinner.style.display = 'none';
|
loadingSpinner.style.display = 'none';
|
||||||
|
@ -1,12 +1,40 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3066;
|
const port = 3066;
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const execPromise = promisify(exec);
|
const execPromise = promisify(exec);
|
||||||
|
|
||||||
|
// Custom key generator to support Cloudflare's CF-Connecting-IP header
|
||||||
|
const getClientIp = (req) => {
|
||||||
|
return req.headers['cf-connecting-ip'] || req.ip;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rate limiter configuration: 1 request per second with exponential backoff
|
||||||
|
const limiter = rateLimit({
|
||||||
|
windowMs: 1000, // 1 second
|
||||||
|
max: 1, // 1 request per IP
|
||||||
|
standardHeaders: true, // Return rate limit info in headers
|
||||||
|
legacyHeaders: false, // Disable X-RateLimit headers
|
||||||
|
keyGenerator: getClientIp, // Use CF-Connecting-IP or fallback to req.ip
|
||||||
|
handler: (req, res) => {
|
||||||
|
// Calculate backoff time (exponential, starting at 2 seconds)
|
||||||
|
const retryAfter = Math.pow(2, Math.min(req.rateLimit.current - req.rateLimit.limit, 5));
|
||||||
|
res.setHeader('Retry-After', retryAfter);
|
||||||
|
res.status(429).json({
|
||||||
|
error: 'Too many requests, please try again later.',
|
||||||
|
retryAfter: retryAfter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply rate limiting to API routes only
|
||||||
|
app.use('/java', limiter);
|
||||||
|
app.use('/bedrock', limiter);
|
||||||
|
|
||||||
async function checkConnectionStatus(hostname, port) {
|
async function checkConnectionStatus(hostname, port) {
|
||||||
try {
|
try {
|
||||||
const command = `${process.env.STATUS_CHECK_PATH} -host ${hostname} -port ${port}`;
|
const command = `${process.env.STATUS_CHECK_PATH} -host ${hostname} -port ${port}`;
|
||||||
@ -55,7 +83,7 @@ app.get('/bedrock/:host/:port', async (req, res) => {
|
|||||||
const { host, port } = req.params;
|
const { host, port } = req.params;
|
||||||
try {
|
try {
|
||||||
const result = await checkGeyserStatus(host, port);
|
const result = await checkGeyserStatus(host, port);
|
||||||
console.log(result)
|
console.log(result);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ isOnline: false, error: `Server error: ${error.message}` });
|
res.status(500).json({ isOnline: false, error: `Server error: ${error.message}` });
|
||||||
|
Reference in New Issue
Block a user