
熟悉Linux或MacOS的同学都应该对终端并不陌生,诸如XShell、PuTTY这样的工具大家也应该很熟悉。如今各种私有云、公有云、虚拟化技术正在蓬勃发展,工作在集群之上的Web应用程序如果能够提供可以执行命令的终端窗口,将大大改善用户的使用体验。这篇文章就跟大家介绍一下我是如何利用Golang实现支持多节点多用户的Web终端。
需求分析
假设一个用户拥有若干台机器的管理权限,如果能够直接在Web界面上直接登陆任意节点,将给用户带来更好的使用体验。而实现这一功能的几大难点在于:
- 供用户访问的Web服务启动在管理节点上,其他节点与管理节点之间通过内网连接,不能被直接访问;
- 该系统支持多个用户同时操作;
- 每个用户应该以自己的用户身份登陆节点。
要实现上述需求,首先WebServer要支持代理请求,并且在计算节点上要能够动态的同时启动多个tty服务。本着“避免重复造轮子”的原则,我先上著名同性交友网站GayHub进行了一番搜寻,最后发现了两个不错的工具能够实现我的需求:
- GoTTY is a simple command line tool that turns your CLI tools into web applications.
- WebsocketProxy is an http.Handler interface build on top of gorilla/websocket that you can plug into your existing Go webserver to provide WebSocket reverse proxy.
具体实现
启动多个GoTTY实例
要支持多用户不同身份同时访问,显然我们必须启动多个GoTTY服务,因为无法预计用户的身份和规模,我们不能在每个节点上预先启动多个服务,而应该动态的接受管理节点的命令去启动服务。这一步很简单,最简单的方法是直接在管理节点上执行ssh命令,更好的方式则是在管理节点和计算节点之间实现消息队列的机制,计算节点上能获取到管理节点的命令并执行。这里我利用Redis在管理节点和计算节点之间实现了Agent,这不是这篇文章的重点,所以具体方法这里不赘述。
简而言之我已经实现了动态的在计算节点上接收命令以不同身份在不同的接口启动多个GoTTY。
反向代理GoTTY接口
下载GoTTY运行后发现这个程序做得非常巧妙,它利用go-bindata将所有静态文件都打包到了二进制文件中,程序编译后只有一个可执行程序,但却能够提供web服务,我通过Chrome调试工具查看了它的网络请求,摸清了它的基本原理,其前端核心是一个websocket请求。前端没有秘密,我只需要将静态文件拆分出来单独提供文件服务就可以使用。于是我尝试在一台计算节点上启动gotty服务,将接口设置了代理,拆分出静态文件,发现服务能正常工作,这意味着成功了一半。
在Web服务中proxy是很常见的,但大都是通过专门的软件去实现,并且这还是一个Websocket的接口,而WebsocketProxy恰好提供了这样的功能。利用WebsocketProxy我们只需要添加几行简单的代码:
1 | agentURL := fmt.Sprintf("ws://%s:%d/ws", nodeIP, ttyPort) |
接下来只需要动态新增这样一个Handler即可。
动态新增HTTP Handler
我的Web Server采用了Beego框架,仔细阅读框架文档和代码后发现Beego的App.Handler能用来添加一个新的HTTP Handler,如果我在一个已有的HTTP Handler中调用该方法,则可以实现动态的增加一个Handler。我们只需要在上述代码的基础上再添加两行:1
2wsURL := fmt.Sprintf("/ws_%s%d", nodeName, ttyPort)
beego.Handler(wsURL, proxyHandler)
如此一来我们就动态的新增了一个handler,并且它的url是根据节点名和端口号动态变化的,接下来我们只需要告诉前端去这个新的URL上建立websocket连接即可。
修改GoTTY前端代码
简单分析一下GoTTY的前端代码,发现作者真的具备很强的工具整合能力,自己手写的代码只有寥寥数行,其余均是直接采用第三方库。上面我们已经成功的在计算节点上启动了GoTTY并且动态的新增了Handler,但前端代码中请求的地址是不会变的,所以我们要对它进行修改,让前端知道应该去访问哪个接口。
原理很简单,我们让Golang的HTTP Handler跳转到GoTTY的静态文件上,并且带上参数,让前端能获取并使用这个参数就行了,在Go中新增:1
2redirectURL := fmt.Sprintf("/tty/index.html?NodeName=%s%d", nodeName, ttyPort)
n.Redirect(redirectURL, 302)
在gotty.js中新增:1
2
3
4
5
6
7
8
9var nodeName = getQueryString("NodeName")
var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + '/ws_' + nodeName;
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
大功告成~试试神奇的体验吧
后记
虽然上述代码已经比较完美的实现了一个多用户多节点的Web终端,但是还有一些小问题比如HTTP新增的路由没有动态的得到释放,在用户规模极大的情况下会影响WebServer的性能,查看Beego源码后发现没有提供删除Handler的功能,后续有时间我会尝试解决该问题后向Beego提交Pull Request。