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