main
ts 77 lines 3.11 KB
Raw
1 import { SPECIAL_URI } from './cross-const'
2 import { Middleware } from 'koa'
3 import { getProjectInfo } from './github'
4 import { isIP, isIPv6 } from 'net'
5 import _ from 'lodash'
6 import { findDefined, haveTimeout } from './cross'
7 import { httpString } from './util-http'
8
9 let activeSelfChecks = 0
10
11 const CHECK_URL = SPECIAL_URI + 'self-check'
12 export const selfCheckMiddleware: Middleware = (ctx, next) => {
13 if (!activeSelfChecks || !ctx.url.startsWith(CHECK_URL))
14 return next()
15 ctx.body = 'HFS'
16 ctx.state.skipFilters = true
17 }
18
19 declare module "koa" {
20 interface DefaultState {
21 skipFilters?: boolean
22 }
23 }
24
25 export async function selfCheck(url: string) {
26 interface PortScannerService {
27 type?: string
28 url: string
29 headers: {[k: string]: string}
30 method: string
31 body?: string
32 regexpFailure: string
33 regexpSuccess: string
34 }
35 const prjInfo = await getProjectInfo()
36 console.log(`Checking server ${url}`)
37 const parsed = new URL(url)
38 const family = !isIP(parsed.hostname) ? undefined : isIPv6(parsed.hostname) ? 6 : 4
39 try {
40 ++activeSelfChecks
41 for (const services of _.chunk(_.shuffle<PortScannerService>(prjInfo.selfCheckServices), 2)) {
42 try {
43 const results = await Promise.allSettled(services.map(async svc => {
44 if (!svc.url || svc.type) throw 'unsupported ' + svc.type // only default type supported for now
45 let { url: serviceUrl, body, regexpSuccess, regexpFailure, ...rest } = svc
46 const service = new URL(serviceUrl).hostname
47 console.log('Trying external service', service)
48 console.debug(svc)
49 body = applySymbols(body)
50 serviceUrl = applySymbols(serviceUrl)!
51 const timeout = 9_000
52 const res = await haveTimeout(timeout, httpString(serviceUrl, { family, timeout, ...rest, body }))
53 const success = new RegExp(regexpSuccess).test(res)
54 const failure = new RegExp(regexpFailure).test(res)
55 if (success === failure) throw 'inconsistent: ' + service + ': ' + res // this result cannot be trusted
56 console.debug(service, 'responded', success)
57 return { success, service, url }
58 }))
59 // prefer a positive check so a fast false negative doesn't mask a working service
60 return findDefined(results, x => x.status === 'fulfilled' && x.value.success ? x.value : undefined)
61 || findDefined(results, x => x.status === 'fulfilled' ? x.value : undefined)
62 }
63 catch (e: any) {
64 console.debug(e?.errors?.map(String) || e?.cause || String(e))
65 }
66 }
67 }
68 finally {
69 --activeSelfChecks
70 }
71
72 function applySymbols(s?: string) {
73 return s?.replace('$IP', parsed.hostname)
74 .replace('$PORT', parsed.port || (parsed.protocol === 'https:' ? '443' : '80'))
75 .replace('$URL', url.replace(/\/$/, '') + CHECK_URL)
76 }
77 }