@@ -38,88 +38,171 @@ func NewWsService(t *gotext.Locale, conf *config.Config, log *slog.Logger, ssh b
3838 }
3939}
4040
41- func (s * WsService ) Session (w http.ResponseWriter , r * http.Request ) {
42- req , err := Bind [request. ID ]( r )
41+ func (s * WsService ) Exec (w http.ResponseWriter , r * http.Request ) {
42+ ws , err := s . upgrade ( w , r )
4343 if err != nil {
44- Error ( w , http . StatusUnprocessableEntity , "%v " , err )
44+ s . log . Warn ( "[Websocket] upgrade exec ws error" , slog . Any ( "err " , err ) )
4545 return
4646 }
47- info , err := s .sshRepo .Get (req .ID )
47+ defer func (ws * websocket.Conn ) { _ = ws .CloseNow () }(ws )
48+
49+ // 第一条消息是命令
50+ ctx , cancel := context .WithCancel (r .Context ())
51+ defer cancel ()
52+
53+ _ , cmd , err := ws .Read (ctx )
4854 if err != nil {
49- Error ( w , http . StatusInternalServerError , " %v" , err )
55+ _ = ws . Close ( websocket . StatusNormalClosure , s . t . Get ( "failed to read command: %v" , err ) )
5056 return
5157 }
5258
53- ws , err := s . upgrade ( w , r )
59+ out , err := shell . ExecfWithPipe ( ctx , string ( cmd ) )
5460 if err != nil {
55- s . log . Warn ( "[Websocket] upgrade session ws error" , slog . Any ( "err " , err ))
61+ _ = ws . Close ( websocket . StatusNormalClosure , s . t . Get ( "failed to run command: %v " , err ))
5662 return
5763 }
58- defer func (ws * websocket.Conn ) { _ = ws .CloseNow () }(ws )
5964
60- client , err := ssh .NewSSHClient (info .Config )
65+ go func () {
66+ scanner := bufio .NewScanner (out )
67+ for scanner .Scan () {
68+ line := scanner .Text ()
69+ _ = ws .Write (ctx , websocket .MessageText , []byte (line ))
70+ }
71+ if err = scanner .Err (); err != nil {
72+ _ = ws .Close (websocket .StatusNormalClosure , s .t .Get ("failed to read command output: %v" , err ))
73+ }
74+ }()
75+
76+ s .readLoop (ctx , ws )
77+ }
78+
79+ // PTY 通用 PTY 命令执行
80+ // 前端发送第一条消息为要执行的命令,后端通过 PTY 执行并实时返回输出
81+ func (s * WsService ) PTY (w http.ResponseWriter , r * http.Request ) {
82+ ws , err := s .upgrade (w , r )
6183 if err != nil {
62- _ = ws . Close ( websocket . StatusNormalClosure , err . Error ( ))
84+ s . log . Warn ( "[Websocket] upgrade pty ws error" , slog . Any ( "err" , err ))
6385 return
6486 }
65- defer func (client * stdssh. Client ) { _ = client . Close () }(client )
87+ defer func (ws * websocket. Conn ) { _ = ws . CloseNow () }(ws )
6688
6789 ctx , cancel := context .WithCancel (r .Context ())
6890 defer cancel ()
6991
70- turn , err := ssh .NewTurn (ctx , ws , client )
92+ // 读取第一条消息获取要执行的命令
93+ _ , message , err := ws .Read (ctx )
94+ if err != nil {
95+ _ = ws .Close (websocket .StatusNormalClosure , s .t .Get ("failed to read command: %v" , err ))
96+ return
97+ }
98+
99+ command := string (message )
100+ if command == "" {
101+ _ = ws .Close (websocket .StatusNormalClosure , s .t .Get ("command is empty" ))
102+ return
103+ }
104+
105+ // 使用 PTY 执行命令
106+ ptyResult , err := shell .ExecWithPTY (ctx , command )
71107 if err != nil {
72- _ = ws .Close (websocket .StatusNormalClosure , err .Error ())
108+ _ = ws .Write (ctx , websocket .MessageBinary , []byte ("\r \n " + s .t .Get ("Failed to start command: %v" , err )+ "\r \n " ))
109+ _ = ws .Close (websocket .StatusNormalClosure , "" )
73110 return
74111 }
112+ defer func () { _ = ptyResult .Close () }()
75113
114+ // 读取 WebSocket 输入并转发到 PTY
76115 go func () {
77- defer turn .Close () // Handle 退出后关闭 SSH 连接,以结束 Wait 阶段
78- _ = turn .Handle (ctx )
116+ for {
117+ _ , data , err := ws .Read (ctx )
118+ if err != nil {
119+ // 通常是客户端关闭连接,取消运行
120+ cancel ()
121+ return
122+ }
123+ // 将用户输入写入 PTY
124+ if len (data ) > 0 {
125+ _ , _ = ptyResult .Write (data )
126+ }
127+ }
79128 }()
80129
81- turn .Wait ()
130+ // 读取 PTY 输出并发送到 WebSocket
131+ go func () {
132+ buf := make ([]byte , 4096 )
133+ for {
134+ n , err := ptyResult .Read (buf )
135+ if n > 0 {
136+ if writeErr := ws .Write (ctx , websocket .MessageBinary , buf [:n ]); writeErr != nil {
137+ s .log .Warn ("[Websocket] write pty output error" , slog .Any ("err" , writeErr ))
138+ cancel ()
139+ return
140+ }
141+ }
142+ if err != nil {
143+ if shell .IsPTYError (err ) != nil {
144+ s .log .Debug ("[Websocket] pty read error" , slog .Any ("err" , err ))
145+ }
146+ return
147+ }
148+ }
149+ }()
150+
151+ // 等待命令完成
152+ if err = ptyResult .Wait (); err != nil {
153+ // 如果是因为被杀死,不输出错误
154+ if ctx .Err () == nil {
155+ _ = ws .Write (ctx , websocket .MessageBinary , []byte ("\r \n " + s .t .Get ("Command failed: %v" , err )+ "\r \n " ))
156+ }
157+ }
158+
159+ _ = ws .Close (websocket .StatusNormalClosure , "" )
82160}
83161
84- func (s * WsService ) Exec (w http.ResponseWriter , r * http.Request ) {
162+ func (s * WsService ) Session (w http.ResponseWriter , r * http.Request ) {
163+ req , err := Bind [request.ID ](r )
164+ if err != nil {
165+ Error (w , http .StatusUnprocessableEntity , "%v" , err )
166+ return
167+ }
168+ info , err := s .sshRepo .Get (req .ID )
169+ if err != nil {
170+ Error (w , http .StatusInternalServerError , "%v" , err )
171+ return
172+ }
173+
85174 ws , err := s .upgrade (w , r )
86175 if err != nil {
87- s .log .Warn ("[Websocket] upgrade exec ws error" , slog .Any ("err" , err ))
176+ s .log .Warn ("[Websocket] upgrade session ws error" , slog .Any ("err" , err ))
88177 return
89178 }
90179 defer func (ws * websocket.Conn ) { _ = ws .CloseNow () }(ws )
91180
92- // 第一条消息是命令
93- ctx , cancel := context .WithCancel (r .Context ())
94- defer cancel ()
95-
96- _ , cmd , err := ws .Read (ctx )
181+ sshClient , err := ssh .NewSSHClient (info .Config )
97182 if err != nil {
98- _ = ws .Close (websocket .StatusNormalClosure , s . t . Get ( "failed to read command: %v" , err ))
183+ _ = ws .Close (websocket .StatusNormalClosure , err . Error ( ))
99184 return
100185 }
186+ defer func (sshClient * stdssh.Client ) { _ = sshClient .Close () }(sshClient )
101187
102- out , err := shell .ExecfWithPipe (ctx , string (cmd ))
188+ ctx , cancel := context .WithCancel (r .Context ())
189+ defer cancel ()
190+
191+ turn , err := ssh .NewTurn (ctx , ws , sshClient )
103192 if err != nil {
104- _ = ws .Close (websocket .StatusNormalClosure , s . t . Get ( "failed to run command: %v" , err ))
193+ _ = ws .Close (websocket .StatusNormalClosure , err . Error ( ))
105194 return
106195 }
107196
108197 go func () {
109- scanner := bufio .NewScanner (out )
110- for scanner .Scan () {
111- line := scanner .Text ()
112- _ = ws .Write (ctx , websocket .MessageText , []byte (line ))
113- }
114- if err = scanner .Err (); err != nil {
115- _ = ws .Close (websocket .StatusNormalClosure , s .t .Get ("failed to read command output: %v" , err ))
116- }
198+ defer turn .Close () // Handle 退出后关闭 SSH 连接,以结束 Wait 阶段
199+ _ = turn .Handle (ctx )
117200 }()
118201
119- s . readLoop ( ctx , ws )
202+ turn . Wait ( )
120203}
121204
122- // ContainerTerminal 容器终端 WebSocket 处理
205+ // ContainerTerminal 容器终端
123206func (s * WsService ) ContainerTerminal (w http.ResponseWriter , r * http.Request ) {
124207 req , err := Bind [request.ContainerID ](r )
125208 if err != nil {
@@ -155,7 +238,7 @@ func (s *WsService) ContainerTerminal(w http.ResponseWriter, r *http.Request) {
155238 turn .Wait ()
156239}
157240
158- // ContainerImagePull 镜像拉取 WebSocket 处理
241+ // ContainerImagePull 镜像拉取
159242func (s * WsService ) ContainerImagePull (w http.ResponseWriter , r * http.Request ) {
160243 ws , err := s .upgrade (w , r )
161244 if err != nil {
@@ -247,89 +330,6 @@ func (s *WsService) ContainerImagePull(w http.ResponseWriter, r *http.Request) {
247330 _ = ws .Close (websocket .StatusNormalClosure , "" )
248331}
249332
250- // PTY 通用 PTY 命令执行 WebSocket 处理
251- // 前端发送第一条消息为要执行的命令,后端通过 PTY 执行并实时返回输出
252- func (s * WsService ) PTY (w http.ResponseWriter , r * http.Request ) {
253- ws , err := s .upgrade (w , r )
254- if err != nil {
255- s .log .Warn ("[Websocket] upgrade pty ws error" , slog .Any ("err" , err ))
256- return
257- }
258- defer func (ws * websocket.Conn ) { _ = ws .CloseNow () }(ws )
259-
260- ctx , cancel := context .WithCancel (r .Context ())
261- defer cancel ()
262-
263- // 读取第一条消息获取要执行的命令
264- _ , message , err := ws .Read (ctx )
265- if err != nil {
266- _ = ws .Close (websocket .StatusNormalClosure , s .t .Get ("failed to read command: %v" , err ))
267- return
268- }
269-
270- command := string (message )
271- if command == "" {
272- _ = ws .Close (websocket .StatusNormalClosure , s .t .Get ("command is empty" ))
273- return
274- }
275-
276- // 使用 PTY 执行命令
277- ptyResult , err := shell .ExecWithPTY (ctx , command )
278- if err != nil {
279- _ = ws .Write (ctx , websocket .MessageBinary , []byte ("\r \n " + s .t .Get ("Failed to start command: %v" , err )+ "\r \n " ))
280- _ = ws .Close (websocket .StatusNormalClosure , "" )
281- return
282- }
283- defer func () { _ = ptyResult .Close () }()
284-
285- // 读取 WebSocket 输入并转发到 PTY
286- go func () {
287- for {
288- _ , data , err := ws .Read (ctx )
289- if err != nil {
290- // 通常是客户端关闭连接,取消运行
291- cancel ()
292- return
293- }
294- // 将用户输入写入 PTY
295- if len (data ) > 0 {
296- _ , _ = ptyResult .Write (data )
297- }
298- }
299- }()
300-
301- // 读取 PTY 输出并发送到 WebSocket
302- go func () {
303- buf := make ([]byte , 4096 )
304- for {
305- n , err := ptyResult .Read (buf )
306- if n > 0 {
307- if writeErr := ws .Write (ctx , websocket .MessageBinary , buf [:n ]); writeErr != nil {
308- s .log .Warn ("[Websocket] write pty output error" , slog .Any ("err" , writeErr ))
309- cancel ()
310- return
311- }
312- }
313- if err != nil {
314- if shell .IsPTYError (err ) != nil {
315- s .log .Debug ("[Websocket] pty read error" , slog .Any ("err" , err ))
316- }
317- return
318- }
319- }
320- }()
321-
322- // 等待命令完成
323- if err = ptyResult .Wait (); err != nil {
324- // 如果是因为被杀死,不输出错误
325- if ctx .Err () == nil {
326- _ = ws .Write (ctx , websocket .MessageBinary , []byte ("\r \n " + s .t .Get ("Command failed: %v" , err )+ "\r \n " ))
327- }
328- }
329-
330- _ = ws .Close (websocket .StatusNormalClosure , "" )
331- }
332-
333333func (s * WsService ) upgrade (w http.ResponseWriter , r * http.Request ) (* websocket.Conn , error ) {
334334 opts := & websocket.AcceptOptions {
335335 CompressionMode : websocket .CompressionContextTakeover ,
0 commit comments