Files
certd/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts
T
xiaojunnuo f9e1c46c45 chore: 1
2026-04-19 12:26:05 +08:00

127 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { logger, safePromise, utils } from "@certd/basic";
import { merge } from "lodash-es";
import https from "https";
import { PeerCertificate } from "tls";
import {DnsCustom} from "./dns-custom.js";
export type SiteTestReq = {
host: string; // 只用域名部分
port?: number;
method?: string;
retryTimes?: number;
ipAddress?: string;
customDns?: DnsCustom;
};
export type SiteTestRes = {
certificate?: PeerCertificate;
};
export class SiteTester {
async test(req: SiteTestReq): Promise<SiteTestRes> {
const req_ = {...req}
delete req_.customDns
logger.info("测试站点:", JSON.stringify(req_));
const maxRetryTimes = req.retryTimes == null ? 3 : req.retryTimes;
let tryCount = 0;
let result: SiteTestRes = {};
while (true) {
try {
result = await this.doTestOnce(req);
return result;
} catch (e) {
tryCount++;
if (tryCount > maxRetryTimes) {
logger.error(`测试站点出错,已超过最大重试次数(${maxRetryTimes}`, e.message);
throw e;
}
//指数退避
const time = 2 ** tryCount;
logger.error(`测试站点出错,${time}s后重试(${tryCount}/${maxRetryTimes})`, e);
await utils.sleep(time * 1000);
}
}
}
async doTestOnce(req: SiteTestReq): Promise<SiteTestRes> {
const options: any = merge(
{
port: 443,
method: "GET",
rejectUnauthorized: false
},
req
);
let customLookup = null
if (req.ipAddress) {
//使用固定的ip
const ipAddress = req.ipAddress;
options.headers = {
host: options.host,
//sni
servername: options.host
};
options.host = ipAddress;
}else if (req.customDns ) {
// 非ip address 请求时
const customDns = req.customDns
customLookup = async (hostname:string, options:any, callback)=> {
console.log(hostname, options);
// { family: undefined, hints: 0, all: true }
const res = await customDns.lookup(hostname, options)
console.log("custom lookup res:",res)
if (!res || res.length === 0) {
callback(new Error("没有解析到IP"));
}
callback(null, res);
}
}
const agentOptions:any = { keepAlive: false };
if (customLookup) {
agentOptions.lookup = customLookup
}
options.agent = new https.Agent(agentOptions);
// 创建 HTTPS 请求
const requestPromise = safePromise((resolve, reject) => {
const req = https.request(options, res => {
res.socket.end();
// 关闭响应
res.destroy();
});
// ✅ 关键:在 'socket' 事件中获取证书(握手完成后立即执行)
req.on('socket', (socket:any) => {
socket.on('secureConnect', () => {
// TLS握手完成,证书已经可用
const certificate = socket.getPeerCertificate();
if (certificate.subject) {
logger.info('证书获取成功', certificate.subject);
resolve({
certificate
});
}else{
logger.warn("证书信息为空");
resolve({
certificate: null
});
}
});
});
req.on("error", e => {
reject(e);
});
req.end();
});
return await requestPromise;
}
}
export const siteTester = new SiteTester();