github.com/godaddy-x/freego@v1.0.156/node/node_websocket_client.go (about) 1 package node 2 3 import ( 4 "github.com/godaddy-x/freego/ex" 5 "github.com/godaddy-x/freego/utils" 6 "github.com/godaddy-x/freego/zlog" 7 "golang.org/x/net/websocket" 8 "net/http" 9 "time" 10 ) 11 12 type TokenAuth struct { 13 Token string 14 Secret string 15 } 16 17 type WsClient struct { 18 Origin string 19 Addr string 20 Path string 21 auth TokenAuth 22 conn *websocket.Conn 23 AuthCall func() (string, string, error) 24 ReceiveCall func(message []byte) (interface{}, error) // 如响应数据为nil,则不回复服务端 25 } 26 27 type Ping struct { 28 HealthCheck string `json:"healthCheck"` 29 } 30 31 func authReq(path string, requestObj interface{}, secret string, encrypted ...bool) ([]byte, error) { 32 if len(path) == 0 || requestObj == nil { 33 return nil, ex.Throw{Msg: "params invalid"} 34 } 35 jsonData, err := utils.JsonMarshal(requestObj) 36 if err != nil { 37 return nil, ex.Throw{Msg: "request data JsonMarshal invalid"} 38 } 39 jsonBody := &JsonResp{ 40 Code: http.StatusOK, 41 Data: jsonData, 42 Time: utils.UnixSecond(), 43 Nonce: utils.RandNonce(), 44 Plan: 0, 45 } 46 if len(encrypted) > 0 && encrypted[0] { 47 d, err := utils.AesEncrypt(jsonBody.Data.([]byte), secret, utils.AddStr(jsonBody.Nonce, jsonBody.Time)) 48 if err != nil { 49 return nil, ex.Throw{Msg: "request data AES encrypt failed"} 50 } 51 jsonBody.Data = d 52 jsonBody.Plan = 1 53 } else { 54 d := utils.Base64Encode(jsonBody.Data.([]byte)) 55 jsonBody.Data = d 56 } 57 jsonBody.Sign = utils.HMAC_SHA256(utils.AddStr(path, jsonBody.Data.(string), jsonBody.Nonce, jsonBody.Time, jsonBody.Plan), secret, true) 58 bytesData, err := utils.JsonMarshal(jsonBody) 59 if err != nil { 60 return nil, ex.Throw{Msg: "jsonBody data JsonMarshal invalid"} 61 } 62 return bytesData, nil 63 } 64 65 func authRes(client *WsClient, respBytes []byte) ([]byte, error) { 66 if len(respBytes) == 0 { 67 return nil, ex.Throw{Msg: "message is nil"} 68 } 69 respData := &JsonResp{ 70 Code: utils.GetJsonInt(respBytes, "c"), 71 Message: utils.GetJsonString(respBytes, "m"), 72 Data: utils.GetJsonString(respBytes, "d"), 73 Nonce: utils.GetJsonString(respBytes, "n"), 74 Time: int64(utils.GetJsonInt(respBytes, "t")), 75 Plan: int64(utils.GetJsonInt(respBytes, "p")), 76 Sign: utils.GetJsonString(respBytes, "s"), 77 } 78 if respData.Code != 200 { 79 if respData.Code > 0 { 80 return nil, ex.Throw{Code: respData.Code, Msg: respData.Message} 81 } 82 return nil, ex.Throw{Msg: respData.Message} 83 } 84 validSign := utils.HMAC_SHA256(utils.AddStr(client.Path, respData.Data, respData.Nonce, respData.Time, respData.Plan), client.auth.Secret, true) 85 if validSign != respData.Sign { 86 return nil, ex.Throw{Msg: "post response sign verify invalid"} 87 } 88 var err error 89 var dec []byte 90 if respData.Plan == 0 { 91 dec = utils.Base64Decode(respData.Data) 92 } else if respData.Plan == 1 { 93 dec, err = utils.AesDecrypt(respData.Data.(string), client.auth.Secret, utils.AddStr(respData.Nonce, respData.Time)) 94 if err != nil { 95 return nil, ex.Throw{Msg: "post response data AES decrypt failed"} 96 } 97 } else { 98 return nil, ex.Throw{Msg: "response sign plan invalid"} 99 } 100 return dec, nil 101 } 102 103 func (client *WsClient) StartWebsocket(auto bool, n ...int) { 104 for { 105 if err := client.initClient(); err != nil { 106 zlog.Error("websocket client error", 0, zlog.AddError(err)) 107 if !auto { 108 break 109 } 110 } 111 restart := time.Duration(10) 112 if len(n) > 0 && n[0] > 0 { 113 restart = time.Duration(n[0]) 114 } 115 time.Sleep(restart * time.Second) 116 } 117 } 118 119 func (client *WsClient) Ready() bool { 120 return client.conn != nil && len(client.auth.Secret) > 0 121 } 122 123 func (client *WsClient) initClient() error { 124 if len(client.Addr) == 0 { 125 return utils.Error("client addr is nil") 126 } 127 128 if len(client.Path) == 0 { 129 return utils.Error("client path is nil") 130 } 131 132 if len(client.Origin) == 0 { 133 return utils.Error("client origin is nil") 134 } 135 136 if client.AuthCall == nil { 137 return utils.Error("client auth call is nil") 138 } 139 140 if client.ReceiveCall == nil { 141 return utils.Error("client receive call is nil") 142 } 143 144 // 创建 WebSocket 连接 145 config, err := websocket.NewConfig(client.Addr+client.Path, client.Origin) 146 if err != nil { 147 return err 148 } 149 150 token, secret, err := client.AuthCall() 151 if err != nil { 152 return err 153 } 154 155 if len(token) == 0 || len(secret) == 0 { 156 return utils.Error("token/secret invalid") 157 } 158 159 // 设置 JWT 头部 160 config.Header.Add("Authorization", token) 161 162 // 建立 WebSocket 连接 163 ws, err := websocket.DialConfig(config) 164 if err != nil { 165 return err 166 } 167 defer closeConn("ws client close", &DevConn{Conn: ws}) 168 169 client.conn = ws 170 client.auth = TokenAuth{Token: token, Secret: secret} 171 172 zlog.Info("websocket connect success", 0, zlog.String("url", client.Addr+client.Path)) 173 174 go client.ping() 175 176 return client.receive() 177 } 178 179 // receive 读取服务端消息 180 func (client *WsClient) receive() error { 181 for { 182 var message []byte 183 if err := websocket.Message.Receive(client.conn, &message); err != nil { 184 return err 185 } 186 res, err := authRes(client, message) 187 if err != nil { 188 zlog.Error("websocket receive parse error", 0, zlog.AddError(err)) 189 continue 190 } 191 reply, err := client.ReceiveCall(res) 192 if err != nil { 193 zlog.Error("websocket receive call error", 0, zlog.AddError(err)) 194 } 195 if err := client.SendMessage(reply); err != nil { 196 break 197 } 198 } 199 return nil 200 } 201 202 func (client *WsClient) SendMessage(reply interface{}) error { 203 if reply == nil { 204 return nil 205 } 206 if !client.Ready() { 207 zlog.Warn("client not ready", 0) 208 return nil 209 } 210 data, err := authReq(client.Path, reply, client.auth.Secret) 211 if err != nil { 212 zlog.Error("websocket receive reply create error", 0, zlog.AddError(err)) 213 return nil 214 } 215 if err := websocket.Message.Send(client.conn, data); err != nil { 216 zlog.Error("websocket client reply error", 0, zlog.AddError(err)) 217 return err 218 } 219 return nil 220 } 221 222 // ping 持续心跳包 223 func (client *WsClient) ping() { 224 for { 225 ping := Ping{ 226 HealthCheck: pingCmd, 227 } 228 data, _ := authReq(client.Path, &ping, client.auth.Secret) 229 if err := websocket.Message.Send(client.conn, data); err != nil { 230 zlog.Error("websocket client ping error", 0, zlog.AddError(err)) 231 break 232 } 233 time.Sleep(10 / 2 * time.Second) 234 } 235 }