文章目录
- 目的
- 方案说说明
- 实现过程与代码演示
- 前端页面(Xterm.js)
- 后端服务(Golang)
- 编译与测试
- 总结
目的
WebTerminal是一个比较有意思的功能,让我们可以脱离专门的软件,在浏览器中就可以与Linux设备进行交互。这篇文章将对这个功能做个简单的说明与演示。
例程地址如下:
https://github.com/NaisuXu/web-terminal-demo-with-golang-and-xterm
方案说说明
WebTerminal这个市面上叫法还是挺混乱的,大多数情况下还和WebSSH混在一起,两者本质上是有一些区别的。这里也把这两者混在一起列举下常见的几个方案:
上面这个情况常见都是用在运维管理等使用,可以在浏览器中管理多台设备或服务器。
上面这个情况也常用在运维管理等使用。
上面这个情况主要用于设备本身通过Web UI进行操作,想要更加高级的操作时可以直接通过终端进行。
这篇文章实现的是最后一种方式。另外因为我主要用在嵌入式Linux设备中,Web Server评估下来使用Golang是适应性和开发效率综合来说最好的。
实现过程与代码演示
前端页面(Xterm.js)
前端实现Terminal功能主要使用 Xterm.js
这个库,官方页面如下:
https://xtermjs.org/
VS Code中的中终端窗口就是使用这个库的,其中后端使用 node-pty 。
安装了Node.js的情况下新建项目目录并进入,然后初始化项目并下载xterm库:
npm init -y
npm install xterm
新建 index.html
文件,文件内容如下:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
const term = new Terminal();
term.open(document.querySelector('#terminal'));
</script>
</body>
</html>
这时候就可以测试 Xterm.js
的功能了,可以使用Terminal对象的 write
方法向终端窗口中输出内容,使用 onData
方法可以接收终端窗口中的键盘操作:
上面就是前端页面最核心的东西了,接下来只要处理与后端服务的数据交互即可。这里使用WebSocket方式,完成后的代码如下:
<!DOCTYPE html>
<html>
<head>
<title>WebTerminal</title>
<link rel="stylesheet" href="./node_modules/xterm/css/xterm.css" />
<script src="./node_modules/xterm/lib/xterm.js"></script>
</head>
<body>
<div style="width: 736px; height: 408px;">
<!-- 目前版本的 Xterm 5.1.0 默认串口大小 24x80 -->
<div id="terminal"></div>
</div>
<script>
const term = new Terminal();
term.open(document.querySelector('#terminal')); // 挂载
const socket = new WebSocket(`ws://${window.location.host}/webterminal`); // 创建WebSocket连接
term.onData((data) => { // 网页xterm窗口中有输入的数据
// console.log('term.onData:', data);
socket.send(data); // 通过WebSocket发送给服务器
});
socket.onmessage = (event) => { // 收到来自服务器的WebSocket消息
// console.log('socket.onmessage:', event.data);
term.write(event.data); // 向xterm对象写入数据
};
</script>
</body>
</html>
后端服务(Golang)
安装了Go的环境下,在刚才同目录中,使用下面命令初始化项目:
go mod init webterminal
新建 main.go
文件,文件内容如下:
package main
import (
"embed"
"net/http"
"os/exec"
"github.com/creack/pty"
"github.com/olahol/melody"
)
//go:embed index.html node_modules/xterm/css/xterm.css node_modules/xterm/lib/xterm.js
var content embed.FS
func main() {
c := exec.Command("sh") // 系统默认shell交互程序
f, err := pty.Start(c) // pty用于调用系统自带的虚拟终端
if err != nil {
panic(err)
}
m := melody.New() // melody用于实现WebSocket功能
go func() { // 处理来自虚拟终端的消息
for {
buf := make([]byte, 1024)
read, err := f.Read(buf)
if err != nil {
return
}
// fmt.Println("f.Read: ", string(buf[:read]))
m.Broadcast(buf[:read]) // 将数据发送给网页
}
}()
m.HandleMessage(func(s *melody.Session, msg []byte) { // 处理来自WebSocket的消息
// fmt.Println("m.HandleMessage: ", string(msg))
f.Write(msg) // 将消息写到虚拟终端
})
http.HandleFunc("/webterminal", func(w http.ResponseWriter, r *http.Request) {
m.HandleRequest(w, r) // 访问 /webterminal 时将转交给melody处理
})
fs := http.FileServer(http.FS(content))
http.Handle("/", http.StripPrefix("/", fs)) // 设置静态文件服务
http.ListenAndServe("0.0.0.0:22333", nil) // 启动服务器,访问 http://本机(服务器)IP地址:22333/ 进行测试
}
代码比较简单,核心功能就是调用系统中的虚拟终端,然后通过WebSocket和网页进行双向通讯。
上面代码只是用于功能测试使用的,这个代码有个问题是并没有对每个客户端进行单独处理,所以打开多个网页时操作都会同步响应。
使用下面命令安装相关依赖:
go mod tidy
编译与测试
在项目目录下创建 build.sh
文件,文件内容如下:
#!/bin/sh
GOOS=linux GOARCH=amd64 go build -o webterminal_linux_x86-64
GOOS=linux GOARCH=arm GOARM=7 go build -o webterminal_linux_armv7
GOOS=linux GOARCH=arm GOARM=5 go build -o webterminal_linux_armv5
后面三行命令分别用于编译生成三个平台的程序,你也可以根据需要来编写。
使用下面命令进行编译(注意window中需要使用 git-bash
等操作):
# chmod +x ./build.sh
./build.sh
将编译生成的程序拷贝到对应的平台中就可以进行测试了。
linux_x86-64 (Ununtu 22.04 AMD Ryzen 5 PRO 4650U):
linux_armv5 (NUC980 Linux buildroot 5.10.103+ armv5tejl GNU/Linux):
这里没有测试 linux_armv7 平台,有条件的话可以找一个树莓派(4B)进行测试。
总结
基于Golang和Xterm.js实现WebTerminal功能原理上比较简单,实际应用时更多的是需要根据需求进行功能和使用上的优化。