diff options
Diffstat (limited to 'src/server/index.ts')
-rw-r--r-- | src/server/index.ts | 108 |
1 files changed, 83 insertions, 25 deletions
diff --git a/src/server/index.ts b/src/server/index.ts index 2cc35ccec..5e411aa3a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -6,8 +6,8 @@ import { Database } from './database'; const serverPort = 4321; import { DashUploadUtils } from './DashUploadUtils'; import RouteSubscriber from './RouteSubscriber'; -import initializeServer from './Initialization'; -import RouteManager, { Method, _success, _permission_denied, _error, _invalid, OnUnauthenticated } from './RouteManager'; +import initializeServer from './server_Initialization'; +import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager'; import * as qs from 'query-string'; import UtilManager from './ApiManagers/UtilManager'; import { SearchManager, SolrManager } from './ApiManagers/SearchManager'; @@ -18,18 +18,20 @@ import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; -import { log_execution } from "./ActionUtilities"; +import { log_execution, Email } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; -import { yellow, red } from "colors"; -import { disconnect } from "../server/Initialization"; -import { ProcessFactory, Logger } from "./ProcessFactory"; +import { Logger } from "./ProcessFactory"; +import { yellow } from "colors"; +import { Session } from "./Session/session"; +import { isMaster } from "cluster"; +import { execSync } from "child_process"; +import { Utils } from "../Utils"; +import { MessageStore } from "./Message"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); -export const ExitHandlers = new Array<() => void>(); - /** * These are the functions run before the server starts * listening. Anything that must be complete @@ -79,29 +81,29 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: addSupervisedRoute({ method: Method.GET, subscription: "/", - onValidation: ({ res }) => res.redirect("/home") + secureHandler: ({ res }) => res.redirect("/home") }); addSupervisedRoute({ method: Method.GET, subscription: "/serverHeartbeat", - onValidation: ({ res }) => res.send(true) + secureHandler: ({ res }) => res.send(true) }); addSupervisedRoute({ method: Method.GET, - subscription: "/shutdown", - onValidation: async ({ res }) => { - WebSocket.disconnect(); - await disconnect(); - await Database.disconnect(); - SolrManager.SetRunning(false); - res.send("Server successfully shut down."); - process.exit(0); + subscription: new RouteSubscriber("kill").add("key"), + secureHandler: ({ req, res }) => { + if (req.params.key === process.env.session_key) { + res.send("<img src='https://media.giphy.com/media/NGIfqtcS81qi4/giphy.gif' style='width:100%;height:100%;'/>"); + process.send!({ action: { message: "kill" } }); + } else { + res.redirect("/home"); + } } }); - const serve: OnUnauthenticated = ({ req, res }) => { + const serve: PublicHandler = ({ req, res }) => { const detector = new mobileDetect(req.headers['user-agent'] || ""); const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; res.sendFile(path.join(__dirname, '../../deploy/' + filename)); @@ -110,8 +112,8 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: addSupervisedRoute({ method: Method.GET, subscription: ["/home", new RouteSubscriber("doc").add("docId")], - onValidation: serve, - onUnauthenticated: ({ req, ...remaining }) => { + secureHandler: serve, + publicHandler: ({ req, ...remaining }) => { const { originalUrl: target } = req; const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === "true"; const docAccess = target.startsWith("/doc/"); @@ -125,14 +127,70 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: // initialize the web socket (bidirectional communication: if a user changes // a field on one client, that change must be broadcast to all other clients) - WebSocket.initialize(serverPort, isRelease); + WebSocket.start(isRelease); } -(async function start() { +/** + * This function can be used in two different ways. If not in release mode, + * this is simply the logic that is invoked to start the server. In release mode, + * however, this becomes the logic invoked by a single worker thread spawned by + * the main monitor (master) thread. + */ +async function launchServer() { await log_execution({ startMessage: "\nstarting execution of preliminary functions", endMessage: "completed preliminary functions\n", action: preliminaryFunctions }); - await initializeServer({ serverPort: 1050, routeSetter }); -})(); + await initializeServer(routeSetter); +} + +/** + * If we're the monitor (master) thread, we should launch the monitor logic for the session. + * Otherwise, we must be on a worker thread that was spawned *by* the monitor (master) thread, and thus + * our job should be to run the server. + */ +async function launchMonitoredSession() { + if (isMaster) { + const recipients = ["samuel_wilkins@brown.edu"]; + const signature = "-Dash Server Session Manager"; + const customizer = await Session.initializeMonitorThread({ + key: async (key: string) => { + const content = `The key for this session (started @ ${new Date().toUTCString()}) is ${key}.\n\n${signature}`; + const failures = await Email.dispatchAll(recipients, "Server Termination Key", content); + return failures.length === 0; + }, + crash: async (error: Error) => { + const subject = "Dash Web Server Crash"; + const { name, message, stack } = error; + const body = [ + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `name:\n${name}`, + `message:\n${message}`, + `stack:\n${stack}`, + "The server is already restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress.", + ].join("\n\n"); + const content = `${body}\n\n${signature}`; + const failures = await Email.dispatchAll(recipients, subject, content); + return failures.length === 0; + } + }); + customizer.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] })); + customizer.addReplCommand("solr", [/start|stop/g], args => SolrManager.SetRunning(args[0] === "start")); + } else { + const addExitHandler = await Session.initializeWorkerThread(launchServer); // server initialization delegated to worker + addExitHandler(() => Utils.Emit(WebSocket._socket, MessageStore.ConnectionTerminated, "Manual")); + } +} + +/** + * If you're in development mode, you won't need to run a session. + * The session spawns off new server processes each time an error is encountered, and doesn't + * log the output of the server process, so it's not ideal for development. + * So, the 'else' clause is exactly what we've always run when executing npm start. + */ +if (process.env.RELEASE) { + launchMonitoredSession(); +} else { + launchServer(); +}
\ No newline at end of file |