banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

Web

banshee/web-collection/web/https/

api-health

The health endpoint of a web service: status code, response time, declared health and version, and the health of each dependency.

  • web
  • https
  • api
  • health
  • spring-boot

by banshee1 installation2 versions

v2Latest

Published on October 28, 2026. One naming convention across collections; documentation completed.

Keen 1.0.0 · 86 lines
// The health endpoint of a web service, behind a token or not: its status code, its response time, the
// health and the version it declares, and the health of each of its dependencies. It reads the common forms:
// {"status": "UP", "components": {...}} (Spring Boot), {"status": "pass", "checks": {...}}, or a plain-text
// "Healthy" (ASP.NET Core).
need('protocol:http');
$token = need('protocol:http:token', 'optional');

$url = variable('web:url', 'text', array(
    'label' => 'Health endpoint address',
    'help'  => 'The full address, for instance https://api.example/health.',
));

per('dependency', 'Dependencies');
output('code', 'number', array('label' => 'Status code'));
output('response-time', 'number', array('label' => 'Response time', 'unit' => 'ms'));
output('health', 'text', array('label' => 'Declared health'));
output('version', 'text', array('label' => 'Declared version'));
output('dependency', 'text', array('label' => 'Health of the dependency', 'per' => 'dependency'));

watch.expect('code', 'Answer in error', 'code', array('critical' => array(200)));
watch.above('slow', 'Slow service', 'response-time', array('warning' => 500, 'critical' => 2000, 'for' => '3 runs'));
watch.expect('unhealthy', 'Service not healthy', 'health', array('warning' => array('ok')));
watch.state('down', 'Service down', 'health', array('critical' => array('down')));
watch.state('dependency-down', 'Dependency down', 'dependency', array('critical' => array('down')));
watch.expect('dependency-unhealthy', 'Dependency not healthy', 'dependency', array('warning' => array('ok')));
watch.missing('dependency-missing', 'Dependency missing', 'dependency', array('enabled' => false));
watch.expect('version', 'Unexpected version', 'version', array(
    'help'    => 'Raises when the service declares a version other than those given, for instance after a deployment that should not have happened.',
    'enabled' => false,
));

// A declared health in one word: "ok" for a service in good health, "down" for a service out, any other
// word as the service writes it, in lower case ("degraded", "warn").
function health_word($status) {
    $word = text.lower($status);
    if ($word == 'ok' || $word == 'up' || $word == 'pass' || $word == 'healthy') {
        return 'ok';
    }
    if ($word == 'down' || $word == 'fail' || $word == 'error' || $word == 'out_of_service' || $word == 'unhealthy') {
        return 'down';
    }
    return $word;
}

// The token goes in a header: the answer is not secret, its body having carried none. A service that
// refuses the connection, or does not answer in time, is down: critical, rather than unreachable.
$headers = array();
if ($token != '') {
    $headers['Authorization'] = 'Bearer ' . $token;
}
$answer = http.get($url, headers=$headers, errors=$err);
if ($err['code'] == 'connection_refused' || $err['code'] == 'timeout') {
    fail('critical', 'Service down', $err['text']);
}
if ($err['code'] != '') {
    fail('unreachable', 'Service unreachable', $err['text']);
}
value('code', $answer['status']);
value('response-time', $answer['time']);

// The body is read whatever the code: a service that says it is down often answers 503 with its health.
// Read only when its type says JSON or plain text: an error page of a proxy says neither.
$type = text.lower(list.get($answer['headers'], 'content-type', ''));
$health = 'unreadable';
$version = 'unreadable';
if (text.contains($type, 'json')) {
    $body = json.read($answer['body']);
    $health = 'missing';
    if (list.has($body, 'status')) {
        $health = health_word($body['status']);
    }
    $version = list.get($body, 'version', 'missing');
    $checks = list.get($body, 'checks', list.get($body, 'components', array()));
    foreach ($checks as $name => $check) {
        $state = 'missing';
        if (list.has($check, 'status')) {
            $state = health_word($check['status']);
        }
        value('dependency', $state, $name);
    }
} elseif (text.contains($type, 'text/plain')) {
    $health = health_word(text.trim($answer['body']));
    $version = 'missing';
}
value('health', $health);
value('version', $version);