在自动化测试和运维工具链中,经常需要把 SSH 会话的过程以可视化方式记录下来,比如生成终端截图用于文档、报告或失败日志。传统做法是启动真实 SSH 会话后借助屏幕截图工具抓取,但这种方式依赖图形环境,难以在 CI 容器中稳定运行。SSH Mock2Image 的思路则是在 Node.js 进程内模拟一个 SSH 服务端,通过伪终端执行命令并捕获完整输出,最后将输出内容绘制成图片。由于整个过程不依赖外部终端模拟器,可重复性和跨平台能力都更强。下文将拆解这一流程的三个关键环节:SSH 服务端模拟、伪终端输出采集、以及终端图像渲染。

SSH 服务端模拟:握手与认证
SSH 协议可以粗略分为传输层、认证层和连接层。传输层负责密钥交换和加密通道,认证层验证客户端身份,连接层则承载会话、端口转发等逻辑。在 Node.js 中实现一个 SSH 服务端并不需要从零编写协议解析器,ssh2 库提供了完整的 Server 模式,支持密码、公钥等多种认证方式。开发者只需要生成主机密钥、监听 TCP 端口,并在回调中处理 connection 事件即可。
下面是一个最小化的 SSH mock 服务端示例,它监听 2222 端口,接受一个固定的用户名和密码,并允许客户端创建 session 通道。示例中使用了 ssh2 的 Server 对象,认证回调里可以根据实际需求扩展为数据库校验或密钥指纹比对。
const { Server } = require('ssh2');
const fs = require('fs');
const server = new Server({
hostKeys: [fs.readFileSync('ssh_host_rsa_key')]
}, (client) => {
console.log('Client connected');
client.on('authentication', (ctx) => {
if (ctx.method === 'password' && ctx.username === 'mock' && ctx.password === '123456') {
ctx.accept();
} else if (ctx.method === 'publickey' && ctx.key.algo === 'ssh-rsa') {
ctx.accept();
} else {
ctx.reject();
}
}).on('ready', () => {
client.on('session', (accept, reject) => {
const session = accept();
session.on('pty', (accept, reject, info) => {
accept();
});
session.on('shell', (accept, reject) => {
accept();
});
});
});
});
server.listen(2222, '0.0.0.0', () => {
console.log('SSH mock server listening on port 2222');
});
认证阶段是 SSH mock 最容易出现安全问题的位置。测试环境中习惯使用固定口令,但如果 mock 服务被暴露到公网,就可能成为攻击入口。建议在生产或准生产环境中限制监听地址为 127.0.0.1,或者使用临时密钥和一次性密码,避免长期运行同一个 mock 实例。
伪终端与输出采集
SSH 客户端通常在连接后请求一个 PTY(伪终端),因为只有 PTY 才能提供行编辑、命令历史、颜色输出等交互能力。如果服务端直接返回一个裸数据流,很多客户端会表现得像非交互式命令,无法正确显示终端提示符。node-pty 是 Node.js 中创建伪终端的成熟方案,它底层调用系统 PTY 接口,可以启动 bash、sh 或自定义命令,并通过事件回调读写数据。
在 SSH session 的 shell 事件中,我们可以使用 node-pty 创建一个 bash 进程,将客户端的输入写入 PTY,把 PTY 的输出转发给 SSH 客户端,同时把输出保存到内存缓冲区,供后续渲染图像使用。下面代码展示了完整的双向转发和采集逻辑。
const pty = require('node-pty');
session.on('pty', (accept, reject, info) => {
accept();
});
session.on('shell', (accept, reject) => {
const shellProcess = pty.spawn('bash', [], {
name: 'xterm-color',
cols: info.cols || 80,
rows: info.rows || 24,
cwd: process.cwd(),
env: process.env
});
const outputBuffer = [];
shellProcess.onData((data) => {
// 转发给 SSH 客户端
session.write(data);
// 采集输出用于后续图像渲染
outputBuffer.push(data);
});
session.on('data', (data) => {
shellProcess.write(data);
});
session.on('close', () => {
shellProcess.kill();
// 可以将 outputBuffer 写入文件或传递给渲染器
console.log('Session closed, captured bytes:', Buffer.concat(outputBuffer).length);
});
accept();
});
这里需要特别注意 ANSI 转义序列的处理。真实终端的输出包含颜色、光标移动、清屏等控制码,原始数据直接存下来后再渲染成图片时,如果不对这些序列进行解析,最终的图片会显示成乱码。一个折中方案是在采集阶段就用第三方库把 ANSI 序列转换成 HTML 或纯文本,但这样会丢失颜色信息。更完整的做法是保留原始 ANSI 数据,在渲染阶段再解析,从而还原终端配色和格式。
将 SSH 会话渲染为图像
把采集到的 SSH 会话输出变成图片有两种主流思路。第一种是使用 node-canvas 直接在内存中绘制,先绘制终端背景,再逐行绘制文本,这种方式轻量、依赖少,适合生成固定风格的黑白或自定义配色图片。第二种是启动 headless 浏览器,把输出内容渲染成 HTML 终端模拟器样式再截图,效果更接近真实终端,但资源消耗更大。对于大多数 mock 测试场景,node-canvas 已经足够。
下面代码使用 node-canvas 将一段纯文本输出绘制成终端风格的 PNG 图片。它按行分割文本,计算画布高度,绘制深色背景和等宽字体文本。实际项目中可以先对输出做 ANSI 解析,提取出每行的文字和颜色信息,再逐段绘制。
const { createCanvas } = require('canvas');
function renderSessionToImage(sessionText, outputPath) {
const fontSize = 14;
const lineHeight = 18;
const padding = 16;
const lines = sessionText.split('\n');
const canvasWidth = 800;
const canvasHeight = padding * 2 + lines.length * lineHeight + 20;
const canvas = createCanvas(canvasWidth, canvasHeight);
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#1e1e1e';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = '#cccccc';
ctx.font = `bold ${fontSize}px Consolas, monospace`;
lines.forEach((line, index) => {
ctx.fillText(line, padding, padding + index * lineHeight + lineHeight);
});
const fs = require('fs');
fs.writeFileSync(outputPath, canvas.toBuffer('image/png'));
}
const captured = 'mock@nodejs:~$ uname -a\nLinux mock-host 5.15.0-91-generic #101-Ubuntu SMP\n';
renderSessionToImage(captured, 'session.png');
渲染阶段还需要考虑字体兼容性。不同操作系统上 Consolas 并不总是存在,中文内容也需要额外的 CJK 字体支持。如果输出包含中文,建议在 canvas 中注册 Noto Sans Mono CJK 等开源字体,否则中文会显示为方框。另外,行宽和换行策略也要与 PTY 的列数保持一致,避免图片中的文本被截断或错位。
完整流程整合与优化建议
将上述三个模块串联后,完整流程可以概括为:启动 SSH mock 服务端,等待客户端连接,认证通过后创建 PTY,客户端执行命令,PTY 输出被采集到缓冲区,会话关闭后将缓冲区内容交给图像渲染器生成 PNG。这个流程可以封装成一个异步函数,暴露给测试框架或 CLI 工具使用。例如在 Mocha 测试中启动 mock 服务,执行一系列命令,最后将生成的图片保存到测试报告目录。
实际使用中有几个值得优化的点。第一,输出缓冲区不要无限增长,可以设置最大行数或字节数,超过后丢弃最旧的数据。第二,如果每个会话都要生成图片,可以把渲染任务放入队列,避免并发绘制导致内存峰值。第三,对于包含 ANSI 颜色和样式的输出,建议引入专门的 ANSI 解析库,把样式信息映射为 canvas 的填充色和字体粗细,这样生成的图片更接近真实终端效果。
SSH Mock2Image 的价值在于它把协议模拟、终端交互和图像生成组合成了一个可复用的 Node.js 方案。除了生成测试记录,它还可以用于构建在线 SSH 演示环境、自动化生成运维文档截图,甚至为聊天机器人提供终端会话的可视化回放。只要把握好 SSH 握手、PTY 缓冲和 ANSI 渲染这几个关键点,就能稳定地实现这一能力。
Node.jsSSH Mock2ImageSSH协议修改时间:2026-08-24 04:22:02