Removed the need for lambda handler, added status message to server, refactored

This commit is contained in:
Alicia Sykes 2023-09-03 15:30:11 +01:00
parent e23347a936
commit 63db1dbd85
3 changed files with 86 additions and 95 deletions

View file

@ -37,7 +37,6 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-simple-maps": "^3.0.0",
"@types/styled-components": "^5.1.26",
"aws-serverless-express": "^3.4.0",
"axios": "^1.4.0",
"cheerio": "^1.0.0-rc.12",
"chrome-aws-lambda": "^10.1.0",

156
server.js
View file

@ -1,5 +1,4 @@
const express = require('express');
const awsServerlessExpress = require('aws-serverless-express');
const fs = require('fs');
const path = require('path');
const cors = require('cors');
@ -8,87 +7,89 @@ require('dotenv').config();
const app = express();
const port = process.env.PORT || 3000; // The port to run the server on
const API_DIR = '/api'; // Name of the dir containing the lambda functions
const dirPath = path.join(__dirname, API_DIR); // Path to the lambda functions dir
const guiPath = path.join(__dirname, 'build');
const handlers = {}; // Will store list of API endpoints
process.env.WC_SERVER = 'true'; // Tells middleware to return in non-lambda mode
// Enable CORS
app.use(cors({
origin: process.env.API_CORS_ORIGIN || '*',
}));
// Execute the lambda function
const executeHandler = async (handler, req) => {
return new Promise((resolve, reject) => {
const callback = (err, response) => err ? reject(err) : resolve(response);
const promise = handler(req, {}, callback);
if (promise && typeof promise.then === 'function') {
promise.then(resolve).catch(reject);
}
});
};
// Array of all the lambda function file names
const fileNames = fs.readdirSync(dirPath, { withFileTypes: true })
// Read and register each API function as an Express routes
fs.readdirSync(dirPath, { withFileTypes: true })
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.js'))
.map(dirent => dirent.name);
.forEach(dirent => {
const routeName = dirent.name.split('.')[0];
const route = `${API_DIR}/${routeName}`;
const handler = require(path.join(dirPath, dirent.name));
handlers[route] = handler;
const handlers = {};
app.get(route, async (req, res) => {
try {
await handler(req, res);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
});
fileNames.forEach(file => {
const routeName = file.split('.')[0];
const route = `${API_DIR}/${routeName}`;
const handler = require(path.join(dirPath, file)).handler;
handlers[route] = handler;
// Create a single API endpoint to execute all lambda functions
app.get('/api', async (req, res) => {
const results = {};
const { url } = req.query;
const maxExecutionTime = process.env.API_TIMEOUT_LIMIT || 20000;
app.get(route, async (req, res) => {
try {
const { statusCode = 200, body = '' } = await executeHandler(handler, req);
res.status(statusCode).json(JSON.parse(body));
} catch (err) {
res.status(500).json({ error: err.message });
}
const executeHandler = async (handler, req, res) => {
return new Promise(async (resolve, reject) => {
try {
const mockRes = {
status: (statusCode) => mockRes,
json: (body) => resolve({ body }),
};
await handler({ ...req, query: { url } }, mockRes);
} catch (err) {
reject(err);
}
});
};
const timeout = (ms, jobName = null) => {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(
`Timed out after ${ms/1000} seconds${jobName ? `, when executing ${jobName}` : ''}`
));
}, ms);
});
};
const handlerPromises = Object.entries(handlers).map(async ([route, handler]) => {
const routeName = route.replace(`${API_DIR}/`, '');
try {
const result = await Promise.race([
executeHandler(handler, req, res),
timeout(maxExecutionTime, routeName)
]);
results[routeName] = result.body;
} catch (err) {
results[routeName] = { error: err.message };
}
});
await Promise.all(handlerPromises);
res.json(results);
});
});
const timeout = (ms, jobName = null) => {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Timed out after the ${ms/1000} second limit${jobName ? `, when executing the ${jobName} task` : ''}`));
}, ms);
});
}
app.get('/api', async (req, res) => {
const results = {};
const { url } = req.query;
const maxExecutionTime = process.env.API_TIMEOUT_LIMIT || 15000;
const handlerPromises = Object.entries(handlers).map(async ([route, handler]) => {
const routeName = route.replace(`${API_DIR}/`, '');
try {
const result = await Promise.race([
executeHandler(handler, { query: { url } }),
timeout(maxExecutionTime, routeName)
]);
results[routeName] = JSON.parse((result || {}).body);
} catch (err) {
results[routeName] = { error: err.message };
}
});
await Promise.all(handlerPromises);
res.json(results);
});
// Handle SPA routing
app.use(historyApiFallback({
rewrites: [
{ from: /^\/api\/.*$/, to: function(context) { return context.parsedUrl.path; } },
{ from: /^\/api\/.*$/, to: (context) => context.parsedUrl.path },
]
}));
@ -111,12 +112,25 @@ if (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') {
app.use(express.static(guiPath));
}
// Create serverless express server
const port = process.env.PORT || 3000;
const server = awsServerlessExpress.createServer(app).listen(port, () => {
console.log(`Server is running on port ${port}`);
// Print nice welcome message to user
const printMessage = () => {
console.log(
`\x1b[36m\n` +
' __ __ _ ___ _ _ \n' +
' \\ \\ / /__| |__ ___ / __| |_ ___ __| |__\n' +
' \\ \\/\\/ / -_) \'_ \\___| (__| \' \\/ -_) _| / /\n' +
' \\_/\\_/\\___|_.__/ \\___|_||_\\___\\__|_\\_\\\n' +
`\x1b[0m\n`,
`\x1b[1m\x1b[32m🚀 Web-Check is up and running at http://localhost:${port} \x1b[0m\n\n`,
`\x1b[2m\x1b[36m🛟 For documentation and support, visit the GitHub repo: ` +
`https://github.com/lissy93/web-check \n`,
`💖 Found Web-Check useful? Consider sponsoring us on GitHub ` +
`to help fund maintenance & development.\x1b[0m`
);
};
// Create server
app.listen(port, () => {
printMessage();
});
exports.handler = (event, context) => {
awsServerlessExpress.proxy(server, event, context);
};

View file

@ -5793,14 +5793,6 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
"@vendia/serverless-express@^3.4.0":
version "3.4.0"
resolved "https://registry.npmjs.org/@vendia/serverless-express/-/serverless-express-3.4.0.tgz"
integrity sha512-/UAAbi9qRjUtjRISt5MJ1sfhtrHb26hqQ0nvE5qhMLsAdR5H7ErBwPD8Q/v2OENKm0iWsGwErIZEg7ebUeFDjQ==
dependencies:
binary-case "^1.0.0"
type-is "^1.6.16"
"@vercel/nft@^0.22.0":
version "0.22.6"
resolved "https://registry.npmjs.org/@vercel/nft/-/nft-0.22.6.tgz"
@ -6612,15 +6604,6 @@ avvio@^8.2.0:
debug "^4.0.0"
fastq "^1.6.1"
aws-serverless-express@^3.4.0:
version "3.4.0"
resolved "https://registry.npmjs.org/aws-serverless-express/-/aws-serverless-express-3.4.0.tgz"
integrity sha512-YG9ZjAOI9OpwqDDWzkRc3kKJYJuR7gTMjLa3kAWopO17myoprxskCUyCEee+RKe34tcR4UNrVtgAwW5yDe74bw==
dependencies:
"@vendia/serverless-express" "^3.4.0"
binary-case "^1.0.0"
type-is "^1.6.16"
axe-core@^4.6.2:
version "4.7.2"
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz"
@ -6866,11 +6849,6 @@ big.js@^5.2.2:
resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
binary-case@^1.0.0:
version "1.1.4"
resolved "https://registry.npmjs.org/binary-case/-/binary-case-1.1.4.tgz"
integrity sha512-9Kq8m6NZTAgy05Ryuh7U3Qc4/ujLQU1AZ5vMw4cr3igTdi5itZC6kCNrRr2X8NzPiDn2oUIFTfa71DKMnue/Zg==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
@ -18272,7 +18250,7 @@ type-fest@^3.0.0:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz"
integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==
type-is@^1.6.16, type-is@~1.6.18:
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==