Skip to content

Commit

Permalink
feat(status): Custom status response (#192)
Browse files Browse the repository at this point in the history
* Added statusOkResponse to configuration

The new configuration property: statusOkResponse will allow to globally configure the HTTP response body, used to respond with on a successful healthcheck.

* Memoize the value of statusOkResponse

When no healthtchecks were defined, the value of statusOkResponse would be returned as is. Therefore, memoizing its string representation would boost performance.

* Added statusErrorResponse to configuration

The new configuration property: statusErrorResponse will allow to globally configure the HTTP response body, used to respond with on an unsuccessful healthcheck.
An unsuccessful healthcheck may override the global statusErrorResponse property by setting the statusResponse property on the Error object.

* Incorporated use of headers
  • Loading branch information
sindilevich authored and rxmarbles committed Apr 16, 2023
1 parent c02925b commit f61009a
Show file tree
Hide file tree
Showing 6 changed files with 464 additions and 22 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ const options = {
caseInsensitive, // [optional] whether given health checks routes are case insensitive (defaults to false)

statusOk, // [optional = 200] status to be returned for successful healthchecks
statusOkResponse, // [optional = { status: 'ok' }] status response to be returned for successful healthchecks
statusError, // [optional = 503] status to be returned for unsuccessful healthchecks
statusErrorResponse, // [optional = { status: 'error' }] status response to be returned for unsuccessful healthchecks

// cleanup options
timeout: 1000, // [optional = 1000] number of milliseconds before forceful exiting
Expand Down
23 changes: 23 additions & 0 deletions lib/standalone-tests/terminus.onsignal.fail.custom.response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'
const http = require('http')
const server = http.createServer((req, res) => res.end('hello'))

const { createTerminus } = require('../../')
const SIGNAL = 'SIGINT'

createTerminus(server, {
healthChecks: {
'/health': () => Promise.resolve()
},
signal: SIGNAL,
beforeShutdown: () => {
return new Promise((resolve) => {
setTimeout(resolve, 1000)
})
},
statusErrorResponse: { status: 'down' }
})

server.listen(8000, () => {
process.kill(process.pid, SIGNAL)
})
23 changes: 23 additions & 0 deletions lib/standalone-tests/terminus.onsignal.fail.custom.status.code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'
const http = require('http')
const server = http.createServer((req, res) => res.end('hello'))

const { createTerminus } = require('../../')
const SIGNAL = 'SIGINT'

createTerminus(server, {
healthChecks: {
'/health': () => Promise.resolve()
},
signal: SIGNAL,
beforeShutdown: () => {
return new Promise((resolve) => {
setTimeout(resolve, 1000)
})
},
statusError: 501
})

server.listen(8000, () => {
process.kill(process.pid, SIGNAL)
})
54 changes: 32 additions & 22 deletions lib/terminus.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,23 @@
const stoppable = require('stoppable')
const { promisify } = require('util')

const SUCCESS_RESPONSE = JSON.stringify({
status: 'ok'
})
let SUCCESS_RESPONSE

const FAILURE_RESPONSE = JSON.stringify({
status: 'error'
})
let FAILURE_RESPONSE

function noopResolves () {
return Promise.resolve()
}

async function sendSuccess (res, { info, verbatim, statusOk, headers }) {
async function sendSuccess (res, { info, verbatim, statusOk, statusOkResponse, headers }) {
res.statusCode = statusOk
res.setHeader('Content-Type', 'application/json')
res.writeHead(statusOk, headers)
if (info) {
return res.end(
JSON.stringify(
Object.assign(
{
status: 'ok'
},
statusOkResponse,
verbatim ? info : { info, details: info }
)
)
Expand All @@ -34,7 +29,7 @@ async function sendSuccess (res, { info, verbatim, statusOk, headers }) {
}

async function sendFailure (res, options) {
const { error, headers, onSendFailureDuringShutdown, exposeStackTraces, statusCode, statusError } = options
const { error, headers, onSendFailureDuringShutdown, exposeStackTraces, statusCode, statusResponse, statusError, statusErrorResponse } = options

function replaceErrors (_, value) {
if (value instanceof Error) {
Expand All @@ -54,16 +49,19 @@ async function sendFailure (res, options) {
if (onSendFailureDuringShutdown) {
await onSendFailureDuringShutdown()
}
res.statusCode = statusCode || statusError
const responseBody = statusResponse || statusErrorResponse
res.setHeader('Content-Type', 'application/json')
res.writeHead(statusCode || statusError, headers)
if (error) {
return res.end(JSON.stringify({
status: 'error',
error,
details: error
}, replaceErrors))
return res.end(JSON.stringify(
Object.assign(
responseBody, {
error,
details: error
}), replaceErrors))
}
res.end(FAILURE_RESPONSE)
res.end(statusResponse ? JSON.stringify(responseBody) : FAILURE_RESPONSE)
}

const intialState = {
Expand All @@ -73,35 +71,39 @@ const intialState = {
function noop () {}

function decorateWithHealthCheck (server, state, options) {
const { healthChecks, logger, headers, onSendFailureDuringShutdown, sendFailuresDuringShutdown, caseInsensitive, statusOk, statusError } = options
const { healthChecks, logger, headers, onSendFailureDuringShutdown, sendFailuresDuringShutdown, caseInsensitive, statusOk, statusOkResponse, statusError, statusErrorResponse } = options

let hasSetHandler = false
const createHandler = (listener) => {
const check = hasSetHandler
? () => {}
: async (healthCheck, res) => {
if (state.isShuttingDown && sendFailuresDuringShutdown) {
return sendFailure(res, { onSendFailureDuringShutdown, statusError })
return sendFailure(res, { onSendFailureDuringShutdown, statusError, statusErrorResponse })
}
let info
try {
info = await healthCheck({ state })
} catch (error) {
logger('healthcheck failed', error)
const statusCode = error.statusCode
const statusResponse = error.statusResponse
return sendFailure(
res,
{
error: error.causes,
headers,
exposeStackTraces: healthChecks.__unsafeExposeStackTraces,
statusCode: error.statusCode,
statusError
statusCode,
statusResponse,
statusError,
statusErrorResponse
}
)
}
return sendSuccess(
res,
{ info, verbatim: healthChecks.verbatim, statusOk, headers }
{ info, verbatim: healthChecks.verbatim, statusOk, statusOkResponse, headers }
)
}

Expand Down Expand Up @@ -184,12 +186,18 @@ function terminus (server, options = {}) {
logger = noop,
caseInsensitive = false,
statusOk = 200,
statusOkResponse = { status: 'ok' },
statusError = 503,
statusErrorResponse = { status: 'error' },
headers = options.headers || {}
} = options
const onSignal = options.onSignal || options.onSigterm || noopResolves
const state = Object.assign({}, intialState)

SUCCESS_RESPONSE = JSON.stringify(statusOkResponse)

FAILURE_RESPONSE = JSON.stringify(statusErrorResponse)

if (Object.keys(healthChecks).length > 0) {
decorateWithHealthCheck(server, state, {
healthChecks,
Expand All @@ -198,7 +206,9 @@ function terminus (server, options = {}) {
onSendFailureDuringShutdown,
caseInsensitive,
statusOk,
statusOkResponse,
statusError,
statusErrorResponse,
headers
})
}
Expand Down
Loading

0 comments on commit f61009a

Please sign in to comment.