-
Notifications
You must be signed in to change notification settings - Fork 3
/
instrument-listen.js
118 lines (103 loc) · 3.67 KB
/
instrument-listen.js
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
// @ts-nocheck
const metrics = require('next-metrics');
const logger = require('@dotcom-reliability-kit/logger');
const http = require('http');
const https = require('https');
const path = require('path');
const {readFile} = require('fs/promises');
const {STATUS_CODES} = http;
const serializeRequest = require('@dotcom-reliability-kit/serialize-request');
module.exports = class InstrumentListen {
constructor (app, meta, initPromises) {
this.app = app;
this.server = null;
this.initApp(meta, initPromises);
}
async createServer () {
if (process.argv.includes('--https')) {
const [key, cert] = await Promise.all([
readFile(path.resolve(process.cwd(), 'self-signed-ssl-key.pem')),
readFile(path.resolve(process.cwd(), 'self-signed-ssl-certificate.pem'))
]).catch(() => {
throw Error(
'n-express was started with --https, but there\'s no self-signed certificate or key in your app directory. run `npx n-express-generate-certificate` to create one'
);
});
return https.createServer({ key, cert }, this.app);
} else {
return http.createServer(this.app);
}
}
initApp (meta, initPromises) {
this.app.listen = async (port, callback) => {
// these middleware are attached in .listen so they're
// definitely after any middleware added by the app itself
// Optional fallthrough error handler
// eslint-disable-next-line no-unused-vars
this.app.use((err, req, res, next) => {
let statusCode = parseInt(err.statusCode || err.status || 500, 10);
// There's clearly an error, so if the error has a status
// code of less than `400` we should default to `500` to
// ensure bad error handling doesn't send false positive
// status codes. We also check that the status code is
// a valid number.
const isValidErrorStatus = (
!Number.isNaN(statusCode) && // Possible if `error.status` is something unexpected, like an object
statusCode >= 400 &&
statusCode <= 599
);
res.statusCode = isValidErrorStatus ? statusCode : 500;
const statusMessage = STATUS_CODES[res.statusCode] || STATUS_CODES[500];
// If headers have been sent already then we need to hand off to
// the final Express error handler as documented here:
// https://expressjs.com/en/guide/error-handling.html#the-default-error-handler
//
// This ensures that the app doesn't crash with `ERR_STREAM_WRITE_AFTER_END`
if (res.headersSent) {
logger.warn({
event: 'EXPRESS_ERROR_HANDLER_FAILURE',
message: 'The default n-express error handler could not output because the response has already been sent',
request: serializeRequest(req)
}, err);
return next(err);
}
const output = `${res.statusCode} ${statusMessage}\n`;
res.end(output);
});
function wrappedCallback () {
// HACK: Use warn so that it gets into Splunk logs
logger.warn({
event: 'EXPRESS_START',
message: `Express application ${meta.name} started`,
app: meta.name,
port: port,
nodeVersion: process.version
});
if (callback) {
return callback.apply(this, arguments);
}
}
try {
await Promise.all(initPromises);
metrics.count('express.start');
const server = await this.createServer();
this.server = server;
return server.listen(port, wrappedCallback);
} catch (err) {
// crash app if initPromises fail by throwing an error asynchronously outside of the promise
// TODO: better error handling
setTimeout(() => {
throw err;
});
}
};
/**
* Attempts to clean up the ticking checks and close the server
*/
this.app.close = (callback) => {
if (this.server) {
this.server.close(() => callback && callback());
}
};
}
};