mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-02-23 03:55:56 +00:00
Add Websocket Test v2
This commit is contained in:
parent
fc628e3bea
commit
587699d7b3
8 changed files with 163 additions and 31 deletions
|
@ -23,7 +23,7 @@ It is a temporary live demo, all data will be deleted after 10 minutes. Sponsore
|
||||||
|
|
||||||
## ⭐ Features
|
## ⭐ Features
|
||||||
|
|
||||||
- Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / HTTP(s) Json Query / Ping / DNS Record / Push / Steam Game Server / Docker Containers
|
- Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / HTTP(s) Json Query / Websocket / Ping / DNS Record / Push / Steam Game Server / Docker Containers
|
||||||
- Fancy, Reactive, Fast UI/UX
|
- Fancy, Reactive, Fast UI/UX
|
||||||
- Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications)
|
- Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications)
|
||||||
- 20-second intervals
|
- 20-second intervals
|
||||||
|
|
15
db/knex_migrations/2025-02-15-2312-add-wstest.js
Normal file
15
db/knex_migrations/2025-02-15-2312-add-wstest.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Add websocket URL
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("monitor", function (table) {
|
||||||
|
table.text("wsurl");
|
||||||
|
table.boolean("ws_ignore_headers").notNullable().defaultTo(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.alterTable("monitor", function (table) {
|
||||||
|
table.dropColumn("wsurl");
|
||||||
|
table.dropColumn("ws_ignore_headers");
|
||||||
|
});
|
||||||
|
};
|
|
@ -96,6 +96,8 @@ class Monitor extends BeanModel {
|
||||||
parent: this.parent,
|
parent: this.parent,
|
||||||
childrenIDs: preloadData.childrenIDs.get(this.id) || [],
|
childrenIDs: preloadData.childrenIDs.get(this.id) || [],
|
||||||
url: this.url,
|
url: this.url,
|
||||||
|
wsurl: this.wsurl,
|
||||||
|
wsIgnoreHeaders: this.getWsIgnoreHeaders(),
|
||||||
method: this.method,
|
method: this.method,
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
|
@ -255,6 +257,14 @@ class Monitor extends BeanModel {
|
||||||
return Boolean(this.ignoreTls);
|
return Boolean(this.ignoreTls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse to boolean
|
||||||
|
* @returns {boolean} Should WS headers be ignored?
|
||||||
|
*/
|
||||||
|
getWsIgnoreHeaders() {
|
||||||
|
return Boolean(this.wsIgnoreHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse to boolean
|
* Parse to boolean
|
||||||
* @returns {boolean} Is the monitor in upside down mode?
|
* @returns {boolean} Is the monitor in upside down mode?
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const { MonitorType } = require("./monitor-type");
|
const { MonitorType } = require("./monitor-type");
|
||||||
const { UP } = require("../../src/util");
|
const WebSocket = require("ws");
|
||||||
|
const { UP, DOWN } = require("../../src/util");
|
||||||
const childProcessAsync = require("promisify-child-process");
|
const childProcessAsync = require("promisify-child-process");
|
||||||
|
|
||||||
class websocket extends MonitorType {
|
class websocket extends MonitorType {
|
||||||
|
@ -9,8 +10,8 @@ class websocket extends MonitorType {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async check(monitor, heartbeat, _server) {
|
async check(monitor, heartbeat, _server) {
|
||||||
//let status_code = await this.attemptUpgrade(monitor.url);
|
let statusCode = await this.attemptUpgrade(monitor);
|
||||||
let statusCode = await this.curlTest(monitor.url);
|
//let statusCode = await this.curlTest(monitor.url);
|
||||||
this.updateStatus(heartbeat, statusCode);
|
this.updateStatus(heartbeat, statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,40 +29,45 @@ class websocket extends MonitorType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if status code is 101 and sets status
|
* Checks if status code is 1000(Normal Closure) and sets status and message
|
||||||
* @param {object} heartbeat The heartbeat object to update.
|
* @param {object} heartbeat The heartbeat object to update.
|
||||||
* @param {string} statusCode Status code from curl
|
* @param {[ string, int ]} status Array containing a status message and response code
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
updateStatus(heartbeat, statusCode) {
|
updateStatus(heartbeat, [ message, code ]) {
|
||||||
if (statusCode === "101") {
|
heartbeat.status = code === 1000 ? UP : DOWN;
|
||||||
heartbeat.status = UP;
|
heartbeat.msg = message;
|
||||||
}
|
|
||||||
heartbeat.msg = statusCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt at using websocket library. Abandoned this idea because of the lack of control of headers. Certain websocket servers don't return the Sec-WebSocket-Accept, which causes websocket to error out.
|
/**
|
||||||
// async attemptUpgrade(hostname) {
|
* Uses the builtin Websocket API to establish a connection to target server
|
||||||
// return new Promise((resolve) => {
|
* @param {object} monitor The monitor object for input parameters.
|
||||||
// const ws = new WebSocket('wss://' + hostname);
|
* @returns {[ string, int ]} Array containing a status message and response code
|
||||||
|
*/
|
||||||
|
async attemptUpgrade(monitor) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const ws = new WebSocket(monitor.wsurl);
|
||||||
|
|
||||||
// ws.addEventListener("open", (event) => {
|
ws.addEventListener("open", (event) => {
|
||||||
// ws.close();
|
ws.close(1000);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// ws.onerror = (error) => {
|
ws.onerror = (error) => {
|
||||||
// console.log(error.message);
|
// console.log(error.message);
|
||||||
// };
|
// Give user the choice to ignore Sec-WebSocket-Accept header
|
||||||
|
if (monitor.wsIgnoreHeaders && error.message === "Invalid Sec-WebSocket-Accept header") {
|
||||||
|
resolve([ "101 - OK", 1000 ]);
|
||||||
|
}
|
||||||
|
resolve([ error.message, error.code ]);
|
||||||
|
};
|
||||||
|
|
||||||
// ws.onclose = (event) => {
|
ws.onclose = (event) => {
|
||||||
// if (event.code === 1005) {
|
// console.log(event.message);
|
||||||
// resolve(true);
|
// console.log(event.code);
|
||||||
// } else {
|
resolve([ "101 - OK", event.code ]);
|
||||||
// resolve(false);
|
};
|
||||||
// }
|
});
|
||||||
// };
|
}
|
||||||
// })
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -790,6 +790,8 @@ let needSetup = false;
|
||||||
bean.parent = monitor.parent;
|
bean.parent = monitor.parent;
|
||||||
bean.type = monitor.type;
|
bean.type = monitor.type;
|
||||||
bean.url = monitor.url;
|
bean.url = monitor.url;
|
||||||
|
bean.wsurl = monitor.wsurl;
|
||||||
|
bean.wsIgnoreHeaders = monitor.wsIgnoreHeaders;
|
||||||
bean.method = monitor.method;
|
bean.method = monitor.method;
|
||||||
bean.body = monitor.body;
|
bean.body = monitor.body;
|
||||||
bean.headers = monitor.headers;
|
bean.headers = monitor.headers;
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
||||||
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
||||||
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||||
|
"wsIgnoreHeadersDescription": "Test non compliant Websocket servers that don't respond with correct headers.",
|
||||||
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
||||||
"Upside Down Mode": "Upside Down Mode",
|
"Upside Down Mode": "Upside Down Mode",
|
||||||
"Max. Redirects": "Max. Redirects",
|
"Max. Redirects": "Max. Redirects",
|
||||||
|
|
|
@ -117,11 +117,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- URL -->
|
<!-- URL -->
|
||||||
<div v-if="monitor.type === 'websocket' || monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'real-browser' " class="my-3">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'real-browser' " class="my-3">
|
||||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||||
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required data-testid="url-input">
|
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required data-testid="url-input">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Websocket -->
|
||||||
|
<div v-if="monitor.type === 'websocket'" class="my-3">
|
||||||
|
<label for="wsurl" class="form-label">{{ $t("URL") }}</label>
|
||||||
|
<input id="wsurl" v-model="monitor.wsurl" type="wsurl" class="form-control" pattern="wss?://.+" required data-testid="url-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- gRPC URL -->
|
<!-- gRPC URL -->
|
||||||
<div v-if="monitor.type === 'grpc-keyword' " class="my-3">
|
<div v-if="monitor.type === 'grpc-keyword' " class="my-3">
|
||||||
<label for="grpc-url" class="form-label">{{ $t("URL") }}</label>
|
<label for="grpc-url" class="form-label">{{ $t("URL") }}</label>
|
||||||
|
@ -625,6 +631,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="monitor.type === 'websocket' " class="my-3 form-check">
|
||||||
|
<input id="ws-ignore-headers" v-model="monitor.wsIgnoreHeaders" class="form-check-input" type="checkbox">
|
||||||
|
<label class="form-check-label" for="ws-ignore-headers">
|
||||||
|
{{ $t("Ignore Server Headers") }}
|
||||||
|
</label>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("wsIgnoreHeadersDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'redis' " class="my-3 form-check">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'redis' " class="my-3 form-check">
|
||||||
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
||||||
<label class="form-check-label" for="ignore-tls">
|
<label class="form-check-label" for="ignore-tls">
|
||||||
|
@ -1078,6 +1094,8 @@ const monitorDefaults = {
|
||||||
name: "",
|
name: "",
|
||||||
parent: null,
|
parent: null,
|
||||||
url: "https://",
|
url: "https://",
|
||||||
|
wsurl: "wss://",
|
||||||
|
wsIgnoreHeaders: false,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
interval: 60,
|
interval: 60,
|
||||||
retryInterval: 60,
|
retryInterval: 60,
|
||||||
|
@ -1731,6 +1749,10 @@ message HealthCheckResponse {
|
||||||
this.monitor.url = this.monitor.url.trim();
|
this.monitor.url = this.monitor.url.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.monitor.wsurl) {
|
||||||
|
this.monitor.wsurl = this.monitor.wsurl.trim();
|
||||||
|
}
|
||||||
|
|
||||||
let createdNewParent = false;
|
let createdNewParent = false;
|
||||||
|
|
||||||
if (this.draftGroupName && this.monitor.parent === -1) {
|
if (this.draftGroupName && this.monitor.parent === -1) {
|
||||||
|
|
76
test/backend-test/test-websocket.js
Normal file
76
test/backend-test/test-websocket.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const { websocket } = require("../../server/monitor-types/websocket");
|
||||||
|
const { UP, DOWN, PENDING } = require("../../src/util");
|
||||||
|
|
||||||
|
test("Test Websocket TLS", async () => {
|
||||||
|
const websocketMonitor = new websocket();
|
||||||
|
|
||||||
|
const monitor = {
|
||||||
|
wsurl: "wss://echo.websocket.org",
|
||||||
|
wsIgnoreHeaders: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const heartbeat = {
|
||||||
|
msg: "",
|
||||||
|
status: PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
await websocketMonitor.check(monitor, heartbeat, {});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
assert.strictEqual(heartbeat.msg, "101 - OK");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test Websocket non TLS", async () => {
|
||||||
|
const websocketMonitor = new websocket();
|
||||||
|
|
||||||
|
const monitor = {
|
||||||
|
wsurl: "ws://ws.ifelse.io",
|
||||||
|
wsIgnoreHeaders: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const heartbeat = {
|
||||||
|
msg: "",
|
||||||
|
status: PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
await websocketMonitor.check(monitor, heartbeat, {});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
assert.strictEqual(heartbeat.msg, "101 - OK");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test a non compliant WS server without ignore", async () => {
|
||||||
|
const websocketMonitor = new websocket();
|
||||||
|
|
||||||
|
const monitor = {
|
||||||
|
wsurl: "wss://c.img-cdn.net/yE4s7KehTFyj/",
|
||||||
|
wsIgnoreHeaders: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const heartbeat = {
|
||||||
|
msg: "",
|
||||||
|
status: PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
await websocketMonitor.check(monitor, heartbeat, {});
|
||||||
|
assert.strictEqual(heartbeat.status, DOWN);
|
||||||
|
assert.strictEqual(heartbeat.msg, "Invalid Sec-WebSocket-Accept header");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test a non compliant WS server with ignore", async () => {
|
||||||
|
const websocketMonitor = new websocket();
|
||||||
|
|
||||||
|
const monitor = {
|
||||||
|
wsurl: "wss://c.img-cdn.net/yE4s7KehTFyj/",
|
||||||
|
wsIgnoreHeaders: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const heartbeat = {
|
||||||
|
msg: "",
|
||||||
|
status: PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
await websocketMonitor.check(monitor, heartbeat, {});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
assert.strictEqual(heartbeat.msg, "101 - OK");
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue