Golang实现的交互Shell
最近在用Golang编写交互Shell的代码,涉及三端(浏览器、服务端、agent)需要一个WebSocket转发来实现它们之间的通讯。
问题与基本架构
如何实现WebSocket转发,打通浏览器和agent的通讯?
基本架构如下:
浏览器 -> 服务端 -> agent agent -> 服务端 -> 浏览器
如何将浏览器和agent对应起来?
我打算在服务端实现以下逻辑:
- 浏览器通过唯一标识连接到服务端的
handler1
,将连接和唯一标识存储在全局中(handler1
只接收浏览器的连接)。 - agent 通过唯一标识连接到服务端的
handler2
,通过唯一标识打通浏览器和agent。
这里不贴出浏览器和agent的具体代码。
- 浏览器通过唯一标识连接到服务端的
使用到的项目
- Linux交互Shell: creack/pty
- Windows交互Shell(可能低版本Windows不兼容): ActiveState/termtest/conpty
- 前端xterm: xtermjs/xterm.js
划重点
要想打通浏览器和agent的通讯,服务端作为中间人,需要进行以下操作。以下是服务端代码示例,非常优雅:
serverClosed := make(chan struct{})
clientClosed := make(chan struct{})
go copyConn(client.ws, ws, serverClosed, clientClosed)
go copyConn(ws, client.ws, clientClosed, serverClosed)
copyConn
函数
func copyConn(readConn, writeConn *websocket.Conn, readClosed, writeClosed chan struct{}) {
var rerr error
for {
var r io.Reader
var messageType int
messageType, r, rerr = readConn.NextReader()
if rerr != nil {
break
}
w, err := writeConn.NextWriter(messageType)
if err != nil {
break
}
if _, err := io.Copy(w, r); err != nil {
break
}
if err := w.Close(); err != nil {
break
}
}
readConn.Close()
close(readClosed)
if e, ok := rerr.(*websocket.CloseError); ok && e.Code != websocket.CloseAbnormalClosure {
var m []byte
if e.Code != websocket.CloseNoStatusReceived {
m = websocket.FormatCloseMessage(e.Code, e.Text)
}
err := writeConn.WriteMessage(websocket.CloseMessage, m)
if err == nil {
select {
case <-writeClosed:
return
case <-time.After(10 * time.Second):
}
}
}
writeConn.Close()
}
注意事项
仅限交流学习使用,如您在使用本工具或代码的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。如侵权请私聊删文。