healthchecks
Advanced tools
Comparing version 1.6.1 to 1.7.0
@@ -0,1 +1,12 @@ | ||
## Version 1.7.0 2015-12-09 | ||
FIXED always run checks against `localAddress` and `localPort` | ||
FIXED checking subdomains works again | ||
CHANGED dropped short-lived support for SSL | ||
CHANGED follow up to 10 redirects | ||
## Version 1.6.1 2015-11-09 | ||
@@ -2,0 +13,0 @@ |
@@ -23,3 +23,3 @@ // Exports an Express middleware factory. | ||
const Handlebars = require('handlebars'); | ||
const Request = require('request'); | ||
const HTTP = require('http'); | ||
const ms = require('ms'); | ||
@@ -111,2 +111,54 @@ const Path = require('path'); | ||
// Makes a request to the given relative URL by crafting the request | ||
// using the resolver function. | ||
// Redirects will be resolved using the resolver function as well. | ||
function get(url, headers, resolver, timeout) { | ||
const loopbackURL = resolver(url); | ||
const request = { | ||
host: loopbackURL.host, | ||
port: loopbackURL.port, | ||
path: loopbackURL.path, | ||
headers: Object.assign({}, headers, { | ||
'Host': loopbackURL.hostname | ||
}) | ||
}; | ||
return new Promise(function(resolve, reject) { | ||
const start = process.hrtime(); | ||
HTTP.get(request) | ||
.on('error', function(error) { | ||
const elapsed = process.hrtime(start); | ||
reject({ error: error, elapsed: elapsed }); | ||
}) | ||
.on('response', function(response) { | ||
const elapsed = process.hrtime(start); | ||
if (response.statusCode < 200 || response.statusCode >= 400) | ||
resolve({ statusCode: response.statusCode, body: null, elapsed: elapsed }); | ||
else if (response.statusCode > 300 && response.statusCode < 400) { | ||
get(response.headers.location, headers, resolver, timeout) | ||
.then(resolve, reject); | ||
} | ||
else { | ||
const buffers = []; | ||
response.on('data', function(buffer) { | ||
buffers.push(buffer); | ||
}); | ||
response.on('end', function() { | ||
const body = Buffer.concat(buffers).toString(); | ||
resolve({ statusCode: response.statusCode, body: body, elapsed: elapsed }); | ||
}); | ||
} | ||
}); | ||
setTimeout(function() { | ||
const elapsed = process.hrtime(start); | ||
const error = new Error('ETIMEDOUT'); | ||
error.code = 'ETIMEDOUT'; | ||
reject({ error: error, elapsed: elapsed }); | ||
}, timeout); | ||
}); | ||
} | ||
// The check function will run all checks in parallel, and resolve to an object | ||
@@ -120,52 +172,34 @@ // with the properties: | ||
// Given a relative URL in string form, returns a parsed URL | ||
// which points to the local server's IP address and port | ||
// and has the right hostname property to use in the Host header. | ||
function loopbackResolve(relativeURL) { | ||
const absoluteURL = URL.parse(URL.resolve(protocol + '://localhost/', relativeURL)); | ||
const loopbackURL = Object.assign({}, absoluteURL, { | ||
hostname: absoluteURL.hostname, | ||
host: hostname, | ||
port: port | ||
}); | ||
return loopbackURL; | ||
} | ||
// Each check resolves into an outcome object | ||
const allChecks = Object.keys(checks).map(function(checkURL) { | ||
const expected = checks[checkURL]; | ||
return new Promise(function(resolve) { | ||
// We need to make an HTTP/S request to the current server, based on the hostname/port passed to us, | ||
// We need to make an HTTP/S request to the current server, | ||
// based on the hostname/port passed to us, | ||
// so the HTTP check would go to http://localhost:80/ or some such URL. | ||
// Checks have relative URLs, resolve them to absolute URLs | ||
if(typeof(port) === 'string') | ||
hostname = hostname + ":" + port; | ||
const baseURL = protocol + "://" + hostname + '/'; | ||
const absCheckURL = URL.resolve(baseURL, checkURL); | ||
const reqOpts = { | ||
url: absCheckURL, | ||
strictSSL: strictSSL, | ||
headers: { | ||
'Host': hostname, | ||
'User-Agent': 'Mozilla/5.0 (compatible) Healthchecks http://broadly.com', | ||
'X-Request-Id': requestID || '' | ||
} | ||
const headers = { | ||
'User-Agent': 'Mozilla/5.0 (compatible) Healthchecks http://broadly.com', | ||
'X-Request-Id': requestID || '' | ||
}; | ||
const start = process.hrtime(); | ||
Request.get(reqOpts) | ||
.on('error', function(error) { | ||
const elapsed = process.hrtime(start); | ||
resolve(new Outcome(checkURL, expected, error, null, null, elapsed)); | ||
get(checkURL, headers, loopbackResolve, timeout) | ||
.then(function(res) { | ||
resolve(new Outcome(checkURL, expected, null, res.statusCode, res.body, res.elapsed)); | ||
}) | ||
.on('response', function(response) { | ||
const elapsed = process.hrtime(start); | ||
if (response.statusCode < 200 || response.statusCode >= 400) | ||
resolve(new Outcome(checkURL, expected, null, response.statusCode, null, elapsed)); | ||
else { | ||
const buffers = []; | ||
response.on('data', function(buffer) { | ||
buffers.push(buffer); | ||
}); | ||
response.on('end', function() { | ||
const body = Buffer.concat(buffers).toString(); | ||
resolve(new Outcome(checkURL, expected, null, response.statusCode, body, elapsed)); | ||
}); | ||
} | ||
.catch(function(error) { | ||
resolve(new Outcome(checkURL, expected, error.error, null, null, error.elapsed)); | ||
}); | ||
setTimeout(function() { | ||
const elapsed = process.hrtime(start); | ||
const error = new Error('ETIMEDOUT'); | ||
error.code = 'ETIMEDOUT'; | ||
resolve(new Outcome(checkURL, expected, error, null, null, elapsed)); | ||
}, timeout); | ||
}); | ||
@@ -284,5 +318,4 @@ }); | ||
const protocol = req.socket.encrypted ? 'https' : 'http'; | ||
const fullHost = req.header("Host").split(':'); | ||
const hostname = fullHost[0]; | ||
const port = fullHost[1]; | ||
const hostname = req.socket.localAddress; | ||
const port = req.socket.localPort; | ||
@@ -289,0 +322,0 @@ // Run all checks |
{ | ||
"name": "healthchecks", | ||
"version": "1.6.1", | ||
"version": "1.7.0", | ||
"engines": { | ||
@@ -15,4 +15,3 @@ "node": ">=4.0.0" | ||
"ms": "^0.7.1", | ||
"pretty-hrtime": "^1.0.0", | ||
"request": "^2.0" | ||
"pretty-hrtime": "^1.0.0" | ||
}, | ||
@@ -23,2 +22,3 @@ "devDependencies": { | ||
"mocha": "^2.2", | ||
"request": "^2.67.0", | ||
"zombie": "^4.0" | ||
@@ -25,0 +25,0 @@ }, |
@@ -109,6 +109,5 @@ # Healthchecks | ||
`filename` -- The name of the checks file | ||
`onFailed` -- Called with array of failed checks | ||
`timeout` -- Timeout slow responses | ||
`strictSSL` -- Defaults to true, false allows use of self-signed certificates for development | ||
* `filename` -- The name of the checks file | ||
* `onFailed` -- Called with array of failed checks | ||
* `timeout` -- Timeout slow responses | ||
@@ -121,12 +120,12 @@ You can specify the timeout in milliseconds or as a string, e.g. "3s" for 3 | ||
`url` -- The absolute URL | ||
`reason` -- One of 'error', 'timeout', 'statusCode' or 'body' | ||
`error` -- Connection or timeout error | ||
`timeout` -- True if failed due to timeout | ||
`statusCode` -- HTTP status code (if no error) | ||
`body` -- Response body | ||
* `url` -- The absolute URL | ||
* `reason` -- One of `'error'`, `'timeout'`, `'statusCode'` or `'body'` | ||
* `error` -- Connection or timeout error | ||
* `timeout` -- True if failed due to timeout | ||
* `statusCode` -- HTTP status code (if no error) | ||
* `body` -- Response body | ||
For convenience, the value of the `reason` property is the name of one of the | ||
other properties. Also, when you call `toString()` you get a URL with the | ||
reason, e.g. "http://example.com => statusCode". | ||
reason, e.g. `"http://example.com => statusCode"`. | ||
@@ -139,3 +138,2 @@ For example: | ||
timeout: '5s', // 5 seconds, can also pass duration in milliseconds | ||
strictSSL: (process.env.NODE_ENV === 'production') ? true : false, | ||
onFailed: function(checks) { | ||
@@ -142,0 +140,0 @@ checks.forEach(function(check) { |
@@ -70,1 +70,12 @@ const express = require('express'); | ||
}); | ||
// Test subdomains | ||
server.locals.subdomain = 'admin'; | ||
server.get('/subdomain', function(req, res) { | ||
const subdomain = req.headers.host.split('.')[0]; | ||
if (subdomain === server.locals.subdomain) | ||
res.status(200).send(''); | ||
else | ||
res.status(404).send(''); | ||
}); |
@@ -153,4 +153,30 @@ const assert = require('assert'); | ||
describe('subdomain not accessible', function() { | ||
before(function() { | ||
server.locals.subdomain = ''; | ||
}); | ||
it('should be notified of a failed test', function(done) { | ||
server.once('failed', function(failed) { | ||
assert.equal(failed[0].url, '//admin.localhost/subdomain'); | ||
assert.equal(failed[0].reason, 'statusCode'); | ||
assert(!failed[0].error); | ||
assert(!failed[0].timeout); | ||
assert.equal(failed[0].statusCode, 404); | ||
assert.equal(failed[0].toString(), '//admin.localhost/subdomain => 404'); | ||
done(); | ||
}); | ||
request(checksURL); | ||
}); | ||
after(function() { | ||
server.locals.subdomain = 'admin'; | ||
}); | ||
}); | ||
}); | ||
@@ -102,2 +102,20 @@ const assert = require('assert'); | ||
describe('subdomain not accessible', function() { | ||
before(function() { | ||
server.locals.subdomain = ''; | ||
}); | ||
it('should receive response with status 500', function(done) { | ||
request(checksURL, function(error, response) { | ||
assert.equal(response.statusCode, 500); | ||
done(); | ||
}); | ||
}); | ||
after(function() { | ||
server.locals.subdomain = 'admin'; | ||
}); | ||
}); | ||
}); |
@@ -22,7 +22,8 @@ const Browser = require('zombie'); | ||
browser.assert.text('h1', 'Passed'); | ||
browser.assert.text('.passed li:nth-of-type(1)', /error \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(2)', /expected \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(3)', /redirect \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(4)', /status \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(5)', /timeout \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(1)', /\/\/admin.localhost\/subdomain \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(2)', /error \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(3)', /expected \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(4)', /redirect \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(5)', /status \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(6)', /timeout \d[.\d]* ms/); | ||
done(); | ||
@@ -42,7 +43,8 @@ }); | ||
browser.assert.text('h1', 'Passed'); | ||
browser.assert.text('.passed li:nth-of-type(1)', /error \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(2)', /expected \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(3)', /redirect \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(4)', /status \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(5)', /timeout \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(1)', /\/\/admin.localhost\/subdomain \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(2)', /error \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(3)', /expected \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(4)', /redirect \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(5)', /status \d[.\d]* ms/); | ||
browser.assert.text('.passed li:nth-of-type(6)', /timeout \d[.\d]* ms/); | ||
done(); | ||
@@ -68,3 +70,3 @@ }); | ||
browser.assert.text('.failed li:nth-of-type(2)', /redirect => socket hang up \d[.\d]* ms/); | ||
browser.assert.elements('.passed li', 3); | ||
browser.assert.elements('.passed li', 4); | ||
done(); | ||
@@ -92,3 +94,3 @@ }); | ||
browser.assert.text('.failed li:nth-of-type(1)', /error => socket hang up \d[.\d]* ms/); | ||
browser.assert.elements('.passed li', 4); | ||
browser.assert.elements('.passed li', 5); | ||
done(); | ||
@@ -116,3 +118,3 @@ }); | ||
browser.assert.text('.failed li:nth-of-type(1)', /timeout => timeout \d[.\d]* s/); | ||
browser.assert.elements('.passed li', 4); | ||
browser.assert.elements('.passed li', 5); | ||
done(); | ||
@@ -138,3 +140,3 @@ }); | ||
browser.assert.text('.failed li:nth-of-type(1)', /status => 400 \d[.\d]* ms/); | ||
browser.assert.elements('.passed li', 4); | ||
browser.assert.elements('.passed li', 5); | ||
done(); | ||
@@ -160,3 +162,3 @@ }); | ||
browser.assert.text('.failed li:nth-of-type(1)', /expected => body \d[.\d]* ms/); | ||
browser.assert.elements('.passed li', 4); | ||
browser.assert.elements('.passed li', 5); | ||
done(); | ||
@@ -172,3 +174,23 @@ }); | ||
describe('subdomain not accessible', function() { | ||
before(function() { | ||
server.locals.subdomain = ''; | ||
}); | ||
it('should see a failed test', function(done) { | ||
browser.visit(checksURL, function() { | ||
browser.assert.text('h1', 'FailedPassed'); | ||
browser.assert.elements('.failed li', 1); | ||
browser.assert.text('.failed li:nth-of-type(1)', /\/\/admin.localhost\/subdomain => 404 \d[.\d]* ms/); | ||
browser.assert.elements('.passed li', 5); | ||
done(); | ||
}); | ||
}); | ||
after(function() { | ||
server.locals.subdomain = 'admin'; | ||
}); | ||
}); | ||
}); | ||
Sorry, the diff of this file is not supported yet
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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
42047
4
782
5
186
2
- Removedrequest@^2.0
- Removedajv@6.12.6(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcore-util-is@1.0.2(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpsl@1.13.0(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedqs@6.5.3(transitive)
- Removedrequest@2.88.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removeduri-js@4.4.1(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)