From 0b21ee48c04c6690e574f8ecfb24c7447136bff0 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 2 Jan 2020 17:03:41 -0800 Subject: stable, clustered session manager --- src/server/Websocket/Websocket.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/server/Websocket') diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index e1e157fc4..0b58ca344 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -13,6 +13,7 @@ import { green } from "colors"; export namespace WebSocket { + export let _socket: Socket; const clients: { [key: string]: Client } = {}; export const socketMap = new Map(); export let disconnect: Function; @@ -28,6 +29,8 @@ export namespace WebSocket { export function initialize(socketPort: number, isRelease: boolean) { const endpoint = io(); endpoint.on("connection", function (socket: Socket) { + _socket = socket; + socket.use((_packet, next) => { const userEmail = socketMap.get(socket); if (userEmail) { -- cgit v1.2.3-70-g09d2 From 987b512d2564710e5c5c7fd2eeff1914af8180dd Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 4 Jan 2020 13:02:54 -0800 Subject: factored out socket and server ports to config, added kill response ;) --- session.config.json | 4 +++- src/server/Initialization.ts | 11 +++-------- src/server/Session/session.ts | 28 ++++++++++++++++------------ src/server/Session/session_config_schema.ts | 6 ++++-- src/server/Websocket/Websocket.ts | 7 ++++--- src/server/index.ts | 12 ++++++------ 6 files changed, 36 insertions(+), 32 deletions(-) (limited to 'src/server/Websocket') diff --git a/session.config.json b/session.config.json index 7396e1135..d8f86d239 100644 --- a/session.config.json +++ b/session.config.json @@ -3,7 +3,9 @@ "recipients": [ "samuel_wilkins@brown.edu" ], - "heartbeat": "http://localhost:1050/serverHeartbeat", + "serverPort": 1050, + "socketPort": 4321, + "heartbeatRoute": "/serverHeartbeat", "pollingIntervalSeconds": 15, "masterIdentifier": "__master__", "workerIdentifier": "__worker__", diff --git a/src/server/Initialization.ts b/src/server/Initialization.ts index 465e7ea63..702339ca1 100644 --- a/src/server/Initialization.ts +++ b/src/server/Initialization.ts @@ -26,15 +26,9 @@ import { blue, yellow } from 'colors'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ export type RouteSetter = (server: RouteManager) => void; -export interface InitializationOptions { - serverPort: number; - routeSetter: RouteSetter; -} - export let disconnect: Function; -export default async function InitializeServer(options: InitializationOptions) { - const { serverPort, routeSetter } = options; +export default async function InitializeServer(routeSetter: RouteSetter) { const app = buildWithMiddleware(express()); app.use(express.static(publicDirectory)); @@ -63,8 +57,9 @@ export default async function InitializeServer(options: InitializationOptions) { routeSetter(new RouteManager(app, isRelease)); + const serverPort = Number(process.env.serverPort); const server = app.listen(serverPort, () => { - logPort("server", serverPort); + logPort("server", Number(serverPort)); console.log(); }); disconnect = async () => new Promise(resolve => server.close(resolve)); diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts index e32811a18..d1d7aab87 100644 --- a/src/server/Session/session.ts +++ b/src/server/Session/session.ts @@ -40,7 +40,9 @@ export namespace Session { workerIdentifier, recipients, signature, - heartbeat, + heartbeatRoute, + serverPort, + socketPort, showServerOutput, pollingIntervalSeconds } = function loadConfiguration(): any { @@ -72,7 +74,7 @@ export namespace Session { }(); // this sends a pseudorandomly generated guid to the configuration's recipients, allowing them alone - // to kill the server via the /kill/:password route + // to kill the server via the /kill/:key route const key = Utils.GenerateGuid(); const timestamp = new Date().toUTCString(); const content = `The key for this session (started @ ${timestamp}) is ${key}.\n\n${signature}`; @@ -112,7 +114,9 @@ export namespace Session { const spawn = (): void => { tryKillActiveWorker(); activeWorker = fork({ - heartbeat, + heartbeatRoute, + serverPort, + socketPort, pollingIntervalSeconds, session_key: key }); @@ -212,18 +216,22 @@ export namespace Session { // one reason to exit, as the process might be in an inconsistent state after such an exception process.on('uncaughtException', activeExit); - const { pollingIntervalSeconds, heartbeat } = process.env; - + const { + pollingIntervalSeconds, + heartbeatRoute, + serverPort + } = process.env; // this monitors the health of the server by submitting a get request to whatever port / route specified // by the configuration every n seconds, where n is also given by the configuration. + const heartbeat = `http://localhost:${serverPort}${heartbeatRoute}`; const checkHeartbeat = async (): Promise => { await new Promise(resolve => { setTimeout(async () => { try { - await get(heartbeat!); + await get(heartbeat); if (!listening) { // notify master thread (which will log update in the console) via IPC that the server is up and running - process.send?.({ lifecycle: green("listening...") }); + process.send?.({ lifecycle: green(`listening on ${serverPort}...`) }); } listening = true; resolve(); @@ -239,12 +247,8 @@ export namespace Session { checkHeartbeat(); }; - // the actual work of the process, may be asynchronous - // for Dash, this is the code that launches the server work(); - - // begin polling - checkHeartbeat(); + checkHeartbeat(); // begin polling } } \ No newline at end of file diff --git a/src/server/Session/session_config_schema.ts b/src/server/Session/session_config_schema.ts index a5010055a..03009a351 100644 --- a/src/server/Session/session_config_schema.ts +++ b/src/server/Session/session_config_schema.ts @@ -1,7 +1,7 @@ import { Schema } from "jsonschema"; const emailPattern = /^(([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+\.([a-zA-Z])+([a-zA-Z])+)?$/g; -const localPortPattern = /http\:\/\/localhost:\d+\/[a-zA-Z]+/g; +const localPortPattern = /\/[a-zA-Z]+/g; const properties = { recipients: { @@ -12,7 +12,9 @@ const properties = { }, minLength: 1 }, - heartbeat: { + serverPort: { type: "number" }, + socketPort: { type: "number" }, + heartbeatRoute: { type: "string", pattern: localPortPattern }, diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 0b58ca344..e26a76107 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -18,15 +18,15 @@ export namespace WebSocket { export const socketMap = new Map(); export let disconnect: Function; - export async function start(serverPort: number, isRelease: boolean) { + export async function start(isRelease: boolean) { await preliminaryFunctions(); - initialize(serverPort, isRelease); + initialize(isRelease); } async function preliminaryFunctions() { } - export function initialize(socketPort: number, isRelease: boolean) { + function initialize(isRelease: boolean) { const endpoint = io(); endpoint.on("connection", function (socket: Socket) { _socket = socket; @@ -63,6 +63,7 @@ export namespace WebSocket { }; }); + const socketPort = Number(process.env.socketPort); endpoint.listen(socketPort); logPort("websocket", socketPort); } diff --git a/src/server/index.ts b/src/server/index.ts index 3c1839e4d..7eb8b12be 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -90,11 +90,11 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: addSupervisedRoute({ method: Method.GET, - subscription: new RouteSubscriber("kill").add("password"), + subscription: new RouteSubscriber("kill").add("key"), secureHandler: ({ req, res }) => { - if (req.params.password === process.env.session_key) { - process.send!({ action: { message: "kill" } }); - res.send("Server successfully killed."); + if (req.params.key === process.env.session_key) { + res.send(""); + setTimeout(() => process.send!({ action: { message: "kill" } }), 1000 * 5); } else { res.redirect("/home"); } @@ -125,7 +125,7 @@ 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); } /** @@ -142,6 +142,6 @@ if (isMaster) { endMessage: "completed preliminary functions\n", action: preliminaryFunctions }); - await initializeServer({ serverPort: 1050, routeSetter }); + await initializeServer(routeSetter); }); } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 962428e758d5df95f54d1af8750c1447556198a7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 5 Jan 2020 16:08:44 -0800 Subject: port fixes --- src/server/Initialization.ts | 2 +- src/server/Websocket/Websocket.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server/Websocket') diff --git a/src/server/Initialization.ts b/src/server/Initialization.ts index 702339ca1..d99aaa3f0 100644 --- a/src/server/Initialization.ts +++ b/src/server/Initialization.ts @@ -57,7 +57,7 @@ export default async function InitializeServer(routeSetter: RouteSetter) { routeSetter(new RouteManager(app, isRelease)); - const serverPort = Number(process.env.serverPort); + const serverPort = process.env.serverPort ? Number(process.env.serverPort) : 1050; const server = app.listen(serverPort, () => { logPort("server", Number(serverPort)); console.log(); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index e26a76107..e574cd111 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -63,7 +63,7 @@ export namespace WebSocket { }; }); - const socketPort = Number(process.env.socketPort); + const socketPort = process.env.socketPort ? Number(process.env.socketPort) : 4321; endpoint.listen(socketPort); logPort("websocket", socketPort); } -- cgit v1.2.3-70-g09d2 From f1c4bdb6c0f80dfe486a589623bd6fd864c1e686 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 5 Jan 2020 17:37:22 -0800 Subject: commenting --- src/server/Initialization.ts | 147 ------------------------------------ src/server/Websocket/Websocket.ts | 2 +- src/server/index.ts | 70 +++++++++++------ src/server/server_Initialization.ts | 147 ++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 173 deletions(-) delete mode 100644 src/server/Initialization.ts create mode 100644 src/server/server_Initialization.ts (limited to 'src/server/Websocket') diff --git a/src/server/Initialization.ts b/src/server/Initialization.ts deleted file mode 100644 index d99aaa3f0..000000000 --- a/src/server/Initialization.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as express from 'express'; -import * as expressValidator from 'express-validator'; -import * as session from 'express-session'; -import * as passport from 'passport'; -import * as bodyParser from 'body-parser'; -import * as cookieParser from 'cookie-parser'; -import expressFlash = require('express-flash'); -import flash = require('connect-flash'); -import { Database } from './database'; -import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller'; -const MongoStore = require('connect-mongo')(session); -import RouteManager from './RouteManager'; -import * as webpack from 'webpack'; -const config = require('../../webpack.config'); -const compiler = webpack(config); -import * as wdm from 'webpack-dev-middleware'; -import * as whm from 'webpack-hot-middleware'; -import * as fs from 'fs'; -import * as request from 'request'; -import RouteSubscriber from './RouteSubscriber'; -import { publicDirectory } from '.'; -import { logPort, } from './ActionUtilities'; -import { timeMap } from './ApiManagers/UserManager'; -import { blue, yellow } from 'colors'; - -/* RouteSetter is a wrapper around the server that prevents the server - from being exposed. */ -export type RouteSetter = (server: RouteManager) => void; -export let disconnect: Function; - -export default async function InitializeServer(routeSetter: RouteSetter) { - const app = buildWithMiddleware(express()); - - app.use(express.static(publicDirectory)); - app.use("/images", express.static(publicDirectory)); - - app.use("*", ({ user, originalUrl }, res, next) => { - if (user && !originalUrl.includes("Heartbeat")) { - const userEmail = (user as any).email; - if (userEmail) { - timeMap[userEmail] = Date.now(); - } - } - if (!user && originalUrl === "/") { - return res.redirect("/login"); - } - next(); - }); - - app.use(wdm(compiler, { publicPath: config.output.publicPath })); - app.use(whm(compiler)); - - registerAuthenticationRoutes(app); - registerCorsProxy(app); - - const isRelease = determineEnvironment(); - - routeSetter(new RouteManager(app, isRelease)); - - const serverPort = process.env.serverPort ? Number(process.env.serverPort) : 1050; - const server = app.listen(serverPort, () => { - logPort("server", Number(serverPort)); - console.log(); - }); - disconnect = async () => new Promise(resolve => server.close(resolve)); - - return isRelease; -} - -const week = 7 * 24 * 60 * 60 * 1000; -const secret = "64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc"; - -function buildWithMiddleware(server: express.Express) { - [ - cookieParser(), - session({ - secret, - resave: true, - cookie: { maxAge: week }, - saveUninitialized: true, - store: new MongoStore({ url: Database.url }) - }), - flash(), - expressFlash(), - bodyParser.json({ limit: "10mb" }), - bodyParser.urlencoded({ extended: true }), - expressValidator(), - passport.initialize(), - passport.session(), - (req: express.Request, res: express.Response, next: express.NextFunction) => { - res.locals.user = req.user; - next(); - } - ].forEach(next => server.use(next)); - return server; -} - -/* Determine if the enviroment is dev mode or release mode. */ -function determineEnvironment() { - const isRelease = process.env.RELEASE === "true"; - - const color = isRelease ? blue : yellow; - const label = isRelease ? "release" : "development"; - console.log(`\nrunning server in ${color(label)} mode`); - - let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8"); - clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; - fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8"); - - return isRelease; -} - -function registerAuthenticationRoutes(server: express.Express) { - server.get("/signup", getSignup); - server.post("/signup", postSignup); - - server.get("/login", getLogin); - server.post("/login", postLogin); - - server.get("/logout", getLogout); - - server.get("/forgotPassword", getForgot); - server.post("/forgotPassword", postForgot); - - const reset = new RouteSubscriber("resetPassword").add("token").build; - server.get(reset, getReset); - server.post(reset, postReset); -} - -function registerCorsProxy(server: express.Express) { - const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; - server.use("/corsProxy", (req, res) => { - req.pipe(request(decodeURIComponent(req.url.substring(1)))).on("response", res => { - const headers = Object.keys(res.headers); - headers.forEach(headerName => { - const header = res.headers[headerName]; - if (Array.isArray(header)) { - res.headers[headerName] = header.filter(h => !headerCharRegex.test(h)); - } else if (header) { - if (headerCharRegex.test(header as any)) { - delete res.headers[headerName]; - } - } - }); - }).pipe(res); - }); -} \ No newline at end of file diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index e574cd111..578147d60 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -63,7 +63,7 @@ export namespace WebSocket { }; }); - const socketPort = process.env.socketPort ? Number(process.env.socketPort) : 4321; + const socketPort = isRelease ? Number(process.env.socketPort) : 4321; endpoint.listen(socketPort); logPort("websocket", socketPort); } diff --git a/src/server/index.ts b/src/server/index.ts index 18b31e7a4..d1480e51e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -6,7 +6,7 @@ import { Database } from './database'; const serverPort = 4321; import { DashUploadUtils } from './DashUploadUtils'; import RouteSubscriber from './RouteSubscriber'; -import initializeServer from './Initialization'; +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'; @@ -130,6 +130,12 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: WebSocket.start(isRelease); } +/** + * 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", @@ -139,31 +145,45 @@ async function launchServer() { await initializeServer(routeSetter); } -if (process.env.RELEASE) { - /** - * Thread dependent session initialization +/** + * A function to dictate the format of the message sent on crash + * @param error the error that caused the crash */ - (async function launchSession() { - if (isMaster) { - const emailGenerator = (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"); - return { subject, body }; - }; - const customizer = await Session.initializeMonitorThread(emailGenerator); - customizer.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] })); - } else { - const addExitHandler = await Session.initializeWorkerThread(launchServer); - addExitHandler(() => Utils.Emit(WebSocket._socket, MessageStore.ConnectionTerminated, "Manual")); - } - })(); +function crashEmailGenerator(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"); + return { subject, body }; +} + +/** + * If on the master thread, launches the monitor for the session. + * Otherwise, the thread must have been spawned *by* the monitor, and thus + * should run the server as a worker. + */ +async function launchMonitoredSession() { + if (isMaster) { + const customizer = await Session.initializeMonitorThread(crashEmailGenerator); + customizer.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] })); + } else { + const addExitHandler = await Session.initializeWorkerThread(launchServer); // server initialization delegated to worker + addExitHandler(() => Utils.Emit(WebSocket._socket, MessageStore.ConnectionTerminated, "Manual")); + } +} + +/** + * Ensures that development mode avoids + * the overhead and lack of default output + * found in a release session. + */ +if (process.env.RELEASE) { + launchMonitoredSession(); } else { launchServer(); } \ No newline at end of file diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts new file mode 100644 index 000000000..4cb1fca47 --- /dev/null +++ b/src/server/server_Initialization.ts @@ -0,0 +1,147 @@ +import * as express from 'express'; +import * as expressValidator from 'express-validator'; +import * as session from 'express-session'; +import * as passport from 'passport'; +import * as bodyParser from 'body-parser'; +import * as cookieParser from 'cookie-parser'; +import expressFlash = require('express-flash'); +import flash = require('connect-flash'); +import { Database } from './database'; +import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller'; +const MongoStore = require('connect-mongo')(session); +import RouteManager from './RouteManager'; +import * as webpack from 'webpack'; +const config = require('../../webpack.config'); +const compiler = webpack(config); +import * as wdm from 'webpack-dev-middleware'; +import * as whm from 'webpack-hot-middleware'; +import * as fs from 'fs'; +import * as request from 'request'; +import RouteSubscriber from './RouteSubscriber'; +import { publicDirectory } from '.'; +import { logPort, } from './ActionUtilities'; +import { timeMap } from './ApiManagers/UserManager'; +import { blue, yellow } from 'colors'; + +/* RouteSetter is a wrapper around the server that prevents the server + from being exposed. */ +export type RouteSetter = (server: RouteManager) => void; +export let disconnect: Function; + +export default async function InitializeServer(routeSetter: RouteSetter) { + const app = buildWithMiddleware(express()); + + app.use(express.static(publicDirectory)); + app.use("/images", express.static(publicDirectory)); + + app.use("*", ({ user, originalUrl }, res, next) => { + if (user && !originalUrl.includes("Heartbeat")) { + const userEmail = (user as any).email; + if (userEmail) { + timeMap[userEmail] = Date.now(); + } + } + if (!user && originalUrl === "/") { + return res.redirect("/login"); + } + next(); + }); + + app.use(wdm(compiler, { publicPath: config.output.publicPath })); + app.use(whm(compiler)); + + registerAuthenticationRoutes(app); + registerCorsProxy(app); + + const isRelease = determineEnvironment(); + + routeSetter(new RouteManager(app, isRelease)); + + const serverPort = isRelease ? Number(process.env.serverPort) : 1050; + const server = app.listen(serverPort, () => { + logPort("server", Number(serverPort)); + console.log(); + }); + disconnect = async () => new Promise(resolve => server.close(resolve)); + + return isRelease; +} + +const week = 7 * 24 * 60 * 60 * 1000; +const secret = "64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc"; + +function buildWithMiddleware(server: express.Express) { + [ + cookieParser(), + session({ + secret, + resave: true, + cookie: { maxAge: week }, + saveUninitialized: true, + store: new MongoStore({ url: Database.url }) + }), + flash(), + expressFlash(), + bodyParser.json({ limit: "10mb" }), + bodyParser.urlencoded({ extended: true }), + expressValidator(), + passport.initialize(), + passport.session(), + (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.locals.user = req.user; + next(); + } + ].forEach(next => server.use(next)); + return server; +} + +/* Determine if the enviroment is dev mode or release mode. */ +function determineEnvironment() { + const isRelease = process.env.RELEASE === "true"; + + const color = isRelease ? blue : yellow; + const label = isRelease ? "release" : "development"; + console.log(`\nrunning server in ${color(label)} mode`); + + let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8"); + clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; + fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8"); + + return isRelease; +} + +function registerAuthenticationRoutes(server: express.Express) { + server.get("/signup", getSignup); + server.post("/signup", postSignup); + + server.get("/login", getLogin); + server.post("/login", postLogin); + + server.get("/logout", getLogout); + + server.get("/forgotPassword", getForgot); + server.post("/forgotPassword", postForgot); + + const reset = new RouteSubscriber("resetPassword").add("token").build; + server.get(reset, getReset); + server.post(reset, postReset); +} + +function registerCorsProxy(server: express.Express) { + const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; + server.use("/corsProxy", (req, res) => { + req.pipe(request(decodeURIComponent(req.url.substring(1)))).on("response", res => { + const headers = Object.keys(res.headers); + headers.forEach(headerName => { + const header = res.headers[headerName]; + if (Array.isArray(header)) { + res.headers[headerName] = header.filter(h => !headerCharRegex.test(h)); + } else if (header) { + if (headerCharRegex.test(header as any)) { + delete res.headers[headerName]; + } + } + }); + }).pipe(res); + }); +} \ No newline at end of file -- cgit v1.2.3-70-g09d2