forked from Ed-Fi-Alliance-OSS/edfi-oneroster
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
132 lines (110 loc) · 4.41 KB
/
server.js
File metadata and controls
132 lines (110 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: Apache-2.0
// Licensed to EdTech Consortium, Inc. under one or more agreements.
// EdTech Consortium, Inc. licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.
// Load environment variables FIRST before importing any modules
import dotenv from 'dotenv';
import fs from 'fs';
import https from 'https';
dotenv.config();
// Validate environment variables before proceeding
const { validateAndExit } = await import('./src/utils/envValidator.js');
validateAndExit();
// Use dynamic imports to ensure dotenv is loaded before app initialization
const { default: app } = await import('./src/app.js');
const { initializeCronJobs } = await import('./src/services/cronService.js');
const { odsInstanceService } = await import('./src/services/database/OdsInstanceService.js');
const { knexManager } = await import('./src/config/knex-factory.js');
const PORT = process.env.PORT || 3000;
const HTTPS_ENABLED = (process.env.ENABLE_HTTPS || 'false').toLowerCase() === 'true';
function loadTlsCredentials() {
const keyPath = process.env.TLS_KEY_PATH;
const certPath = process.env.TLS_CERT_PATH;
if (!keyPath || !certPath) {
throw new Error('ENABLE_HTTPS=true requires TLS_KEY_PATH and TLS_CERT_PATH.');
}
const credentials = {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath),
minVersion: 'TLSv1.2'
};
if (process.env.TLS_CA_PATH) {
credentials.ca = fs.readFileSync(process.env.TLS_CA_PATH);
}
return credentials;
}
// Store server and pgBoss instances for graceful shutdown
let server;
if (HTTPS_ENABLED) {
const tlsCredentials = loadTlsCredentials();
server = https.createServer(tlsCredentials, app).listen(PORT, () => {
console.log(`HTTPS server running on port ${PORT}`);
});
} else {
server = app.listen(PORT, () => {
console.log(`HTTP server running on port ${PORT}`);
});
}
// Initialize CRON jobs for materialized view refresh (PostgreSQL only)
let pgBossInstance = null;
initializeCronJobs()
.then(boss => {
pgBossInstance = boss;
})
.catch(err => {
console.error('Failed to initialize CRON jobs:', err);
// Server continues running even if CRON jobs fail to start
});
/**
* Graceful shutdown handler
* Cleans up resources when SIGTERM or SIGINT is received
* Important for IIS app pool recycling (gracefulShutdownTimeout: 60s)
*/
async function gracefulShutdown(signal) {
console.log(`\n[Server] ${signal} received. Starting graceful shutdown...`);
// Stop accepting new connections
server.close(async () => {
console.log('[Server] HTTP server closed');
try {
// Clean up resources in parallel where possible
const cleanupTasks = [];
// Stop pg-boss CRON jobs
if (pgBossInstance) {
console.log('[Server] Stopping pg-boss...');
cleanupTasks.push(
pgBossInstance.stop({ graceful: true, timeout: 5000 })
.then(() => console.log('[Server] pg-boss stopped'))
.catch(err => console.error('[Server] Error stopping pg-boss:', err.message))
);
}
// Close ODS Instance Service admin connections
console.log('[Server] Closing ODS instance admin connections...');
cleanupTasks.push(
odsInstanceService.destroy()
.then(() => console.log('[Server] ODS instance connections closed'))
.catch(err => console.error('[Server] Error closing ODS instance connections:', err.message))
);
// Close all knex connection pools
console.log('[Server] Closing knex connection pools...');
cleanupTasks.push(
knexManager.closeAll()
.then(() => console.log('[Server] Knex connections closed'))
.catch(err => console.error('[Server] Error closing knex connections:', err.message))
);
await Promise.allSettled(cleanupTasks);
console.log('[Server] All resources cleaned up successfully');
process.exit(0);
} catch (error) {
console.error('[Server] Error during shutdown:', error);
process.exit(1);
}
});
// Force shutdown after timeout (50s to fit within IIS 60s gracefulShutdownTimeout)
setTimeout(() => {
console.error('[Server] Forced shutdown after 50s timeout');
process.exit(1);
}, 50000);
}
// Register signal handlers for graceful shutdown
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));