Files
certd/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts
T

127 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-05-28 00:57:52 +08:00
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";
2025-05-28 00:57:52 +08:00
export type SiteTestReq = {
host: string; // 只用域名部分
port?: number;
method?: string;
retryTimes?: number;
2025-05-28 00:57:52 +08:00
ipAddress?: string;
2025-06-30 23:48:00 +08:00
customDns?: DnsCustom;
};
export type SiteTestRes = {
certificate?: PeerCertificate;
};
2025-05-28 00:57:52 +08:00
export class SiteTester {
async test(req: SiteTestReq): Promise<SiteTestRes> {
const req_ = {...req}
delete req_.customDns
logger.info("测试站点:", JSON.stringify(req_));
2025-06-30 23:48:00 +08:00
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) {
2025-06-13 00:25:08 +08:00
logger.error(`测试站点出错,已超过最大重试次数(${maxRetryTimes}`, e.message);
throw e;
}
//指数退避
const time = 2 ** tryCount;
2025-06-13 00:25:08 +08:00
logger.error(`测试站点出错,${time}s后重试(${tryCount}/${maxRetryTimes})`, e);
await utils.sleep(time * 1000);
}
}
}
async doTestOnce(req: SiteTestReq): Promise<SiteTestRes> {
const options: any = merge(
{
port: 443,
2025-05-28 00:57:52 +08:00
method: "GET",
rejectUnauthorized: false
},
req
);
2025-05-28 00:57:52 +08:00
let customLookup = null
2025-05-28 00:57:52 +08:00
if (req.ipAddress) {
//使用固定的ip
const ipAddress = req.ipAddress;
2025-06-30 23:48:00 +08:00
options.headers = {
2025-05-28 13:57:31 +08:00
host: options.host,
//sni
servername: options.host
2025-06-30 23:48:00 +08:00
};
2025-05-28 13:57:31 +08:00
options.host = ipAddress;
}else if (req.customDns ) {
// 非ip address 请求时
const customDns = req.customDns
customLookup = async (hostname:string, options:any, callback)=> {
console.log(hostname, options);
2025-06-30 23:48:00 +08:00
// { 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);
}
}
2025-06-30 23:48:00 +08:00
const agentOptions:any = { keepAlive: false };
if (customLookup) {
agentOptions.lookup = customLookup
}
options.agent = new https.Agent(agentOptions);
2025-05-28 00:57:52 +08:00
// 创建 HTTPS 请求
2025-04-30 09:38:44 +08:00
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
});
2026-04-19 12:25:28 +08:00
}else{
logger.warn("证书信息为空");
resolve({
certificate: null
});
}
});
});
2025-05-28 00:57:52 +08:00
req.on("error", e => {
reject(e);
});
req.end();
});
return await requestPromise;
}
}
export const siteTester = new SiteTester();