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/

page

A web page the way check_http reads it: status code, response time, size, expected and unwanted text, expected header, redirections.

  • web
  • http
  • https
  • page
  • redirect
  • check_http

by banshee1 installation2 versions

Keen 1.0.0 · 160 lines
// A web page, the way check_http reads it: its status code, its response time, its size, its content and
// its headers, and where its redirections lead. A redirection is followed only when the probe allows it,
// and only on the same host: the collector reads the location itself, as http.get follows none.
// The expiry of the certificate is not read here: network/tls/certificate watches it.
need('protocol:http');
$user = need('protocol:http:user', 'optional');
$password = need('protocol:http:password', 'optional');
$token = need('protocol:http:token', 'optional');

$url = variable('web:url', 'text', array(
    'label' => 'Page address',
    'help'  => 'The full address, http:// or https://, for instance https://www.example/health.',
));
$host = variable('web:host', 'text', array(
    'label'    => 'Site name',
    'help'     => 'Sent as the Host header, for a site that a server hosts beside others and that is reached by an IP address. Empty: the name in the address.',
    'required' => false,
));
$follow = variable('web:redirects', 'integer', array(
    'label'   => 'Redirections followed',
    'help'    => 'How many redirections the collector follows, on the same host only. 0: none, the page returns its own code and where it points.',
    'default' => 0,
));

output('status', 'number', array('label' => 'Status code'));
output('response-time', 'number', array('label' => 'Response time', 'unit' => 'ms'));
output('size', 'number', array('label' => 'Page size', 'unit' => 'characters'));
output('final-url', 'text', array('label' => 'Final address'));
output('redirects', 'number', array('label' => 'Redirections followed'));

watch.expect('unexpected-response', 'Unexpected response', 'status', array('critical' => array(200)));
watch.custom('expected-text', 'Expected text missing', array(
    'help'     => 'Critical when the page does not hold the text you give, for instance a word only a page in good health shows.',
    'settings' => array(
        'text' => array('label' => 'Expected text', 'form' => 'text'),
    ),
    'enabled'  => false,
));
watch.custom('unwanted-text', 'Unwanted text present', array(
    'help'     => 'Critical when the page holds the text you give, for instance the words of a maintenance page.',
    'settings' => array(
        'text' => array('label' => 'Unwanted text', 'form' => 'text'),
    ),
    'enabled'  => false,
));
watch.expect('unexpected-redirect', 'Unexpected redirection', 'final-url', array(
    'help'    => 'Raises when the final address is none of those given, for instance when a page that should send to https://www.example/login sends elsewhere, or nowhere.',
    'enabled' => false,
));
watch.above('slow', 'Slow page', 'response-time', array('warning' => 1000, 'critical' => 3000, 'for' => '3 runs'));
watch.below('too-small', 'Page too small', 'size', array('critical' => 1));
watch.custom('expected-header', 'Expected header missing', array(
    'help'     => 'Critical when the answer has no header of this name, or when its value does not hold the text given.',
    'settings' => array(
        'name'  => array('label' => 'Header name', 'form' => 'text'),
        'value' => array('label' => 'Its value holds', 'form' => 'text'),
    ),
    'enabled'  => false,
));
watch.habit('slow-unusual', 'Unusual response time', 'response-time', array('floor' => 500, 'enabled' => false));

// One GET. A site that refuses the connection, or does not answer in time, is down: critical, rather than
// leaving the probe unreachable.
function fetch($address, $headers, $user, $password) {
    $answer = http.get($address, headers=$headers, user=$user, password=$password, errors=$err);
    if ($err['code'] == 'connection_refused' || $err['code'] == 'timeout') {
        fail('critical', 'Site down', $err['text']);
    }
    if ($err['code'] != '') {
        fail('unreachable', 'Site unreachable', $err['text']);
    }
    return $answer;
}

// Where an answer redirects, as a full address; '' when it is not a redirection. A location is absolute
// (https://www.example/login), from the root of the site (/login), without its scheme (//www.example/login),
// or beside the page (login).
function redirect_target($address, $answer) {
    $location = list.get($answer['headers'], 'location', '');
    if ($answer['status'] < 300 || $answer['status'] > 399 || $location == '') {
        return '';
    }
    if (list.count(text.match($location, '^(?i)https?://')) > 0) {
        return $location;
    }
    $parts = text.match($address, '^((?i)https?:)(//[^/?#]+)([^?#]*)');
    if (list.count(text.match($location, '^//')) > 0) {
        return $parts[1] . $location;
    }
    if (list.count(text.match($location, '^/')) > 0) {
        return $parts[1] . $parts[2] . $location;
    }
    $folder = text.match($parts[3], '^(.*/)');
    $path = '/';
    if (list.count($folder) > 0) {
        $path = $folder[1];
    }
    return $parts[1] . $parts[2] . $path . $location;
}

// The host of an address, in lower case, without its account or its port.
function host_of($address) {
    $found = text.match($address, '^(?i)https?://(?:[^@/?#]*@)?(\[[^\]]*\]|[^:/?#]+)');
    return text.lower($found[1]);
}

// The Host header names the site; a token goes as a bearer, and then no account is sent beside it.
$headers = array();
if ($host != '') {
    $headers['Host'] = $host;
}
if ($token != '') {
    $headers['Authorization'] = 'Bearer ' . $token;
    $user = '';
}

$answer = fetch($url, $headers, $user, $password);
$time = $answer['time'];
$current = $url;
$redirects = 0;
$target = redirect_target($current, $answer);
while ($target != '' && $redirects < $follow && host_of($target) == host_of($current)) {
    $current = $target;
    $answer = fetch($current, $headers, $user, $password);
    $time = $time + $answer['time'];
    $redirects = $redirects + 1;
    $target = redirect_target($current, $answer);
}
// The final address: where the last answer points when it is a redirection not followed, the last page read
// otherwise.
$final = $current;
if ($target != '') {
    $final = $target;
}

value('status', $answer['status']);
value('response-time', $time);
value('size', text.length($answer['body']));
value('final-url', $final);
value('redirects', $redirects);

$expected = watch.setting('expected-text', 'text');
if ($expected != '' && !text.contains($answer['body'], $expected)) {
    watch.raise('expected-text', 'critical', 'The page does not hold "' . $expected . '"');
}
$unwanted = watch.setting('unwanted-text', 'text');
if ($unwanted != '' && text.contains($answer['body'], $unwanted)) {
    watch.raise('unwanted-text', 'critical', 'The page holds "' . $unwanted . '"');
}
// Header names arrive in lower case. The value found is not repeated in the message: a header may carry a
// cookie.
$header = text.lower(watch.setting('expected-header', 'name'));
$holds = watch.setting('expected-header', 'value');
if ($header != '') {
    if (!list.has($answer['headers'], $header)) {
        watch.raise('expected-header', 'critical', 'The answer has no header "' . $header . '"');
    } elseif ($holds != '' && !text.contains($answer['headers'][$header], $holds)) {
        watch.raise('expected-header', 'critical', 'The header "' . $header . '" does not hold "' . $holds . '"');
    }
}