server
This commit is contained in:
parent
1b7cf44154
commit
f5e4c11b99
164
src/server/Server.js
Normal file
164
src/server/Server.js
Normal file
@ -0,0 +1,164 @@
|
||||
// Native
|
||||
const { EventEmitter } = require('node:events');
|
||||
// const { inspect } = require('node:util');
|
||||
const path = require('node:path');
|
||||
const http = require('node:http2');
|
||||
|
||||
// External
|
||||
const { LoggerClient } = require('@navy.gif/logger');
|
||||
// const { Collection } = require('@discordjs/collection');
|
||||
const express = require('express');
|
||||
const { default: helmet } = require('helmet');
|
||||
|
||||
// Local
|
||||
const { Util } = require('../util');
|
||||
const { Registry, UserDatabase } = require('./components');
|
||||
const { MariaDB, MongoDB } = require('./database');
|
||||
const Authenticator = require('./middleware/Authenticator');
|
||||
|
||||
/**
|
||||
* Meant to be deployed behind a proxy (e.g. nginx) instance that takes care of certs and load balancing, trusts the first proxy
|
||||
*
|
||||
* @class Server
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
class Server extends EventEmitter {
|
||||
|
||||
constructor (options = {}) {
|
||||
|
||||
super();
|
||||
|
||||
const { MARIA_HOST, MARIA_USER, MARIA_PORT, MARIA_PASS, MARIA_DB,
|
||||
MONGO_HOST, MONGO_USER, MONGO_PORT, MONGO_PASS, MONGO_DB,
|
||||
NODE_ENV, SECRET, DISCORD_SECRET } = process.env; // Secret used for cookies, discord secret is for discord oauth
|
||||
const { discord, http: httpOpts, databases } = options;
|
||||
|
||||
this._options = options;
|
||||
this._shardId = parseInt(process.env.SHARD_ID);
|
||||
this._ready = false;
|
||||
this._debug = options.debug || false;
|
||||
this._proto = NODE_ENV === 'development' ? 'http' : 'https';
|
||||
|
||||
if (!httpOpts?.port) return Util.fatal(new Error(`Missing http.port in server options`));
|
||||
this.port = httpOpts.port + this._shardId;
|
||||
this.domain = options.domain;
|
||||
|
||||
|
||||
this.server = null;
|
||||
this.app = express();
|
||||
|
||||
this.registry = new Registry(this, { path: path.join(__dirname, 'endpoints') });
|
||||
this.logger = new LoggerClient({ ...options.logger, name: this.constructor.name });
|
||||
|
||||
this.mariadb = new MariaDB(this, { options: databases.mariadb, MARIA_HOST, MARIA_USER, MARIA_PORT, MARIA_PASS, MARIA_DB });
|
||||
this.mongodb = new MongoDB(this, { options: databases.mongodb, MONGO_HOST, MONGO_USER, MONGO_PORT, MONGO_PASS, MONGO_DB });
|
||||
this.userDatabase = new UserDatabase(this.mongodb);
|
||||
this.authenticator = new Authenticator(this, this.app, this.userDatabase, {
|
||||
mongo: this.mongodb.client,
|
||||
secret: SECRET,
|
||||
discordID: discord.id,
|
||||
discordScope: discord.scope,
|
||||
discordSecret: DISCORD_SECRET,
|
||||
discordVersion: discord.version,
|
||||
callbackURL: `${this.baseURL}${discord.callbackURL}`,
|
||||
cookie: {
|
||||
secure: true
|
||||
}
|
||||
});
|
||||
|
||||
// Expecting every other env to be behind a proxy
|
||||
if (NODE_ENV !== 'development') this.app.set('trust proxy', 1);
|
||||
|
||||
this.app.use(helmet());
|
||||
this.app.use(express.json({ limit: '10mb' }));
|
||||
this.app.use(this.logRequest.bind(this)); // Logs every request
|
||||
this.app.use(this.logError.bind(this)); // Logs endpoints that error and sends a 500
|
||||
this.app.use(this.ready.bind(this)); // denies requests before the server is ready
|
||||
|
||||
process.on('message', this._handleMessage.bind(this));
|
||||
|
||||
}
|
||||
|
||||
async init () {
|
||||
|
||||
const start = Date.now();
|
||||
this.logger.status('Starting server');
|
||||
|
||||
this.logger.info('Initialising MariaDB');
|
||||
this.mariadb.init();
|
||||
|
||||
this.logger.info('Initialising MongoDB');
|
||||
await this.mongodb.init();
|
||||
|
||||
this.userDatabase.init();
|
||||
|
||||
this.logger.info('Loading endpoints');
|
||||
this.registry.loadEndpoints();
|
||||
this.logger.debug(this.registry.print);
|
||||
|
||||
this.logger.info('Creating http server');
|
||||
this.server = http.createServer(this._options.http, this.app).listen(this.port);
|
||||
this._ready = true;
|
||||
|
||||
this.logger.status(`Server created, took ${Date.now() - start} ms, listening on port ${this.port}`);
|
||||
process.send({ _ready: true });
|
||||
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
logError (err, req, res, next) {
|
||||
this.logger.error(`Unhandled error:\n${err.stack || err}`);
|
||||
res.status(500).send('An internal error was encountered');
|
||||
}
|
||||
|
||||
logRequest (req, res, next) {
|
||||
res.once('finish', () => {
|
||||
this.logger[res.statusCode === 401 ? 'unauthorised' : 'access'](`[${req.get('X-Forwarded-For') || req.socket.remoteAddress}] [STATUS: ${res.statusCode}] Request to ${req.route?.path || req.path}`);
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
ready (req, res, next) {
|
||||
if (!this._ready) return res.status(503).send('Server not ready');
|
||||
next();
|
||||
}
|
||||
|
||||
async shutdown () {
|
||||
this.logger.info(`Received shutdown command, initiating graceful shutdown`);
|
||||
process.send({ _shutdown: true });
|
||||
|
||||
this._ready = false; // stops any new connections
|
||||
|
||||
await this.mongodb.close();
|
||||
await this.mariadb.close();
|
||||
this.logger.status('DB shutdowns complete.');
|
||||
|
||||
this.logger.status('Shutdown complete. Goodbye');
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit();
|
||||
|
||||
}
|
||||
|
||||
_handleMessage (msg) {
|
||||
if (msg._shutdown) this.shutdown();
|
||||
}
|
||||
|
||||
createLogger (comp) {
|
||||
return new LoggerClient({ name: comp.constructor.name, ...this._options.logger });
|
||||
}
|
||||
|
||||
get baseURL () {
|
||||
return `${this._proto}://${this.domain}/`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
process.once('message', (msg) => {
|
||||
if (msg._start) {
|
||||
const options = msg._start;
|
||||
const server = new Server(options);
|
||||
server.init();
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Server;
|
Loading…
Reference in New Issue
Block a user