From 0bee90ea31d19921dc072764704b8b4e8bd35220 Mon Sep 17 00:00:00 2001 From: PoleTransformer Date: Tue, 30 Dec 2025 16:25:17 -0800 Subject: [PATCH 1/6] WS test status code support + improved error handing + subprotocol input --- server/monitor-types/websocket-upgrade.js | 46 +++++++-- src/lang/en.json | 36 +------- src/pages/EditMonitor.vue | 108 ++++++++-------------- test/backend-test/test-websocket.js | 94 +++++++++++++++++-- 4 files changed, 167 insertions(+), 117 deletions(-) diff --git a/server/monitor-types/websocket-upgrade.js b/server/monitor-types/websocket-upgrade.js index 762e8df3b..ed1ba10f2 100644 --- a/server/monitor-types/websocket-upgrade.js +++ b/server/monitor-types/websocket-upgrade.js @@ -1,6 +1,26 @@ const { MonitorType } = require("./monitor-type"); const WebSocket = require("ws"); const { UP } = require("../../src/util"); +const { checkStatusCode } = require("../util-server"); +// Define closing error codes https://www.iana.org/assignments/websocket/websocket.xml#close-code-number +const WS_ERR_CODE = { + 1002: "Protocol error", + 1003: "Unsupported Data", + 1005: "No Status Received", + 1006: "Abnormal Closure", + 1007: "Invalid frame payload data", + 1008: "Policy Violation", + 1009: "Message Too Big", + 1010: "Mandatory Extension Missing", + 1011: "Internal Error", + 1012: "Service Restart", + 1013: "Try Again Later", + 1014: "Bad Gateway", + 1015: "TLS Handshake Failed", + 3000: "Unauthorized", + 3003: "Forbidden", + 3008: "Timeout", +}; class WebSocketMonitorType extends MonitorType { name = "websocket-upgrade"; @@ -11,24 +31,31 @@ class WebSocketMonitorType extends MonitorType { async check(monitor, heartbeat, _server) { const [ message, code ] = await this.attemptUpgrade(monitor); - if (code === 1000) { + // If no close code, then an error has occurred, display to user + if (typeof code === "undefined") { + throw new Error(`${message}`); + } + // If returned status code matches user controlled accepted status code(default 1000), return success + if (checkStatusCode(code, JSON.parse(monitor.accepted_statuscodes_json))) { heartbeat.status = UP; heartbeat.msg = message; - } else { - throw new Error(message); + return; //success at this point } + + // Throw an error using friendly name if defined, fallback to generic msg + throw new Error(WS_ERR_CODE[code] || `Unexpected status code: ${code}`); } /** - * Uses the builtin Websocket API to establish a connection to target server + * Uses the ws Node.js library to establish a connection to target server * @param {object} monitor The monitor object for input parameters. * @returns {[ string, int ]} Array containing a status message and response code */ async attemptUpgrade(monitor) { return new Promise((resolve) => { let ws; - //If user selected a subprotocol, sets Sec-WebSocket-Protocol header. Subprotocol Identifier column: https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name - ws = monitor.wsSubprotocol === "" ? new WebSocket(monitor.url) : new WebSocket(monitor.url, monitor.wsSubprotocol); + // If user inputs subprotocol(s), convert to array, then set Sec-WebSocket-Protocol header. Subprotocol Identifier column: https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name + ws = !monitor.wsSubprotocol ? new WebSocket(monitor.url) : new WebSocket(monitor.url, monitor.wsSubprotocol.replace(/\s/g, "").split(",")); ws.addEventListener("open", (event) => { // Immediately close the connection @@ -36,7 +63,8 @@ class WebSocketMonitorType extends MonitorType { }); ws.onerror = (error) => { - // Give user the choice to ignore Sec-WebSocket-Accept header + // Give user the choice to ignore Sec-WebSocket-Accept header for non compliant servers + // Header in HTTP 101 Switching Protocols response from server, technically already upgraded to WS if (monitor.wsIgnoreSecWebsocketAcceptHeader && error.message === "Invalid Sec-WebSocket-Accept header") { resolve([ "101 - OK", 1000 ]); return; @@ -46,8 +74,8 @@ class WebSocketMonitorType extends MonitorType { }; ws.onclose = (event) => { - // Upgrade success, connection closed successfully - resolve([ "101 - OK", event.code ]); + // Return the close code, if connection didn't close cleanly, return the reason if present + resolve([ event.wasClean ? event.code.toString() + " - OK" : event.reason, event.code ]); }; }); } diff --git a/src/lang/en.json b/src/lang/en.json index 727358fdc..e46c6ebbe 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -90,39 +90,9 @@ "upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.", "ignoreSecWebsocketAcceptHeaderDescription": "Allows the server to not reply with Sec-WebSocket-Accept header, if the websocket upgrade succeeds.", "Ignore Sec-WebSocket-Accept header": "Ignore {0} header", - "wsSubprotocolDescription": "For more information on subprotocols, please consult the {documentation}", - "WebSocket Application Messaging Protocol": "WAMP (The WebSocket Application Messaging Protocol)", - "Session Initiation Protocol": "WebSocket Transport for SIP (Session Initiation Protocol)", - "Subprotocol": "Subprotocol", - "Network API for Notification Channel": "OMA RESTful Network API for Notification Channel", - "Web Process Control Protocol": "Web Process Control Protocol (WPCP)", - "Advanced Message Queuing Protocol": "Advanced Message Queuing Protocol (AMQP) 1.0+", - "jsflow": "jsFlow pubsub/queue Protocol", - "Reverse Web Process Control": "Reverse Web Process Control Protocol (RWPCP)", - "Extensible Messaging and Presence Protocol": "WebSocket Transport for the Extensible Messaging and Presence Protocol (XMPP)", - "Smart Home IP": "SHIP - Smart Home IP", - "Miele Cloud Connect Protocol": "Miele Cloud Connect Protocol", - "Push Channel Protocol": "Push Channel Protocol", - "Message Session Relay Protocol": "WebSocket Transport for MSRP (Message Session Relay Protocol)", - "Binary Floor Control Protocol": "WebSocket Transport for BFCP (Binary Floor Control Protocol)", - "Softvelum Low Delay Protocol": "Softvelum Low Delay Protocol", - "OPC UA Connection Protocol": "OPC UA Connection Protocol", - "OPC UA JSON Encoding": "OPC UA JSON Encoding", - "Swindon Web Server Protocol": "Swindon Web Server Protocol (JSON encoding)", - "Broadband Forum User Services Platform": "USP (Broadband Forum User Services Platform)", - "Constrained Application Protocol": "Constrained Application Protocol (CoAP)", - "Softvelum WebSocket signaling protocol": "Softvelum WebSocket Signaling Protocol", - "Cobra Real Time Messaging Protocol": "Cobra Real Time Messaging Protocol", - "Declarative Resource Protocol": "Declarative Resource Protocol", - "BACnet Secure Connect Hub Connection": "BACnet Secure Connect Hub Connection", - "BACnet Secure Connect Direct Connection": "BACnet Secure Connect Direct Connection", - "WebSocket Transport for JMAP": "WebSocket Transport for JMAP (JSON Meta Application Protocol)", - "ITU-T T.140 Real-Time Text": "ITU-T T.140 Real-Time Text", - "Done.best IoT Protocol": "Done.best IoT Protocol", - "Collection Update": "The Collection Update Websocket Subprotocol", - "Text IRC Protocol": "Text IRC Protocol", - "Binary IRC Protocol": "Binary IRC Protocol", - "Penguin Statistics Live Protocol v3": "Penguin Statistics Live Protocol v3 (Protobuf encoding)", + "wsSubprotocolDescription": "Enter a comma delimited list of subprotocols. For more information on subprotocols, please consult the {documentation}", + "wsCodeDescription": "For more information on status codes, please consult {documentation}", + "Subprotocol(s)": "Subprotocol(s)", "maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.", "Upside Down Mode": "Upside Down Mode", "Max. Redirects": "Max. Redirects", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 8c7e5cd21..55c551dd6 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -142,73 +142,8 @@
- - + +