在集成Tenable漏洞管理平台时,部分报表组件会请求一个名为Mock2Image的图像端点,用来展示漏洞分布、风险趋势等图形化内容。该端点通常返回PNG或SVG格式的图像,但在开发和测试环境中,Tenable真实扫描引擎可能并未接入,或者扫描数据量不足以生成有意义的图表。此时如果直接调用真实接口,前端会因为404或空响应而渲染失败。为了保持开发进度,我们可以使用Node.js搭建一个轻量级的Mock服务,模拟Tenable Mock2Image接口的行为,根据请求参数动态生成假图像。

一、理解Tenable Mock2Image的请求与响应模型
Mock2Image并不是Tenable官方公开的标准API名称,它更像是内部报表模块使用的一个图像渲染端点。从常见的调用方式来看,前端会通过GET请求向类似/api/mock2image的路径发送参数,例如chartType=severity、timeRange=30d、width=600、height=400等。服务端解析这些参数后,返回一张符合尺寸和类型要求的图像。响应头中的Content-Type通常是image/png或image/svg+xml,如果前端只用<img>标签加载,SVG也能正常显示。
在实际项目中,我们并不需要完全复刻Tenable的私有实现,只要保证请求能收到一张合理的图像即可。关键点在于参数解析的宽容性和图像内容的可辨识性。比如,当chartType为severity时,图像应该呈现出不同严重等级的漏洞数量对比;当chartType为trend时,则应该展示一条随时间变化的折线。这样前端开发者就能在本地看到真实的渲染效果,而不会因为假图过于简单导致样式错乱。
为了模拟响应,我们可以先构建一个简单的HTTP服务,接收查询字符串,并打印出所有参数。下面的代码展示了使用Node.js原生http模块搭建的基础框架,它只返回一段简单的SVG文本,但足以验证请求链路是否打通。
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const query = parsedUrl.query;
console.log('请求参数:', query);
const svg = `<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="100" fill="#f0f0f0"/>
<text x="10" y="50" font-size="14">Mock2Image OK</text>
</svg>`;
res.writeHead(200, {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'no-store'
});
res.end(svg);
});
server.listen(3000, () => {
console.log('Mock服务运行在 http://localhost:3000');
});
这段代码的核心是手动解析req.url中的查询参数,并把它们打印到控制台。通过观察实际前端请求携带了哪些字段,我们就能确定模拟图像需要支持哪些动态变化。需要注意的是,返回SVG时必须设置正确的Content-Type,否则浏览器可能直接把文本渲染成乱码。此外,开发阶段建议关闭缓存,避免修改代码后浏览器仍然加载旧图像。
二、Express框架下构建Mock2Image核心服务
原生http模块虽然可行,但参数处理、路由匹配和错误捕获都比较繁琐。使用Express可以大幅减少样板代码,同时保持足够的灵活性。在Express中,我们可以通过req.query直接获取解析好的参数对象,并根据参数类型调用不同的图像生成函数。
下面是一个完整的Express版本Mock2Image服务。它定义了两个路由:/api/mock2image负责生成漏洞严重性分布图,/api/mock2image/trend负责生成趋势折线图。每个路由都会校验必要参数,如果缺失则返回400状态码,并附带一段可读的错误信息。
const express = require('express');
const app = express();
function generateSeverityChart(width, height) {
const data = [
{ label: 'Critical', value: 12, color: '#d9534f' },
{ label: 'High', value: 28, color: '#f0ad4e' },
{ label: 'Medium', value: 45, color: '#5bc0de' },
{ label: 'Low', value: 20, color: '#5cb85c' }
];
const barWidth = width / data.length * 0.6;
const gap = width / data.length;
let bars = '';
data.forEach((item, index) => {
const barHeight = (item.value / 50) * height * 0.8;
const x = index * gap + (gap - barWidth) / 2;
const y = height - barHeight - 30;
bars += `<rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}" fill="${item.color}"/>`;
bars += `<text x="${x + barWidth / 2}" y="${height - 10}" text-anchor="middle" font-size="12">${item.label}</text>`;
});
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#ffffff"/>
${bars}
</svg>`;
}
app.get('/api/mock2image', (req, res) => {
const width = parseInt(req.query.width, 10) || 600;
const height = parseInt(req.query.height, 10) || 400;
if (width < 100 || height < 100) {
return res.status(400).send('width和height必须大于100');
}
const svg = generateSeverityChart(width, height);
res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Cache-Control', 'no-store');
res.send(svg);
});
app.listen(3000, () => {
console.log('Express Mock2Image服务已启动');
});
上述代码中,generateSeverityChart函数根据传入的宽高动态计算每个柱状图的坐标和尺寸。这里使用固定数据,因为Mock阶段不需要真实扫描结果,只要图像形态符合预期即可。柱状图的高度与数值成正比,颜色按照严重等级区分,这样前端开发者可以直观地看到不同颜色的柱形分布。如果前端需要PNG格式,可以在返回SVG的基础上使用第三方库进行转换,但SVG本身已经足够满足绝大多数浏览器渲染需求。
三、动态注入漏洞数据并模拟趋势变化
固定数据的图表虽然能跑通流程,但无法覆盖前端组件在不同数据量下的表现。比如Critical漏洞数量为0时图表是否还能正常显示,或者数值过大时坐标轴是否溢出。为了更好地模拟真实场景,我们可以让Mock2Image接口接受一个可选的seed参数,用相同的seed生成确定性的伪随机数据。这样前端测试时可以反复复现同一种图像状态。
更实用的做法是维护一个内存中的漏洞数据数组,每次请求时根据当前时间戳或请求次数做轻微变化,模拟扫描结果的持续更新。下面的代码实现了趋势折线图生成函数,它接收一个days参数,生成过去N天每天发现的漏洞总数。折线图的绘制需要计算多个点的坐标,然后用<polyline>标签连接。
function generateTrendChart(width, height, days) {
const points = [];
const startX = 50;
const endX = width - 30;
const chartWidth = endX - startX;
const maxVal = 100;
const stepX = chartWidth / (days - 1);
const baselineY = height - 40;
const chartHeight = height - 80;
for (let i = 0; i < days; i++) {
// 使用正弦函数叠加随机波动,让折线看起来更自然
const noise = Math.sin(i / 2) * 15 + (i % 3) * 8;
const val = Math.max(5, Math.min(maxVal, 40 + noise));
const x = startX + i * stepX;
const y = baselineY - (val / maxVal) * chartHeight;
points.push(`${x},${y}`);
}
const polylinePoints = points.join(' ');
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#fafafa"/>
<line x1="${startX}" y1="${baselineY}" x2="${endX}" y2="${baselineY}" stroke="#ccc" stroke-width="1"/>
<polyline points="${polylinePoints}" fill="none" stroke="#337ab7" stroke-width="2"/>
<text x="${width / 2}" y="${height - 10}" text-anchor="middle" font-size="12">过去${days}天漏洞趋势</text>
</svg>`;
}
这段代码首先确定了绘图区域的边界,然后根据天数计算每个数据点的横坐标。纵坐标则基于一个基础值加上正弦波动,保证折线既有起伏又不会出现负数。生成的图像包含一条横向基准线和一条蓝色折线,底部还有简单的文字说明。即使没有真实数据,前端也能看到清晰的趋势走向。通过调整days参数,开发者可以测试图表在7天、30天和90天等不同时间跨度下的布局表现。
如果需要模拟不同严重等级的漏洞数量,可以在查询参数中加入severity数组或逗号分隔的数值。Express会自动把重复参数解析成数组,这让Mock接口能响应更复杂的请求。例如,前端可能会发送?severity=critical&severity=high,服务端就可以根据数组长度动态调整图表结构。
四、常见问题与生产环境适配建议
在开发Mock2Image服务时,最常见的问题是Content-Type设置错误导致图像无法显示。如果返回的是SVG文本但头信息仍然是text/html,浏览器会尝试解析HTML而不是渲染图形。另一个容易忽略的点是跨域问题。当Mock服务与前端运行在不同端口时,需要在响应头中添加Access-Control-Allow-Origin字段,否则浏览器会拦截图像加载。下面这段代码展示了如何统一设置CORS头和缓存策略。
app.use('/api/mock2image', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Cache-Control', 'no-store');
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
对于生产环境,Mock2Image不应该直接对外暴露,它只服务于内部测试和前端联调。可以通过环境变量控制路由是否启用,例如if (process.env.NODE_ENV === 'production')时禁用Mock路由,避免误将模拟数据当成真实结果。如果团队使用Docker部署,可以将Mock服务单独打包成一个容器,与真实Tenable适配器并行运行,通过反向代理按路径前缀转发请求。
最后,性能方面需要注意图像生成的计算量。如果每次请求都重新生成整个SVG字符串,对于并发较高的场景可能会产生不必要的CPU开销。可以引入简单的内存缓存,以请求参数为键存储生成结果,并设置较短的过期时间。这样相同的查询参数在短时间内直接命中缓存,减轻服务压力。由于Mock数据本身变化不大,缓存策略并不会影响测试效果。
通过以上步骤,我们就能用Node.js完整模拟Tenable Mock2Image接口,让前端报表开发不再依赖真实扫描环境。这个思路也可以扩展到其他需要动态图像响应的Mock场景中。
Node.jsTenable Mock2Image图像生成修改时间:2026-09-25 13:24:23