handler.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package terminal
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "sync"
  9. "time"
  10. "github.com/gorilla/websocket"
  11. "golang.org/x/crypto/ssh"
  12. )
  13. var upgrader = websocket.Upgrader{
  14. CheckOrigin: func(r *http.Request) bool {
  15. return true // 允许所有来源
  16. },
  17. }
  18. // TerminalSession 终端会话
  19. type TerminalSession struct {
  20. sshClient *ssh.Client
  21. sshSession *ssh.Session
  22. stdin io.Writer
  23. stdout io.Reader
  24. wsConn *websocket.Conn
  25. done chan struct{}
  26. mu sync.Mutex
  27. }
  28. // ConnectSSH 建立SSH连接并创建交互式Shell
  29. func ConnectSSH(host string, port int, username, password string) (*TerminalSession, error) {
  30. config := &ssh.ClientConfig{
  31. User: username,
  32. Auth: []ssh.AuthMethod{ssh.Password(password)},
  33. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  34. Timeout: 10 * time.Second,
  35. Config: ssh.Config{
  36. Ciphers: []string{
  37. "aes128-ctr", "aes192-ctr", "aes256-ctr",
  38. "aes128-gcm@openssh.com", "aes256-gcm@openssh.com",
  39. "chacha20-poly1305@openssh.com",
  40. "aes128-cbc", "aes256-cbc",
  41. },
  42. KeyExchanges: []string{
  43. "curve25519-sha256", "curve25519-sha256@libssh.org",
  44. "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
  45. "diffie-hellman-group14-sha256", "diffie-hellman-group16-sha512",
  46. "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1",
  47. },
  48. },
  49. }
  50. addr := fmt.Sprintf("%s:%d", host, port)
  51. client, err := ssh.Dial("tcp", addr, config)
  52. if err != nil {
  53. return nil, fmt.Errorf("SSH连接失败: %w", err)
  54. }
  55. session, err := client.NewSession()
  56. if err != nil {
  57. client.Close()
  58. return nil, fmt.Errorf("创建SSH会话失败: %w", err)
  59. }
  60. // 获取 stdin 管道
  61. stdin, err := session.StdinPipe()
  62. if err != nil {
  63. session.Close()
  64. client.Close()
  65. return nil, fmt.Errorf("获取stdin失败: %w", err)
  66. }
  67. // 获取 stdout 管道
  68. stdout, err := session.StdoutPipe()
  69. if err != nil {
  70. session.Close()
  71. client.Close()
  72. return nil, fmt.Errorf("获取stdout失败: %w", err)
  73. }
  74. // 也获取 stderr
  75. session.Stderr = io.Discard
  76. // 请求 PTY(xterm 终端)
  77. modes := ssh.TerminalModes{
  78. ssh.ECHO: 1,
  79. ssh.TTY_OP_ISPEED: 14400,
  80. ssh.TTY_OP_OSPEED: 14400,
  81. }
  82. if err := session.RequestPty("xterm", 40, 120, modes); err != nil {
  83. session.Close()
  84. client.Close()
  85. return nil, fmt.Errorf("请求PTY失败: %w", err)
  86. }
  87. // 启动 Shell
  88. if err := session.Shell(); err != nil {
  89. session.Close()
  90. client.Close()
  91. return nil, fmt.Errorf("启动Shell失败: %w", err)
  92. }
  93. return &TerminalSession{
  94. sshClient: client,
  95. sshSession: session,
  96. stdin: stdin,
  97. stdout: stdout,
  98. done: make(chan struct{}),
  99. }, nil
  100. }
  101. // HandleTerminal 处理WebSocket终端连接
  102. func HandleTerminal(w http.ResponseWriter, r *http.Request, host string, port int, username, password string) {
  103. // 建立 SSH 连接
  104. session, err := ConnectSSH(host, port, username, password)
  105. if err != nil {
  106. log.Printf("[终端] SSH连接失败 %s: %v", host, err)
  107. http.Error(w, err.Error(), http.StatusInternalServerError)
  108. return
  109. }
  110. // 升级为 WebSocket
  111. wsConn, err := upgrader.Upgrade(w, r, nil)
  112. if err != nil {
  113. session.Close()
  114. log.Printf("[终端] WebSocket升级失败: %v", err)
  115. return
  116. }
  117. session.wsConn = wsConn
  118. log.Printf("[终端] 已连接到 %s (%s)", host, username)
  119. // 启动 SSH -> WebSocket 的数据转发
  120. go session.sshToWs()
  121. // 启动 WebSocket -> SSH 的数据转发
  122. go session.wsToSsh()
  123. // 等待结束
  124. <-session.done
  125. session.Close()
  126. log.Printf("[终端] 已断开 %s", host)
  127. }
  128. // sshToWs 从SSH读取输出并转发到WebSocket
  129. func (s *TerminalSession) sshToWs() {
  130. buf := make([]byte, 8192)
  131. for {
  132. select {
  133. case <-s.done:
  134. return
  135. default:
  136. }
  137. n, err := s.stdout.Read(buf)
  138. if err != nil {
  139. log.Printf("[终端] SSH读取结束: %v", err)
  140. s.closeDone()
  141. return
  142. }
  143. if n > 0 {
  144. s.mu.Lock()
  145. err := s.wsConn.WriteMessage(websocket.TextMessage, buf[:n])
  146. s.mu.Unlock()
  147. if err != nil {
  148. log.Printf("[终端] WebSocket写入失败: %v", err)
  149. s.closeDone()
  150. return
  151. }
  152. }
  153. }
  154. }
  155. // wsToSsh 从WebSocket读取输入并转发到SSH
  156. func (s *TerminalSession) wsToSsh() {
  157. for {
  158. select {
  159. case <-s.done:
  160. return
  161. default:
  162. }
  163. _, message, err := s.wsConn.ReadMessage()
  164. if err != nil {
  165. log.Printf("[终端] WebSocket读取失败: %v", err)
  166. s.closeDone()
  167. return
  168. }
  169. if len(message) > 0 {
  170. // 解析JSON消息格式(xterm.js发送的)
  171. var msg map[string]interface{}
  172. if err := json.Unmarshal(message, &msg); err == nil {
  173. if input, ok := msg["input"].(string); ok {
  174. _, err := s.stdin.Write([]byte(input))
  175. if err != nil {
  176. log.Printf("[终端] SSH写入失败: %v", err)
  177. s.closeDone()
  178. return
  179. }
  180. }
  181. // 处理resize消息
  182. if msg["type"] == "resize" {
  183. if cols, ok := msg["cols"].(float64); ok {
  184. if rows, ok := msg["rows"].(float64); ok {
  185. _ = s.sshSession.WindowChange(int(rows), int(cols))
  186. }
  187. }
  188. }
  189. } else {
  190. // 原始二进制数据,直接写入
  191. _, err := s.stdin.Write(message)
  192. if err != nil {
  193. s.closeDone()
  194. return
  195. }
  196. }
  197. }
  198. }
  199. }
  200. // closeDone 安全关闭
  201. func (s *TerminalSession) closeDone() {
  202. s.mu.Lock()
  203. defer s.mu.Unlock()
  204. select {
  205. case <-s.done:
  206. // 已经关闭
  207. default:
  208. close(s.done)
  209. }
  210. }
  211. // Close 关闭会话
  212. func (s *TerminalSession) Close() {
  213. s.closeDone()
  214. if s.wsConn != nil {
  215. s.wsConn.Close()
  216. }
  217. if s.sshSession != nil {
  218. s.sshSession.Close()
  219. }
  220. if s.sshClient != nil {
  221. s.sshClient.Close()
  222. }
  223. }