背景
前段时间,需要实现“web端的terminal”,后端技术是用websocket发送和处理数据。前端需要做的只是将用户输入的命令发送给后端,后端再将执行后的结果发送给前端。说白了就是说,前端只负责发送数据和显示结果数据(纯展示)。
之前的同事大概是基于xterm3.0的版本实现的,而且是把整个依赖包下载到本地;另外,网上关于xterm的教程真是少之又少,又看到xterm4.0在基于3.0的基础上修复了很多问题。出于提高项目的可维护性,于是就升级为xterm4.0
实现
安装
- 安装xterm相关
yarn add xterm xterm-addon-attach xterm-addon-fit xterm-addon-search xterm-addon-web-links
xterm-addon-attach: 通过 Websocket 附加到运行进程的服务器
xterm-addon-fit: 将终端适配到html容器中
xterm-addon-search: 添加搜索功能
xterm-addon-web-links: 添加 Web 链接检测和交互
- 安装rxjs相关
关于rxjs相关的知识就不在这个文章中表述,感兴趣的可以阅读RxJS - Subject
yarn add rxjs rxjs-hooks
实现步骤
- 创建terminal容器
<div id="terminal" style={{ width: '100%', height: 600 }} />
- 渲染terminal容器,并添加事件
renderTerminal = () => {
// 根据容器的高度动态设置行数rows
let container = document.getElementById('terminal');
let height = container.clientHeight;
let rows = parseInt(height / 18); //18是字体高度,根据需要自己修改
// 渲染Terminal,并添加事件
this.term = new XTerminal({
rendererType: 'canvas',
fontSize: 14,
fontFamily: 'Consolas, "Courier New", monospace',
bellStyle: 'sound',
cursorBlink: true,
rows,
});
const fitAddon = this.fitAddon;
const webLinksAddon = this.webLinksAddon;
const term = this.term;
term.loadAddon(fitAddon);
term.loadAddon(webLinksAddon);
term.open(container);
fitAddon.fit();
term.onData((msg) => {
this.input$.next(
JSON.stringify({
data: msg,
})
);
});
// 内容全屏显示-窗口大小发生改变时
const resizeScreen = () => {
try {
fitAddon.fit();
// 窗口大小改变时触发xterm的resize方法,向后端发送行列数,格式由后端决定
term.onResize(() => {
this.input$.next({ resize: [this.term.cols, this.term.rows] });
});
} catch (e) {
console.log('e', e.message);
}
};
window.addEventListener('resize', resizeScreen);
};
- 建立 websocket 连接,并绑定onopen、onmessage、onclose事件
setupConn = () => {
const { id, token } = this.props
// 建立 websocket 连接,并绑定事件
let url = `wss://${sshUrl()}/ws?id=${id}&token=${token}`
this.conn = new WebSocket(url)
this.conn.onopen = () => {
const startData = { Op: 'bind' }
this.connected = true
this.input$.next(startData)
}
this.conn.onmessage = (event) => {
this.output$.next(JSON.parse(event.data))
}
this.conn.onclose = this.closeConn
}
- 订阅 input 对象并将 output$ 接收的数据写入 terminal 容器中
// 订阅input$对象
this.subscription = this.input$.subscribe((data) => {
this.sendMsg(data)
})
this.subscription.add(
// 订阅output$对象, 将output$接收的数据写入terminal容器中
this.output$.subscribe((data) => {
data && this.term.write(data)
})
)
- 监听terminal输入数据时,发送数据给websocket
term.onData((msg) => {
this.input$.next(
JSON.stringify({
data: msg,
})
)
})
- 在websocket的onmessage中输出数据
this.conn.onmessage = (event) => {
this.output$.next(JSON.parse(event.data))
}