Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 557ff54b2b435e5f1e789c6a8a4e1bebf2d7deb6 143 lines 5.1 kB view raw
1#!/usr/bin/env node 2import http from 'http'; 3import { promisify } from 'util'; 4import _ from 'lodash'; 5 6import getBottle from '../iocContainer/index.js'; 7import makeServer from '../server.js'; 8import { 9 getIntegrationRegistry, 10 getIntegrationsConfigPath, 11} from '../services/integrationRegistry/index.js'; 12import { logErrorJson, logJson } from '../utils/logging.js'; 13import { sleep } from '../utils/misc.js'; 14 15const { app, shutdown } = await getBottle().then(async (bottle) => 16 makeServer(bottle.container), 17); 18 19// Eager-load integration registry so config/plugins are read at startup (fail fast, and so logo URLs are set). 20try { 21 const registry = getIntegrationRegistry(); 22 const configPath = getIntegrationsConfigPath(); 23 const ids = registry.getConfigurableIds(); 24 // eslint-disable-next-line no-restricted-syntax 25 logJson( 26 `Integrations: config=${configPath}, loaded=${ids.length} (${ids.join(', ')})`, 27 ); 28} catch (err) { 29 // eslint-disable-next-line no-restricted-syntax 30 logErrorJson({ 31 message: 'Failed to load integrations registry', 32 error: err instanceof Error ? err : new Error(String(err)), 33 }); 34 process.exit(1); 35} 36 37const port = parsePort(process.env.PORT) ?? 8080; 38app.set('port', port); 39 40const server = http 41 .createServer(app) 42 .listen(port, () => { 43 // eslint-disable-next-line no-restricted-syntax 44 logJson(`Server is running at http://localhost:${port}`); 45 }) 46 .on('error', function (error: NodeJS.ErrnoException) { 47 // eslint-disable-next-line no-restricted-syntax 48 logErrorJson({ error }); 49 if (error.syscall !== 'listen') { 50 throw error; 51 } 52 53 // handle specific listen errors with friendly messages 54 switch (error.code) { 55 case 'EACCES': 56 // eslint-disable-next-line no-restricted-syntax 57 logErrorJson({ 58 error, 59 message: `Port ${port} requires elevated privileges`, 60 }); 61 process.exit(1); 62 case 'EADDRINUSE': 63 // eslint-disable-next-line no-restricted-syntax 64 logErrorJson({ error, message: `Port ${port} is already in use.` }); 65 process.exit(1); 66 default: 67 throw error; 68 } 69 }); 70 71// We need the keep-alive timeout to be longer than the ALB's idle timeout to 72// prevent the 502 errors we've been seeing. See this article for more info: 73// https://adamcrowder.net/posts/node-express-api-and-aws-alb-502/ 74server.keepAliveTimeout = 65_000; 75 76const shutdownOnce = _.once(async () => { 77 try { 78 const closeServer = promisify(server.close.bind(server)); 79 80 // Immediately disallow new connections + close idle ones. 81 const serverClosedPromise = closeServer(); 82 server.closeIdleConnections(); 83 84 // For the remaining connections (that were still active when we called 85 // `closeIdleConnections()` above), close them one at at time, as they 86 // become idle. To do that, after a response is finished, we close all idle 87 // connections, rather than just closing the socket that that request was 88 // using to send its response. This is probably overkill (and not optimal 89 // perf-wise, as `closeIdleConnections()` loops over all open connections) 90 // but it's defensive against the (rare and likely-inapplicable) possibility 91 // that the socket might still be in use for a request whose response hasn't 92 // been sent yet. That can happen multiple requests were pipelined over the 93 // same socket, which Nodejs supports (see 94 // https://www.yld.io/blog/exploring-how-nodejs-handles-http-connections/), 95 // although our load balancer probably never uses HTTP pipelining, as it's 96 // ill-supported by backends. For this to make a difference, we're also 97 // assuming that Node's definition of "idle" accounts for the possibility of 98 // pipelining. 99 server.prependListener('request', (_req, res) => { 100 if (!res.headersSent) { 101 res.setHeader('connection', 'close'); 102 } 103 104 res.prependOnceListener('finish', () => { 105 server.closeIdleConnections(); 106 }); 107 }); 108 109 // After 30s, if the server hasn't finished closing, force close all 110 // connections. Waiting longer doesn't make sense because the load balancer will 111 // time out the request at that point anyway. 112 await Promise.race([sleep(30_000), serverClosedPromise]); 113 server.closeAllConnections(); 114 115 // Now that there are no pending requests or open client connections, clean 116 // up the app resources, like db connections, apollo plugins, etc. 117 await shutdown(); 118 process.exit(0); 119 } catch (e) { 120 // eslint-disable-next-line no-restricted-syntax 121 logErrorJson({ message: 'error during shutdown', error: e }); 122 process.exit(1); 123 } 124}); 125 126process.on('uncaughtException', (err, _) => { 127 // eslint-disable-next-line no-restricted-syntax 128 logErrorJson({ 129 message: 'UncaughtException', 130 error: err, 131 }); 132 process.exit(1); 133}); 134 135process.once('SIGTERM', shutdownOnce); 136process.once('SIGINT', shutdownOnce); 137 138function parsePort(val: string | undefined): number | undefined { 139 if (!val) return undefined; 140 141 const parsed = parseInt(val, 10); 142 return isNaN(parsed) ? undefined : parsed; 143}