aboutsummaryrefslogtreecommitdiff
path: root/src/server/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/index.ts')
-rw-r--r--src/server/index.ts108
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