Skip to content

Commit ee94766

Browse files
gergelykerxmarblessindilevich
authored
feat(status): custom status codes (#190)
* feat(status): custom status codes * revert(binary) revert lockfile change * Custom status (#191) * Revert "feat(status): custom status codes" This reverts commit 522df64. * Added statusOk and statusError to configuration The new configuration properties: statusOk and statusError will allow to globally configure the HTTP codes, used to respond with on a successful or an unsuccessful healthcheck respectively. An unsuccessful healthcheck may override the global statusError property by setting the statusCode property on the Error object. * Added typings for statusOk and statusError Co-authored-by: rxmarbles <[email protected]> Co-authored-by: Matanel Sindilevich <[email protected]>
1 parent 04191cc commit ee94766

File tree

4 files changed

+127
-21
lines changed

4 files changed

+127
-21
lines changed

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,26 @@ const options = {
5252
// health check options
5353
healthChecks: {
5454
'/healthcheck': healthCheck, // a function accepting a state and returning a promise indicating service health,
55-
verbatim: true, // [optional = false] use object returned from /healthcheck verbatim in response,
55+
verbatim: true, // [optional = false] use object returned from /healthcheck verbatim in response,
5656
__unsafeExposeStackTraces: true // [optional = false] return stack traces in error response if healthchecks throw errors
5757
},
58-
caseInsensitive, // [optional] whether given health checks routes are case insensitive (defaults to false)
58+
caseInsensitive, // [optional] whether given health checks routes are case insensitive (defaults to false)
59+
60+
statusOk, // [optional = 200] status to be returned for successful healthchecks
61+
statusError, // [optional = 503] status to be returned for unsuccessful healthchecks
5962

6063
// cleanup options
61-
timeout: 1000, // [optional = 1000] number of milliseconds before forceful exiting
62-
signal, // [optional = 'SIGTERM'] what signal to listen for relative to shutdown
63-
signals, // [optional = []] array of signals to listen for relative to shutdown
64-
sendFailuresDuringShutdown, // [optional = true] whether or not to send failure (503) during shutdown
65-
beforeShutdown, // [optional] called before the HTTP server starts its shutdown
66-
onSignal, // [optional] cleanup function, returning a promise (used to be onSigterm)
67-
onShutdown, // [optional] called right before exiting
68-
onSendFailureDuringShutdown, // [optional] called before sending each 503 during shutdowns
64+
timeout: 1000, // [optional = 1000] number of milliseconds before forceful exiting
65+
signal, // [optional = 'SIGTERM'] what signal to listen for relative to shutdown
66+
signals, // [optional = []] array of signals to listen for relative to shutdown
67+
sendFailuresDuringShutdown, // [optional = true] whether or not to send failure (503) during shutdown
68+
beforeShutdown, // [optional] called before the HTTP server starts its shutdown
69+
onSignal, // [optional] cleanup function, returning a promise (used to be onSigterm)
70+
onShutdown, // [optional] called right before exiting
71+
onSendFailureDuringShutdown, // [optional] called before sending each 503 during shutdowns
6972

7073
// both
71-
logger // [optional] logger function to be called with errors. Example logger call: ('error happened during shutdown', error). See terminus.js for more details.
74+
logger // [optional] logger function to be called with errors. Example logger call: ('error happened during shutdown', error). See terminus.js for more details.
7275
};
7376

7477
createTerminus(server, options);

lib/terminus.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ function noopResolves () {
1515
return Promise.resolve()
1616
}
1717

18-
async function sendSuccess (res, { info, verbatim }) {
19-
res.statusCode = 200
18+
async function sendSuccess (res, { info, verbatim, statusOk }) {
19+
res.statusCode = statusOk
2020
res.setHeader('Content-Type', 'application/json')
2121
if (info) {
2222
return res.end(
@@ -34,7 +34,7 @@ async function sendSuccess (res, { info, verbatim }) {
3434
}
3535

3636
async function sendFailure (res, options) {
37-
const { error, onSendFailureDuringShutdown, exposeStackTraces } = options
37+
const { error, onSendFailureDuringShutdown, exposeStackTraces, statusCode, statusError } = options
3838

3939
function replaceErrors (_, value) {
4040
if (value instanceof Error) {
@@ -54,7 +54,7 @@ async function sendFailure (res, options) {
5454
if (onSendFailureDuringShutdown) {
5555
await onSendFailureDuringShutdown()
5656
}
57-
res.statusCode = 503
57+
res.statusCode = statusCode || statusError
5858
res.setHeader('Content-Type', 'application/json')
5959
if (error) {
6060
return res.end(JSON.stringify({
@@ -73,24 +73,24 @@ const intialState = {
7373
function noop () {}
7474

7575
function decorateWithHealthCheck (server, state, options) {
76-
const { healthChecks, logger, onSendFailureDuringShutdown, sendFailuresDuringShutdown, caseInsensitive } = options
76+
const { healthChecks, logger, onSendFailureDuringShutdown, sendFailuresDuringShutdown, caseInsensitive, statusOk, statusError } = options
7777

7878
let hasSetHandler = false
7979
const createHandler = (listener) => {
8080
const check = hasSetHandler
8181
? () => {}
8282
: async (healthCheck, res) => {
8383
if (state.isShuttingDown && sendFailuresDuringShutdown) {
84-
return sendFailure(res, { onSendFailureDuringShutdown })
84+
return sendFailure(res, { onSendFailureDuringShutdown, statusError })
8585
}
8686
let info
8787
try {
8888
info = await healthCheck({ state })
8989
} catch (error) {
9090
logger('healthcheck failed', error)
91-
return sendFailure(res, { error: error.causes, exposeStackTraces: healthChecks.__unsafeExposeStackTraces })
91+
return sendFailure(res, { error: error.causes, exposeStackTraces: healthChecks.__unsafeExposeStackTraces, statusCode: error.statusCode, statusError })
9292
}
93-
return sendSuccess(res, { info, verbatim: healthChecks.verbatim })
93+
return sendSuccess(res, { info, verbatim: healthChecks.verbatim, statusOk })
9494
}
9595

9696
hasSetHandler = true
@@ -162,7 +162,9 @@ function terminus (server, options = {}) {
162162
onShutdown = noopResolves,
163163
beforeShutdown = noopResolves,
164164
logger = noop,
165-
caseInsensitive = false
165+
caseInsensitive = false,
166+
statusOk = 200,
167+
statusError = 503
166168
} = options
167169
const onSignal = options.onSignal || options.onSigterm || noopResolves
168170
const state = Object.assign({}, intialState)
@@ -173,7 +175,9 @@ function terminus (server, options = {}) {
173175
logger,
174176
sendFailuresDuringShutdown,
175177
onSendFailureDuringShutdown,
176-
caseInsensitive
178+
caseInsensitive,
179+
statusOk,
180+
statusError
177181
})
178182
}
179183

lib/terminus.spec.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,27 @@ describe('Terminus', () => {
5151
expect(onHealthCheckRan).to.eql(true)
5252
})
5353

54+
it('returns custom status code on resolve', async () => {
55+
let onHealthCheckRan = false
56+
57+
createTerminus(server, {
58+
healthChecks: {
59+
'/health': () => {
60+
onHealthCheckRan = true
61+
return Promise.resolve()
62+
}
63+
},
64+
statusOk: 201
65+
})
66+
server.listen(8000)
67+
68+
const response = await fetch('http://localhost:8000/health')
69+
expect(response.status).to.eql(201)
70+
expect(response.headers.has('Content-Type')).to.eql(true)
71+
expect(response.headers.get('Content-Type')).to.eql('application/json')
72+
expect(onHealthCheckRan).to.eql(true)
73+
})
74+
5475
it('case sensitive by default', async () => {
5576
let onHealthCheckRan = false
5677

@@ -216,6 +237,82 @@ describe('Terminus', () => {
216237
expect(loggerRan).to.eql(true)
217238
})
218239

240+
it('returns global custom status code on reject', async () => {
241+
let onHealthCheckRan = false
242+
let loggerRan = false
243+
244+
createTerminus(server, {
245+
healthChecks: {
246+
'/health': () => {
247+
onHealthCheckRan = true
248+
const error = new Error()
249+
return Promise.reject(error)
250+
}
251+
},
252+
logger: () => {
253+
loggerRan = true
254+
},
255+
statusError: 500
256+
})
257+
server.listen(8000)
258+
259+
const res = await fetch('http://localhost:8000/health')
260+
expect(res.status).to.eql(500)
261+
expect(onHealthCheckRan).to.eql(true)
262+
expect(loggerRan).to.eql(true)
263+
})
264+
265+
it('returns custom status code on reject', async () => {
266+
let onHealthCheckRan = false
267+
let loggerRan = false
268+
269+
createTerminus(server, {
270+
healthChecks: {
271+
'/health': () => {
272+
onHealthCheckRan = true
273+
const error = new Error()
274+
error.statusCode = 500
275+
return Promise.reject(error)
276+
}
277+
},
278+
logger: () => {
279+
loggerRan = true
280+
}
281+
})
282+
server.listen(8000)
283+
284+
const res = await fetch('http://localhost:8000/health')
285+
expect(res.status).to.eql(500)
286+
expect(onHealthCheckRan).to.eql(true)
287+
expect(loggerRan).to.eql(true)
288+
})
289+
290+
it('returns custom status code on reject (prevailing over global custom status code)', async () => {
291+
let onHealthCheckRan = false
292+
let loggerRan = false
293+
294+
createTerminus(server, {
295+
healthChecks: {
296+
'/health': () => {
297+
onHealthCheckRan = true
298+
const error = new Error()
299+
error.statusCode = 500
300+
return Promise.reject(error)
301+
}
302+
},
303+
logger: () => {
304+
loggerRan = true
305+
},
306+
statusError: 501
307+
})
308+
server.listen(8000)
309+
310+
const res = await fetch('http://localhost:8000/health')
311+
expect(res.status).to.eql(500)
312+
expect(onHealthCheckRan).to.eql(true)
313+
expect(loggerRan).to.eql(true)
314+
})
315+
219316
it('exposes internal state (isShuttingDown: false) to health check', async () => {
220317
let onHealthCheckRan = false
221318
let exposedState

typings/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ declare module "@godaddy/terminus" {
1919
signal?: string;
2020
signals?: string[];
2121
sendFailuresDuringShutdown?: boolean;
22+
statusOk?: number,
23+
statusError?: number,
2224
onSignal?: () => Promise<any>;
2325
onSendFailureDuringShutdown?: () => Promise<any>;
2426
onShutdown?: () => Promise<any>;

0 commit comments

Comments
 (0)