diff --git a/greenvironment-server+frontend_dist.tar.gz b/greenvironment-server+frontend_dist.tar.gz new file mode 100644 index 0000000..c15567c Binary files /dev/null and b/greenvironment-server+frontend_dist.tar.gz differ diff --git a/package-lock.json b/package-lock.json index c7df7fa..165243e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -278,11 +278,18 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.2.tgz", "integrity": "sha512-Ind+4qMNfQ62llyB4IMs1D8znMEBsMKohZBPqfBUIXqLQ9bdtWIbNTBWwtdcBWJKnokMZGcmWOOKslatni5vtA==", - "dev": true, "requires": { "@types/node": "*" } }, + "@types/socket.io-redis": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@types/socket.io-redis/-/socket.io-redis-1.0.25.tgz", + "integrity": "sha512-a1b6eW/8CWsIVOhkwkGCNLgXy8G+Vo1rrpRjs9l+1/V/yZV3p0U6V002ZFJ26iNclloCcKJMklJGVkqPRBvDtA==", + "requires": { + "@types/socket.io": "*" + } + }, "@types/validator": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", @@ -1844,6 +1851,11 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -4984,6 +4996,11 @@ "remove-trailing-separator": "^1.0.1" } }, + "notepack.io": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.1.3.tgz", + "integrity": "sha512-AgSt+cP5XMooho1Ppn8NB3FFaVWefV+qZoZncYTUSch2GAEwlYLcIIbT5YVkMlFeNHnfwOvc4HDlbvrB5BRxXA==" + }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -5849,6 +5866,26 @@ "strip-indent": "^1.0.1" } }, + "redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "requires": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.6.0" + } + }, + "redis-commands": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", + "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==" + }, + "redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -6556,6 +6593,18 @@ } } }, + "socket.io-redis": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/socket.io-redis/-/socket.io-redis-5.2.0.tgz", + "integrity": "sha1-j+KtlEX8UIhvtwq8dZ1nQD1Ymd8=", + "requires": { + "debug": "~2.6.8", + "notepack.io": "~2.1.2", + "redis": "~2.8.0", + "socket.io-adapter": "~1.1.0", + "uid2": "0.0.3" + } + }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -7368,6 +7417,11 @@ "random-bytes": "~1.0.0" } }, + "uid2": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", + "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", diff --git a/package.json b/package.json index 080eec7..a8604a5 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "typescript": "^3.5.3" }, "dependencies": { + "@types/socket.io-redis": "^1.0.25", "compression": "^1.7.4", "connect-session-sequelize": "^6.0.0", "cookie-parser": "^1.4.4", @@ -72,6 +73,7 @@ "sequelize": "^5.19.6", "sequelize-typescript": "^1.0.0", "socket.io": "^2.2.0", + "socket.io-redis": "^5.2.0", "sqlite3": "^4.1.0", "winston": "^3.2.1", "winston-daily-rotate-file": "^4.2.1" diff --git a/src/app.ts b/src/app.ts index d8f174d..4aadbf7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,6 +14,7 @@ import * as httpStatus from "http-status"; import * as path from "path"; import {Sequelize} from "sequelize-typescript"; import * as socketIo from "socket.io"; +import * as socketIoRedis from "socket.io-redis"; import {resolver} from "./graphql/resolvers"; import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; @@ -26,9 +27,11 @@ class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; + public readonly id?: number; public readonly sequelize: Sequelize; - constructor() { + constructor(id?: number) { + this.id = id; this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); @@ -56,7 +59,7 @@ class App { logger.info(`Sequelize Table force: ${force}`); await this.sequelize.sync({force, logging: (msg) => logger.silly(msg)}); await routes.ioListeners(this.io); - + this.io.adapter(socketIoRedis()); this.io.use(sharedsession(appSession, {autoSave: true})); this.app.set("views", path.join(__dirname, "views")); @@ -69,6 +72,7 @@ class App { this.app.use(express.static(path.join(__dirname, "public"))); this.app.use(cookieParser()); this.app.use(appSession); + // enable cross origin requests if enabled in the config if (globals.config.server.cors) { this.app.use(cors()); } @@ -77,6 +81,7 @@ class App { next(); }); this.app.use(routes.router); + // listen for graphql requrest this.app.use("/graphql", graphqlHTTP((request, response) => { return { // @ts-ignore all @@ -86,14 +91,28 @@ class App { schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))), }; })); + // allow access to cluster information + this.app.use("/cluster-info", (req: Request, res: Response) => { + res.json({ + id: this.id, + }); + }); + // redirect all request to the angular file this.app.use((req: any, res: Response) => { if (globals.config.frontend.angularIndex) { - res.sendFile(path.join(__dirname, globals.config.frontend.angularIndex)); + const angularIndex = path.join(__dirname, globals.config.frontend.angularIndex); + if (fsx.existsSync(path.join(angularIndex))) { + res.sendFile(angularIndex); + } else { + res.status(httpStatus.NOT_FOUND); + res.render("errors/404.pug", {url: req.url}); + } } else { res.status(httpStatus.NOT_FOUND); res.render("errors/404.pug", {url: req.url}); } }); + // show an error page for internal errors this.app.use((err, req: Request, res: Response) => { res.status(httpStatus.INTERNAL_SERVER_ERROR); res.render("errors/500.pug"); diff --git a/src/index.ts b/src/index.ts index a70b3b0..e226261 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,26 @@ +// tslint:disable:no-console +import * as cluster from "cluster"; import App from "./app"; +const numCPUs = require("os").cpus().length; -/** - * async main function wrapper. - */ -(async () => { - const app = new App(); - await app.init(); - app.start(); -})(); +if (cluster.isMaster) { + console.log(`[CLUSTER] Master ${process.pid} is running`); + for (let i = 0; i < numCPUs; i++) { + cluster.fork(); + } + cluster.on("exit", (worker, code, signal) => { + console.log(`[CLUSTER] Worker ${worker.process.pid} died!`); + }); +} else { + /** + * async main function wrapper. + */ + (async () => { + const app = new App(process.pid); + await app.init(); + app.start(); + })(); + + console.log(`[CLUSTER] Worker ${process.pid} started`); +}