core/source/logic/check_kinds/http_request.ts
Christian Fraß cf9c4f009d [mod] source
2023-07-27 17:37:45 +02:00

392 lines
8.4 KiB
TypeScript

namespace _heimdall.check_kinds.http_request
{
/**
*/
function parameters_schema(
) : _heimdall.helpers.json_schema.type_schema
{
return {
"type": "object",
"additionalProperties": false,
"properties": {
"request": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {
"description": "URL",
"type": "string"
},
"method": {
"type": "string",
"enum": [
"GET",
"POST"
],
"default": "GET"
}
},
"required": [
"target"
]
},
"timeout": {
"description": "maximum allowed execution time in seconds",
"type": "number",
"default": 5.0
},
"follow_redirects": {
"description": "whether redirect instructions in responses shall be followend instead of being exposed as result",
"type": "boolean",
"default": false
},
"response": {
"type": "object",
"additionalProperties": false,
"properties": {
"status_code": {
"description": "checks whether the response status code is this",
"anyOf": [
{
"type": "null",
},
{
"type": "integer",
},
],
"default": 200
},
"headers": {
"description": "conjunctively checks header key-value pairs",
"type": "object",
"additionalProperties": {
"description": "header value",
"type": "string"
},
"properties": {
},
"required": [
],
"default": {}
},
"body_part": {
"description": "checks whether the response body contains this string",
"type": "string"
}
},
"required": [
]
},
"critical": {
"description": "whether a violation of this check shall be leveled as critical instead of concerning",
"type": "boolean",
"default": true
},
"strict": {
"deprecated": true,
"description": "alias for 'critical'",
"type": "boolean",
"default": true
},
},
"required": [
"request",
]
};
}
/**
*/
function normalize_order_node(
node : any
) : any
{
const version : string = (
(! ("critical" in node))
? "v1"
: "v2"
);
switch (version) {
default: {
throw (new Error("unhandled version"));
break;
}
case "v1": {
const node_ = lib_plankton.object.patched(
{
"request": {
"method": "GET"
},
"timeout": 5.0,
"follow_redirects": false,
"response": {
"status_code": 200
},
"strict": true,
},
node,
true
);
const allowed_methods : Array<string> = ["GET", "POST"];
if (! allowed_methods.includes(node_["request"]["method"])) {
throw (new Error("invalid HTTP request method: " + node_["request"]["method"]));
}
else {
return {
"request": node_["request"],
"timeout": node_["timeout"],
"follow_redirects": node_["follow_redirects"],
"response": node_["response"],
"critical": node_["strict"],
};
}
break;
}
case "v2": {
const node_ = lib_plankton.object.patched(
{
"request": {
"method": "GET"
},
"timeout": 5.0,
"follow_redirects": false,
"response": {
"status_code": 200
},
"critical": true,
},
node,
true
);
const allowed_methods : Array<string> = ["GET", "POST"];
if (! allowed_methods.includes(node_["request"]["method"])) {
throw (new Error("invalid HTTP request method: " + node_["request"]["method"]));
}
else {
return node_;
}
break;
}
}
}
/**
*/
async function run(
parameters : {
request : {
target : string;
method : string;
};
timeout : float;
follow_redirects : boolean;
response : {
status_code : int;
headers ?: Record<string, string>;
body_part ?: string;
};
critical : boolean;
}
) : Promise<_heimdall.type_result>
{
let error : (null | Error);
const url : URL = new URL(parameters.request.target);
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/1.1",
"scheme": (
(url.protocol.slice(0, -1) === "https")
? "https"
: "http"
),
"host": url.host,
"method": {
"GET": lib_plankton.http.enum_method.get,
"POST": lib_plankton.http.enum_method.post,
}[parameters.request.method],
"path": url.pathname,
"query": url.search,
"headers": {},
"body": "",
};
let http_response : (null | lib_plankton.http.type_response);
try {
http_response = await lib_plankton.http.call(
http_request,
{
"timeout": parameters.timeout,
"follow_redirects": parameters.follow_redirects,
"implementation": "fetch",
}
);
error = null;
}
catch (error_) {
http_response = null;
error = error_;
}
if (http_response === null) {
return {
"condition": (
parameters.critical
? _heimdall.enum_condition.critical
: _heimdall.enum_condition.concerning
),
"info": {
"request": parameters.request,
"faults": [
lib_plankton.translate.get("checks.http_request.request_failed", {"reason": error.toString()}),
],
},
};
}
else {
let faults : Array<string> = [];
// status code
{
if (
(! ("status_code" in parameters.response))
||
(parameters.response.status_code === null)
) {
// do nothing
}
else {
const status_code_expected : int = (parameters.response.status_code as int);
if (! (http_response.status_code === status_code_expected)) {
faults.push(
lib_plankton.translate.get(
"checks.http_request.status_code_mismatch",
{
"status_code_actual": http_response.status_code.toFixed(0),
"status_code_expected": status_code_expected.toFixed(0),
}
)
);
}
else {
// do nothing
}
}
}
// headers
{
if (
(! ("headers" in parameters.response))
||
(parameters.response.headers === null)
) {
// do nothing
}
else {
const headers_expected : Record<string, string> = (parameters.response.headers as Record<string, string>);
Object.entries(headers_expected).forEach(
([header_key, header_value]) => {
if (
(! (header_key in http_response.headers))
&&
(! (header_key.toLowerCase() in http_response.headers))
) {
faults.push(
lib_plankton.translate.get(
"checks.http_request.header_missing",
{
"key": header_key,
"value_expected": header_value,
}
)
);
}
else {
if (
(! (http_response.headers[header_key] === header_value))
&&
(! (http_response.headers[header_key.toLowerCase()].toLowerCase() === header_value.toLowerCase()))
) {
faults.push(
lib_plankton.translate.get(
"checks.http_request.header_value_mismatch",
{
"key": header_key,
"value_actual": http_response.headers[header_key],
"value_expected": header_value,
}
)
);
}
else {
// do nothing
}
}
}
);
}
}
// body
{
if (
(! ("body_part" in parameters.response))
||
(parameters.response.body_part === null)
) {
// do nothing
}
else {
const body_part : string = (parameters.response.body_part as string);
if (! http_response.body.toString().includes(body_part)) {
faults.push(
lib_plankton.translate.get(
"checks.http_request.body_misses_part",
{
"part": body_part,
}
)
);
}
else {
// do nothing
}
}
}
return {
"condition": (
(faults.length <= 0)
? _heimdall.enum_condition.ok
: (
parameters.critical
? _heimdall.enum_condition.critical
: _heimdall.enum_condition.concerning
)
),
"info": {
"request": parameters.request,
"response": {
"status_code": http_response.status_code,
"headers": http_response.headers,
// "body": http_response.body,
},
"faults": faults,
}
};
}
}
/**
*/
export function check_kind_implementation(
) : type_check_kind
{
return {
"parameters_schema": parameters_schema,
"normalize_order_node": normalize_order_node,
"run": run,
};
}
}