@godaddy/terminus
Advanced tools
Comparing version 2.2.0 to 2.2.1
@@ -1,19 +0,17 @@ | ||
const express = require('express'); | ||
const http = require('http'); | ||
const terminus = require('../lib/terminus'); | ||
const app = express(); | ||
const express = require('express') | ||
const http = require('http') | ||
const terminus = require('../lib/terminus') | ||
const app = express() | ||
app.get('/', (req, res) => { | ||
setTimeout(() => { | ||
res.send('ok'); | ||
}, 100000); | ||
}); | ||
res.send('ok') | ||
}, 100000) | ||
}) | ||
function onHealthCheck() { | ||
return Promise.resolve(); | ||
} | ||
terminus(http.createServer(app), { | ||
logger: console.log, | ||
onHealthCheck | ||
}).listen(3000); | ||
healthChecks: { | ||
'/healthz': () => Promise.resolve() | ||
} | ||
}).listen(3000) |
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
module.exports = require('./lib/terminus'); | ||
'use strict' | ||
module.exports = require('./lib/terminus') |
@@ -1,7 +0,7 @@ | ||
'use strict'; | ||
const http = require('http'); | ||
const server = http.createServer((req, res) => res.end('hello')); | ||
'use strict' | ||
const http = require('http') | ||
const server = http.createServer((req, res) => res.end('hello')) | ||
const terminus = require('../terminus'); | ||
const SIGNAL = 'SIGINT'; | ||
const terminus = require('../terminus') | ||
const SIGNAL = 'SIGINT' | ||
@@ -11,12 +11,12 @@ terminus(server, { | ||
onSignal: () => { | ||
console.log('on-sigint-runs'); | ||
return Promise.resolve(); | ||
console.log('on-sigint-runs') | ||
return Promise.resolve() | ||
}, | ||
onShutdown: () => { | ||
console.log('on-shutdown-runs'); | ||
console.log('on-shutdown-runs') | ||
} | ||
}); | ||
}) | ||
server.listen(8000, () => { | ||
process.kill(process.pid, SIGNAL); | ||
}); | ||
process.kill(process.pid, SIGNAL) | ||
}) |
@@ -1,19 +0,19 @@ | ||
'use strict'; | ||
const http = require('http'); | ||
const server = http.createServer((req, res) => res.end('hello')); | ||
'use strict' | ||
const http = require('http') | ||
const server = http.createServer((req, res) => res.end('hello')) | ||
const terminus = require('../terminus'); | ||
const terminus = require('../terminus') | ||
terminus(server, { | ||
onSignal: () => { | ||
console.log('on-sigterm-runs'); | ||
return Promise.resolve(); | ||
console.log('on-sigterm-runs') | ||
return Promise.resolve() | ||
}, | ||
onShutdown: () => { | ||
console.log('on-shutdown-runs'); | ||
console.log('on-shutdown-runs') | ||
} | ||
}); | ||
}) | ||
server.listen(8000, () => { | ||
process.kill(process.pid, 'SIGTERM'); | ||
}); | ||
process.kill(process.pid, 'SIGTERM') | ||
}) |
@@ -1,7 +0,7 @@ | ||
'use strict'; | ||
const http = require('http'); | ||
const server = http.createServer((req, res) => res.end('hello')); | ||
'use strict' | ||
const http = require('http') | ||
const server = http.createServer((req, res) => res.end('hello')) | ||
const terminus = require('../terminus'); | ||
const SIGNAL = 'SIGINT'; | ||
const terminus = require('../terminus') | ||
const SIGNAL = 'SIGINT' | ||
@@ -11,9 +11,9 @@ terminus(server, { | ||
onSignal: () => { | ||
console.log('on-sigint-runs'); | ||
return Promise.resolve(); | ||
console.log('on-sigint-runs') | ||
return Promise.resolve() | ||
} | ||
}); | ||
}) | ||
server.listen(8000, () => { | ||
process.kill(process.pid, SIGNAL); | ||
}); | ||
process.kill(process.pid, SIGNAL) | ||
}) |
@@ -1,16 +0,16 @@ | ||
'use strict'; | ||
const http = require('http'); | ||
const server = http.createServer((req, res) => res.end('hello')); | ||
'use strict' | ||
const http = require('http') | ||
const server = http.createServer((req, res) => res.end('hello')) | ||
const terminus = require('../terminus'); | ||
const terminus = require('../terminus') | ||
terminus(server, { | ||
onSignal: () => { | ||
console.log('on-sigterm-runs'); | ||
return Promise.resolve(); | ||
console.log('on-sigterm-runs') | ||
return Promise.resolve() | ||
} | ||
}); | ||
}) | ||
server.listen(8000, () => { | ||
process.kill(process.pid, 'SIGTERM'); | ||
}); | ||
process.kill(process.pid, 'SIGTERM') | ||
}) |
@@ -1,92 +0,113 @@ | ||
'use strict'; | ||
const stoppable = require('stoppable'); | ||
const promisify = require('es6-promisify'); | ||
'use strict' | ||
const stoppable = require('stoppable') | ||
const { promisify } = require('es6-promisify') | ||
const SUCCESS_RESPONSE = JSON.stringify({ | ||
status: 'ok' | ||
}); | ||
}) | ||
const FAILURE_RESPONSE = JSON.stringify({ | ||
status: 'error' | ||
}); | ||
}) | ||
function noopResolves() { | ||
return Promise.resolve(); | ||
function noopResolves () { | ||
return Promise.resolve() | ||
} | ||
function noop() {} | ||
function sendSuccess (res) { | ||
res.statusCode = 200 | ||
res.end(SUCCESS_RESPONSE) | ||
} | ||
function decorateWithHealthCheck(server, options) { | ||
const { healthChecks, logger } = options; | ||
function sendFailure (res) { | ||
res.statusCode = 503 | ||
res.end(FAILURE_RESPONSE) | ||
} | ||
const state = { | ||
isShuttingDown: false | ||
} | ||
function noop () {} | ||
function decorateWithHealthCheck (server, options) { | ||
const { healthChecks, logger } = options | ||
server.listeners('request').forEach((listener) => { | ||
server.removeListener('request', listener); | ||
server.removeListener('request', listener) | ||
server.on('request', (req, res) => { | ||
if (healthChecks[req.url]) { | ||
if (state.isShuttingDown) { | ||
return sendFailure(res) | ||
} | ||
healthChecks[req.url]() | ||
.then(() => { | ||
res.statusCode = 200; | ||
res.end(SUCCESS_RESPONSE); | ||
sendSuccess(res) | ||
}) | ||
.catch((error) => { | ||
logger('healthcheck failed', error); | ||
res.statusCode = 503; | ||
res.end(FAILURE_RESPONSE); | ||
}); | ||
logger('healthcheck failed', error) | ||
sendFailure(res) | ||
}) | ||
} else { | ||
listener(req, res); | ||
listener(req, res) | ||
} | ||
}); | ||
}); | ||
}) | ||
}) | ||
} | ||
function decorateWithSignalHandler(server, options) { | ||
const { signal, onSignal, onShutdown, timeout, logger } = options; | ||
function decorateWithSignalHandler (server, options) { | ||
const { signal, onSignal, beforeShutdown, onShutdown, timeout, logger } = options | ||
stoppable(server, timeout); | ||
stoppable(server, timeout) | ||
const asyncServerStop = promisify(server.stop).bind(server); | ||
const asyncServerStop = promisify(server.stop).bind(server) | ||
function cleanup() { | ||
asyncServerStop() | ||
function cleanup () { | ||
state.isShuttingDown = true | ||
beforeShutdown() | ||
.then(() => asyncServerStop()) | ||
.then(() => onSignal()) | ||
.then(() => onShutdown()) | ||
.then(() => { | ||
process.removeListener(signal, cleanup); | ||
process.kill(process.pid, signal); | ||
process.removeListener(signal, cleanup) | ||
process.kill(process.pid, signal) | ||
}) | ||
.catch((error) => { | ||
logger('error happened during shutdown', error); | ||
process.exit(1); | ||
}); | ||
logger('error happened during shutdown', error) | ||
process.exit(1) | ||
}) | ||
} | ||
process.on(signal, cleanup); | ||
process.on(signal, cleanup) | ||
} | ||
function terminus(server, options = {}) { | ||
const healthChecks = options.healthChecks || {}; | ||
function terminus (server, options = {}) { | ||
const { signal='SIGTERM', | ||
timeout=1000, | ||
healthChecks={}, | ||
onShutdown=noopResolves, | ||
beforeShutdown=noopResolves, | ||
logger=noop } = options | ||
const onSignal = options.onSignal || options.onSigterm || noopResolves | ||
const signal = options.signal || 'SIGTERM'; | ||
const timeout = options.timeout || 1000; | ||
const onSignal = options.onSignal || options.onSigterm || noopResolves; | ||
const onShutdown = options.onShutdown || noopResolves; | ||
if (Object.keys(healthChecks).length > 0) { | ||
decorateWithHealthCheck(server, { | ||
healthChecks, | ||
logger | ||
}) | ||
} | ||
const logger = options.logger || noop; | ||
decorateWithHealthCheck(server, { | ||
healthChecks, | ||
logger | ||
}); | ||
decorateWithSignalHandler(server, { | ||
signal, | ||
onSignal, | ||
beforeShutdown, | ||
onShutdown, | ||
timeout, | ||
logger | ||
}); | ||
}) | ||
return server; | ||
return server | ||
} | ||
module.exports = terminus; | ||
module.exports = terminus |
@@ -1,25 +0,25 @@ | ||
'use strict'; | ||
const http = require('http'); | ||
const { execFileSync } = require('child_process'); | ||
'use strict' | ||
const http = require('http') | ||
const { execFile, execFileSync } = require('child_process') | ||
const expect = require('chai').expect; | ||
const fetch = require('node-fetch'); | ||
const expect = require('chai').expect | ||
const fetch = require('node-fetch') | ||
const terminus = require('./terminus'); | ||
const terminus = require('./terminus') | ||
describe('Terminus', () => { | ||
let server; | ||
let server | ||
beforeEach(() => { | ||
server = http.createServer((req, res) => res.end('hello')); | ||
}); | ||
server = http.createServer((req, res) => res.end('hello')) | ||
}) | ||
afterEach(() => { | ||
server.close(); | ||
}); | ||
server.close() | ||
}) | ||
describe('supports onHealthcheck for the healthcheck route', () => { | ||
it('but keeps all the other endpoints', (done) => { | ||
terminus(server, {}); | ||
server.listen(8000); | ||
terminus(server, {}) | ||
server.listen(8000) | ||
@@ -29,10 +29,10 @@ fetch('http://localhost:8000') | ||
.then(responseText => { | ||
expect(responseText).to.eql('hello'); | ||
done(); | ||
expect(responseText).to.eql('hello') | ||
done() | ||
}) | ||
.catch(done); | ||
}); | ||
.catch(done) | ||
}) | ||
it('returns 200 on resolve', (done) => { | ||
let onHealthCheckRan = false; | ||
let onHealthCheckRan = false | ||
@@ -42,21 +42,21 @@ terminus(server, { | ||
'/health': () => { | ||
onHealthCheckRan = true; | ||
return Promise.resolve(); | ||
onHealthCheckRan = true | ||
return Promise.resolve() | ||
} | ||
} | ||
}); | ||
server.listen(8000); | ||
}) | ||
server.listen(8000) | ||
fetch('http://localhost:8000/health') | ||
.then(res => { | ||
expect(res.status).to.eql(200); | ||
expect(onHealthCheckRan).to.eql(true); | ||
done(); | ||
expect(res.status).to.eql(200) | ||
expect(onHealthCheckRan).to.eql(true) | ||
done() | ||
}) | ||
.catch(done); | ||
}); | ||
.catch(done) | ||
}) | ||
it('returns 503 on reject', (done) => { | ||
let onHealthCheckRan = false; | ||
let loggerRan = false; | ||
let onHealthCheckRan = false | ||
let loggerRan = false | ||
@@ -66,66 +66,80 @@ terminus(server, { | ||
'/health': () => { | ||
onHealthCheckRan = true; | ||
return Promise.reject(new Error('failed')); | ||
onHealthCheckRan = true | ||
return Promise.reject(new Error('failed')) | ||
} | ||
}, | ||
logger: () => { | ||
loggerRan = true; | ||
loggerRan = true | ||
} | ||
}); | ||
server.listen(8000); | ||
}) | ||
server.listen(8000) | ||
fetch('http://localhost:8000/health') | ||
.then(res => { | ||
expect(res.status).to.eql(503); | ||
expect(onHealthCheckRan).to.eql(true); | ||
expect(loggerRan).to.eql(true); | ||
done(); | ||
expect(res.status).to.eql(503) | ||
expect(onHealthCheckRan).to.eql(true) | ||
expect(loggerRan).to.eql(true) | ||
done() | ||
}) | ||
.catch(done); | ||
}); | ||
}); | ||
.catch(done) | ||
}) | ||
it('returns 503 once signal received', (done) => { | ||
execFile('node', ['lib/standalone-tests/terminus.onsignal.fail.js']) | ||
// let the process start up | ||
setTimeout(() => { | ||
fetch('http://localhost:8000/health') | ||
.then(res => { | ||
expect(res.status).to.eql(503) | ||
done() | ||
}) | ||
.catch(done) | ||
}, 300) | ||
}) | ||
}) | ||
it('runs onSigterm when getting the SIGTERM signal', () => { | ||
try { | ||
execFileSync('node', ['lib/standalone-tests/terminus.onsigterm.js']); | ||
execFileSync('node', ['lib/standalone-tests/terminus.onsigterm.js']) | ||
} catch (ex) { | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigterm-runs'); | ||
return; | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigterm-runs') | ||
return | ||
} | ||
throw new Error('running the test should throw, as the exitcode is not 0'); | ||
}); | ||
throw new Error('running the test should throw, as the exitcode is not 0') | ||
}) | ||
it('runs onShutdown after onSigterm', () => { | ||
try { | ||
execFileSync('node', ['lib/standalone-tests/terminus.onshutdown.sigterm.js']); | ||
execFileSync('node', ['lib/standalone-tests/terminus.onshutdown.sigterm.js']) | ||
} catch (ex) { | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigterm-runs\non-shutdown-runs'); | ||
return; | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigterm-runs\non-shutdown-runs') | ||
return | ||
} | ||
throw new Error('running the test should throw, as the exitcode is not 0'); | ||
}); | ||
throw new Error('running the test should throw, as the exitcode is not 0') | ||
}) | ||
it('runs onSigint when getting SIGINT signal', () => { | ||
try { | ||
execFileSync('node', ['lib/standalone-tests/terminus.onsigint.js']); | ||
execFileSync('node', ['lib/standalone-tests/terminus.onsigint.js']) | ||
} catch (ex) { | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigint-runs'); | ||
return; | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigint-runs') | ||
return | ||
} | ||
throw new Error('running the test should throw, as the exitcode is not 0'); | ||
}); | ||
throw new Error('running the test should throw, as the exitcode is not 0') | ||
}) | ||
it('runs onShutdown after onSigint', () => { | ||
try { | ||
execFileSync('node', ['lib/standalone-tests/terminus.onshutdown.sigint.js']); | ||
execFileSync('node', ['lib/standalone-tests/terminus.onshutdown.sigint.js']) | ||
} catch (ex) { | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigint-runs\non-shutdown-runs'); | ||
return; | ||
expect(ex.stdout.toString().trim()).to.eql('on-sigint-runs\non-shutdown-runs') | ||
return | ||
} | ||
throw new Error('running the test should throw, as the exitcode is not 0'); | ||
}); | ||
}); | ||
throw new Error('running the test should throw, as the exitcode is not 0') | ||
}) | ||
}) |
@@ -1,1 +0,42 @@ | ||
{"name":"@godaddy/terminus","version":"2.2.0","description":"","main":"index.js","types":"./typings/index.d.ts","scripts":{"test-typings":"tsc --lib ES2015 --noEmit typings/*.ts","test":"mocha lib/**/*.spec.js && npm run test-typings","eslint":"eslint-godaddy -c .eslintrc lib example index.js","coverage":"nyc mocha lib/**/*.spec.js","publish":"npm run eslint && npm test","semantic-release":"semantic-release pre && npm publish && semantic-release post"},"repository":{"type":"git","url":"https://github.com/godaddy/terminus.git"},"keywords":[],"author":"","license":"MIT","dependencies":{"es6-promisify":"^5.0.0","stoppable":"^1.0.5"},"devDependencies":{"@types/express":"^4.0.39","@types/koa":"^2.0.42","@types/node":"^8.0.58","chai":"^4.1.2","eslint":"^4.4.1","eslint-config-godaddy":"^2.0.0","eslint-plugin-json":"^1.2.0","eslint-plugin-mocha":"^4.11.0","express":"^4.16.2","mocha":"^4.0.1","node-fetch":"^1.7.3","nyc":"^11.3.0","semantic-release":"^8.2.0","supertest":"^3.0.0","typescript":"^2.6.2"}} | ||
{ | ||
"name": "@godaddy/terminus", | ||
"version": "2.2.1", | ||
"description": "", | ||
"main": "index.js", | ||
"types": "./typings/index.d.ts", | ||
"scripts": { | ||
"test-typings": "tsc --lib ES2015 --noEmit typings/*.ts", | ||
"test": "mocha lib/**/*.spec.js && npm run test-typings", | ||
"lint": "standard --fix --env mocha", | ||
"coverage": "nyc mocha lib/**/*.spec.js", | ||
"publish": "npm run lint && npm test", | ||
"semantic-release": "semantic-release", | ||
"travis-deploy-once": "travis-deploy-once" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/godaddy/terminus.git" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "MIT", | ||
"dependencies": { | ||
"es6-promisify": "^6.0.0", | ||
"stoppable": "^1.0.5" | ||
}, | ||
"devDependencies": { | ||
"@types/express": "^4.0.39", | ||
"@types/koa": "^2.0.42", | ||
"@types/node": "^10.0.7", | ||
"chai": "^4.1.2", | ||
"express": "^4.16.2", | ||
"mocha": "^5.0.0", | ||
"node-fetch": "^2.0.0", | ||
"nyc": "^11.3.0", | ||
"semantic-release": "^15.5.1", | ||
"standard": "^11.0.1", | ||
"supertest": "^3.0.0", | ||
"typescript": "^2.6.2", | ||
"travis-deploy-once": "^5.0.0" | ||
} | ||
} |
[![Build Status](https://travis-ci.org/godaddy/terminus.svg?branch=master)](https://travis-ci.org/godaddy/terminus) | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/godaddy/terminus.svg)](https://greenkeeper.io/) | ||
# terminus | ||
@@ -7,2 +9,10 @@ | ||
## Installation | ||
Install via npm: | ||
```console | ||
$ npm i @godaddy/terminus --save | ||
``` | ||
## Usage | ||
@@ -14,3 +24,3 @@ | ||
function onSigterm () { | ||
function onSignal () { | ||
console.log('server is starting cleanup'); | ||
@@ -27,6 +37,12 @@ return Promise.all([ | ||
const server = http.createServer((request, response) => { | ||
response.end('<html><body><h1>Hello, World!</h1></body></html>'); | ||
response.end( | ||
`<html> | ||
<body> | ||
<h1>Hello, World!</h1> | ||
</body> | ||
</html>` | ||
); | ||
}) | ||
terminus(server, { | ||
const options = { | ||
// healtcheck options | ||
@@ -38,4 +54,5 @@ healthChecks: { | ||
// cleanup options | ||
timeout: 1000, // [optional = 5000] number of milliseconds before forcefull exiting | ||
timeout: 1000, // [optional = 1000] number of milliseconds before forcefull exiting | ||
signal, // [optional = 'SIGTERM'] what signal to listen for relative to shutdown | ||
beforeShutdown, // [optional] called before the HTTP server starts its shutdown | ||
onSignal, // [optional] cleanup function, returning a promise (used to be onSigterm) | ||
@@ -45,6 +62,8 @@ onShutdown, // [optional] called right before exiting | ||
// both | ||
logger // [optional] logger function to be called with errors | ||
}); | ||
logger // [optional] logger function to be called with errors | ||
}; | ||
server.listen(PORT); | ||
terminus(server, options); | ||
server.listen(PORT || 3000); | ||
``` | ||
@@ -64,7 +83,9 @@ | ||
terminus(server, { | ||
const options = { | ||
// opts | ||
}); | ||
}; | ||
server.listen(3000); | ||
terminus(server, options); | ||
server.listen(PORT || 3000); | ||
``` | ||
@@ -81,7 +102,17 @@ | ||
terminus(server, { | ||
//opts | ||
}); | ||
const options = { | ||
// opts | ||
}; | ||
server.listen(3000); | ||
terminus(server, options); | ||
server.listen(PORT || 3000); | ||
``` | ||
## Limited Windows support | ||
Due to inherent platform limitations, `terminus` has limited support for Windows. | ||
You can expect `SIGINT` to work, as well as `SIGBREAK` and to some extent `SIGHUP`. | ||
However `SIGTERM` will never work on Windows because killing a process in the task manager is unconditional, i.e., there's no way for an application to detect or prevent it. | ||
Here's some relevant documentation from [`libuv`](https://github.com/libuv/libuv) to learn more about what `SIGINT`, `SIGBREAK` etc. signify and what's supported on Windows - http://docs.libuv.org/en/v1.x/signal.html. | ||
Also see https://nodejs.org/api/process.html#process_signal_events. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23361
13
34
578
112
1
18
+ Addedes6-promisify@6.1.1(transitive)
- Removedes6-promise@4.2.8(transitive)
- Removedes6-promisify@5.0.0(transitive)
Updatedes6-promisify@^6.0.0