WIP: http

pull/147/head
lyc8503 2025-10-16 23:51:47 +08:00
parent 1d12cf0b3e
commit 820d460512
3 changed files with 149 additions and 57 deletions

View File

@ -12,7 +12,7 @@ import {
} from 'chart.js' } from 'chart.js'
import 'chartjs-adapter-moment' import 'chartjs-adapter-moment'
import { MonitorState, MonitorTarget } from '@/types/config' import { MonitorState, MonitorTarget } from '@/types/config'
import { iataToCountry } from '@/util/iata' import { codeToCountry } from '@/util/iata'
ChartJS.register( ChartJS.register(
CategoryScale, CategoryScale,
@ -66,7 +66,7 @@ export default function DetailChart({
callbacks: { callbacks: {
label: (item: any) => { label: (item: any) => {
if (item.parsed.y) { if (item.parsed.y) {
return `${item.parsed.y}ms (${iataToCountry(item.raw.loc)})` return `${item.parsed.y}ms (${codeToCountry(item.raw.loc)})`
} }
}, },
}, },

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,142 @@
import { MonitorTarget } from '../../types/config' import { MonitorTarget } from '../../types/config'
import { withTimeout, fetchTimeout } from './util' import { withTimeout, fetchTimeout } from './util'
async function httpResponseBasicCheck(monitor: MonitorTarget, code: number, bodyReader: () => Promise<string>): Promise<string | null> {
if (monitor.expectedCodes) {
if (!monitor.expectedCodes.includes(code)) {
console.log(`${monitor.name} expected ${monitor.expectedCodes}, got ${code}`)
return `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${code}`
}
} else {
if (code < 200 || code > 299) {
console.log(`${monitor.name} expected 2xx, got ${code}`)
return `Expected codes: 2xx, Got: ${code}`
}
}
if (monitor.responseKeyword || monitor.responseForbiddenKeyword) {
// Only read response body if we have a keyword to check
const responseBody = await bodyReader()
// MUST contain responseKeyword
if (monitor.responseKeyword && !responseBody.includes(monitor.responseKeyword)) {
console.log(
`${monitor.name} expected keyword ${
monitor.responseKeyword
}, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`
)
return "HTTP response doesn't contain the configured keyword"
}
// MUST NOT contain responseForbiddenKeyword
if (
monitor.responseForbiddenKeyword &&
responseBody.includes(monitor.responseForbiddenKeyword)
) {
console.log(
`${monitor.name} forbidden keyword ${
monitor.responseForbiddenKeyword
}, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`
)
return 'HTTP response contains the configured forbidden keyword'
}
}
return null
}
export async function getStatusWithGlobalPing(monitor: MonitorTarget): Promise<{ location: string; status: { ping: number; up: boolean; err: string } }> {
if (monitor.checkProxy === undefined) {
throw 'empty check proxy for globalping, shouldn\'t call this method'
}
const gpUrl = new URL(monitor.checkProxy)
if (gpUrl.protocol !== 'globalping:') {
throw 'incorrect check proxy protocol for globalping, got: ' + gpUrl.protocol
}
const token = gpUrl.hostname
let globalPingRequest = {}
if (monitor.method === 'TCP_PING') {
} else {
const targetUrl = new URL(monitor.target)
globalPingRequest = {
type: 'http',
target: targetUrl.hostname,
measurementOptions: {
request: {
method: monitor.method,
path: targetUrl.pathname,
query: targetUrl.search,
headers: monitor.headers
},
port: targetUrl.port
}
}
}
const startTime = Date.now()
console.log(`Requesting the Global Ping API, payload: ${globalPingRequest}`)
const measurement = await fetchTimeout('https://api.globalping.io/v1/measurements', 5000, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify(globalPingRequest)
})
const measurementResponse = (await measurement.json() as any)
if (measurement.status !== 202) {
throw measurementResponse.error.message
}
const measurementId = measurementResponse.id
console.log(`Measurement created successfully, id: ${measurementId}, time elapsed: ${Date.now() - startTime}ms`)
const pollStart = Date.now()
let measurementResult: any
while (true) {
if (Date.now() - pollStart > (monitor.timeout ?? 10000) + 2000) {
throw 'api polling timeout'
}
measurementResult = await(await fetchTimeout(`https://api.globalping.io/v1/measurements/${measurementId}`, 5000)).json() as any
if (measurementResult.status !== 'in-progress') {
break
}
await new Promise(resolve => setTimeout(resolve, 1000))
}
console.log(`Measurement ${measurementId} finished with response: ${measurementResult}`)
const time = measurementResult.results[0].result.timings.total
const code = measurementResult.results[0].result.statusCode
const body = measurementResult.results[0].result.rawBody
const country = measurementResult.results[0].probe.country
const err = await httpResponseBasicCheck(monitor, code, () => body)
if (err !== null) {
console.log(`${monitor.name} didn't pass response check: ${err}`)
}
// TODO: missing headers
return {
location: country,
status: { // TODO TLS err
ping: time,
up: err === null,
err: err ?? ''
}
}
}
export async function getStatus( export async function getStatus(
monitor: MonitorTarget monitor: MonitorTarget
): Promise<{ ping: number; up: boolean; err: string }> { ): Promise<{ ping: number; up: boolean; err: string }> {
@ -56,58 +192,12 @@ export async function getStatus(
console.log(`${monitor.name} responded with ${response.status}`) console.log(`${monitor.name} responded with ${response.status}`)
status.ping = Date.now() - startTime status.ping = Date.now() - startTime
if (monitor.expectedCodes) { const err = await httpResponseBasicCheck(monitor, response.status, response.text)
if (!monitor.expectedCodes.includes(response.status)) { if (err !== null) {
console.log(`${monitor.name} expected ${monitor.expectedCodes}, got ${response.status}`) console.log(`${monitor.name} didn't pass response check: ${err}`)
status.up = false
status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${
response.status
}`
return status
}
} else {
if (response.status < 200 || response.status > 299) {
console.log(`${monitor.name} expected 2xx, got ${response.status}`)
status.up = false
status.err = `Expected codes: 2xx, Got: ${response.status}`
return status
}
} }
status.up = err === null
if (monitor.responseKeyword || monitor.responseForbiddenKeyword) { status.err = err ?? ''
// Only read response body if we have a keyword to check
const responseBody = await response.text()
// MUST contain responseKeyword
if (monitor.responseKeyword && !responseBody.includes(monitor.responseKeyword)) {
console.log(
`${monitor.name} expected keyword ${
monitor.responseKeyword
}, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`
)
status.up = false
status.err = "HTTP response doesn't contain the configured keyword"
return status
}
// MUST NOT contain responseForbiddenKeyword
if (
monitor.responseForbiddenKeyword &&
responseBody.includes(monitor.responseForbiddenKeyword)
) {
console.log(
`${monitor.name} forbidden keyword ${
monitor.responseForbiddenKeyword
}, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`
)
status.up = false
status.err = 'HTTP response contains the configured forbidden keyword'
return status
}
}
status.up = true
status.err = ''
} catch (e: any) { } catch (e: any) {
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`) console.log(`${monitor.name} errored with ${e.name}: ${e.message}`)
if (e.name === 'AbortError') { if (e.name === 'AbortError') {