/
termisnal.go
130 lines (115 loc) · 3.63 KB
/
termisnal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
Copyright 2021 The Pixiu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/gorilla/websocket"
"k8s.io/client-go/tools/remotecommand"
)
// TerminalMessage 定义了终端和容器 shell 交互内容的格式 Operation 是操作类型
// Data 是具体数据内容 Rows和Cols 可以理解为终端的行数和列数,也就是宽、高
type TerminalMessage struct {
Operation string `json:"operation"`
Data string `json:"data"`
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
}
// TerminalSession 定义 TerminalSession 结构体,实现 PtyHandler 接口 // wsConn 是 websocket 连接 // sizeChan 用来定义终端输入和输出的宽和高 // doneChan 用于标记退出终端
type TerminalSession struct {
wsConn *websocket.Conn
sizeChan chan remotecommand.TerminalSize
doneChan chan struct{}
}
// NewTerminalSession 该方法用于升级 http 协议至 websocket,并new一个 TerminalSession 类型的对象返回
func NewTerminalSession(w http.ResponseWriter, r *http.Request) (*TerminalSession, error) {
// 初始化 Upgrader 类型的对象,用于http协议升级为 websocket 协议
upgrader := &websocket.Upgrader{
HandshakeTimeout: time.Second * 2,
// 检测请求来源
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: []string{r.Header.Get("Sec-WebSocket-Protocol")},
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return nil, err
}
session := &TerminalSession{
wsConn: conn,
sizeChan: make(chan remotecommand.TerminalSize),
doneChan: make(chan struct{}),
}
return session, nil
}
// 用于读取web端的输入,接收web端输入的指令内容
func (t *TerminalSession) Read(p []byte) (int, error) {
_, message, err := t.wsConn.ReadMessage()
if err != nil {
return copy(p, "\u0004"), err
}
// 反序列化
var msg TerminalMessage
if err = json.Unmarshal(message, &msg); err != nil {
return copy(p, "\u0004"), err
}
// 逻辑判断
switch msg.Operation {
// 如果是标准输入
case "stdin":
return copy(p, msg.Data), nil
// 窗口调整大小
case "resize":
t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}
return 0, nil
// ping 无内容交互
case "ping":
return 0, nil
default:
return copy(p, "\u0004"), fmt.Errorf("unknown message type")
}
}
// 写数据的方法,拿到 api-server 的返回内容,向web端输出
func (t *TerminalSession) Write(p []byte) (int, error) {
msg, err := json.Marshal(TerminalMessage{
Operation: "stdout",
Data: string(p),
})
if err != nil {
return 0, err
}
if err = t.wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
return 0, err
}
return len(p), nil
}
// Done 标记关闭doneChan,关闭后触发退出终端
func (t *TerminalSession) Done() {
close(t.doneChan)
}
// Close 用于关闭websocket连接
func (t *TerminalSession) Close() error {
return t.wsConn.Close()
}
// Next 获取web端是否resize,以及是否退出终端
func (t *TerminalSession) Next() *remotecommand.TerminalSize {
select {
case size := <-t.sizeChan:
return &size
case <-t.doneChan:
return nil
}
}