Files
Spark/client/service/terminal/terminal.go
2022-03-16 16:26:28 +08:00

242 lines
5.0 KiB
Go

package terminal
import (
"Spark/client/common"
"Spark/modules"
"Spark/utils/cmap"
"bytes"
"encoding/hex"
"errors"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io"
"io/ioutil"
"os"
"os/exec"
"runtime"
"time"
)
type terminal struct {
lastInput int64
event string
cmd *exec.Cmd
stdout *io.ReadCloser
stderr *io.ReadCloser
stdin *io.WriteCloser
}
var terminals = cmap.New()
var (
errDataNotFound = errors.New(`no input found in packet`)
errDataInvalid = errors.New(`can not parse data in packet`)
errUUIDNotFound = errors.New(`can not find terminal identifier`)
)
func init() {
go healthCheck()
}
func InitTerminal(pack modules.Packet) error {
cmd := exec.Command(getTerminal())
stdout, err := cmd.StdoutPipe()
if err != nil {
cmd.Process.Kill()
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
cmd.Process.Kill()
return err
}
stdin, err := cmd.StdinPipe()
if err != nil {
cmd.Process.Kill()
return err
}
go func() {
for {
buffer := make([]byte, 512)
n, err := stdout.Read(buffer)
buffer = buffer[:n]
buffer, _ = gbkToUtf8(buffer)
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
`output`: hex.EncodeToString(buffer),
}}, pack, common.WSConn)
if err != nil {
common.SendCb(modules.Packet{Act: `quitTerminal`}, pack, common.WSConn)
break
}
}
}()
go func() {
for {
buffer := make([]byte, 512)
n, err := stderr.Read(buffer)
buffer = buffer[:n]
buffer, _ = gbkToUtf8(buffer)
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
`output`: hex.EncodeToString(buffer),
}}, pack, common.WSConn)
if err != nil {
common.SendCb(modules.Packet{Act: `quitTerminal`}, pack, common.WSConn)
break
}
}
}()
event := ``
if pack.Data != nil {
if val, ok := pack.Data[`event`]; ok {
event, _ = val.(string)
}
}
terminals.Set(pack.Data[`terminal`].(string), &terminal{
cmd: cmd,
event: event,
stdout: &stdout,
stderr: &stderr,
stdin: &stdin,
lastInput: time.Now().Unix(),
})
cmd.Start()
return nil
}
func InputTerminal(pack modules.Packet) error {
if pack.Data == nil {
return errDataNotFound
}
val, ok := pack.Data[`input`]
if !ok {
return errDataNotFound
}
hexStr, ok := val.(string)
if !ok {
return errDataNotFound
}
data, err := hex.DecodeString(hexStr)
if err != nil {
return errDataInvalid
}
val, ok = pack.Data[`terminal`]
if !ok {
return errUUIDNotFound
}
termUUID, ok := val.(string)
if !ok {
return errUUIDNotFound
}
val, ok = terminals.Get(termUUID)
if !ok {
common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn)
return nil
}
terminal, ok := val.(*terminal)
if !ok {
common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn)
return nil
}
terminal.lastInput = time.Now().Unix()
if len(data) == 1 && data[0] == '\x03' {
terminal.cmd.Process.Signal(os.Interrupt)
return nil
}
data, _ = utf8ToGbk(data)
(*terminal.stdin).Write(data)
return nil
}
func KillTerminal(pack modules.Packet) error {
if pack.Data == nil {
return errUUIDNotFound
}
val, ok := pack.Data[`terminal`]
if !ok {
return errUUIDNotFound
}
termUUID, ok := val.(string)
if !ok {
return errUUIDNotFound
}
val, ok = terminals.Get(termUUID)
if !ok {
common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn)
return nil
}
terminal, ok := val.(*terminal)
if !ok {
terminals.Remove(termUUID)
common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn)
return nil
}
doKillTerminal(terminal)
return nil
}
func doKillTerminal(terminal *terminal) {
(*terminal.stdout).Close()
(*terminal.stderr).Close()
(*terminal.stdin).Close()
if terminal.cmd.Process != nil {
terminal.cmd.Process.Kill()
}
}
func getTerminal() string {
switch runtime.GOOS {
case `windows`:
return `cmd.exe`
case `linux`:
return `sh`
case `darwin`:
return `sh`
default:
return `sh`
}
}
func gbkToUtf8(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GB18030.NewDecoder())
d, e := ioutil.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}
func utf8ToGbk(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GB18030.NewEncoder())
d, e := ioutil.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}
func healthCheck() {
const MaxInterval = 180
for now := range time.NewTicker(30 * time.Second).C {
timestamp := now.Unix()
// stores sessions to be disconnected
queue := make([]string, 0)
terminals.IterCb(func(uuid string, t interface{}) bool {
terminal, ok := t.(*terminal)
if !ok {
queue = append(queue, uuid)
return true
}
if timestamp-terminal.lastInput > MaxInterval {
queue = append(queue, uuid)
doKillTerminal(terminal)
}
return true
})
for i := 0; i < len(queue); i++ {
terminals.Remove(queue[i])
}
}
}