From 52ecefb7b3185046e72ef86fc93cfdaba83739f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Guti=C3=A9rrez=20Hermoso?= Date: Thu, 9 May 2024 16:46:25 -0400 Subject: [PATCH] BootProbes: add a websocket probe This self-test isn't perfect because we're running it from the backend instead of the frontend. Conceivably the backend might have trouble resolving its own url. Eventually we should move this test or something like it to something that executes in the frontend. --- app/client/models/AdminChecks.ts | 9 +++++++++ app/common/BootProbe.ts | 3 ++- app/server/lib/BootProbes.ts | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/client/models/AdminChecks.ts b/app/client/models/AdminChecks.ts index f8ee5420213..f6b955653c6 100644 --- a/app/client/models/AdminChecks.ts +++ b/app/client/models/AdminChecks.ts @@ -162,6 +162,15 @@ It is good practice not to run Grist as the root user. 'reachable': { info: ` The main page of Grist should be available. +` + }, + + 'websockets': { + // TODO: add a link to https://support.getgrist.com/self-managed/#how-do-i-run-grist-on-a-server + info: ` +Websocket connections need HTTP 1.1 and the ability to pass a few +extra headers in order to work. Sometimes a reverse proxy can +interfere with these requirements. ` }, }; diff --git a/app/common/BootProbe.ts b/app/common/BootProbe.ts index 753af1c4b48..699d3515659 100644 --- a/app/common/BootProbe.ts +++ b/app/common/BootProbe.ts @@ -6,7 +6,8 @@ export type BootProbeIds = 'reachable' | 'host-header' | 'sandboxing' | - 'system-user' + 'system-user' | + 'websockets' ; export interface BootProbeResult { diff --git a/app/server/lib/BootProbes.ts b/app/server/lib/BootProbes.ts index b770883897e..f1bed77362e 100644 --- a/app/server/lib/BootProbes.ts +++ b/app/server/lib/BootProbes.ts @@ -4,6 +4,7 @@ import { removeTrailingSlash } from 'app/common/gutil'; import { expressWrap, jsonErrorHandler } from 'app/server/lib/expressWrap'; import { GristServer } from 'app/server/lib/GristServer'; import * as express from 'express'; +import WS from 'ws'; import fetch from 'node-fetch'; /** @@ -58,6 +59,7 @@ export class BootProbes { this._probes.push(_bootProbe); this._probes.push(_hostHeaderProbe); this._probes.push(_sandboxingProbe); + this._probes.push(_webSocketsProbe); this._probeById = new Map(this._probes.map(p => [p.id, p])); } } @@ -104,6 +106,37 @@ const _homeUrlReachableProbe: Probe = { } }; +const _webSocketsProbe: Probe = { + id: 'websockets', + name: 'Can we open a websocket with the server', + apply: async (server, req) => { + return new Promise((resolve) => { + const url = new URL(server.getHomeUrl(req)); + url.protocol = (url.protocol === 'https:') ? 'wss:' : 'ws:'; + const ws = new WS.WebSocket(url.href); + const details: Record = { + url, + }; + ws.on('open', () => { + ws.send('Just nod if you can hear me.'); + resolve({ + success: true, + details, + }); + ws.close(); + }); + ws.on('error', (ev) => { + details.error = ev.message; + resolve({ + success: false, + details, + }); + ws.close(); + }); + }); + } +}; + const _statusCheckProbe: Probe = { id: 'health-check', name: 'Is an internal health check passing',