github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/gate/client/wsp/connection.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package wsp 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "io" 26 "net/http" 27 "net/http/httptest" 28 "strings" 29 "sync" 30 "time" 31 32 "github.com/gorilla/websocket" 33 34 "github.com/e154/smart-home/api" 35 "github.com/e154/smart-home/common/apperr" 36 m "github.com/e154/smart-home/models" 37 "github.com/e154/smart-home/system/gate/common" 38 "github.com/e154/smart-home/system/stream" 39 ) 40 41 // Status of a Connection 42 const ( 43 CONNECTING = iota 44 IDLE 45 RUNNING 46 ) 47 48 // Connection handle a single websocket (HTTP/TCP) connection to an Server 49 type Connection struct { 50 pool *Pool 51 status int 52 api *api.Api 53 stream *stream.Stream 54 cli *stream.Client 55 *sync.Mutex 56 ws *websocket.Conn 57 } 58 59 // NewConnection create a Connection object 60 func NewConnection(pool *Pool, 61 api *api.Api, 62 stream *stream.Stream) *Connection { 63 c := &Connection{ 64 pool: pool, 65 status: CONNECTING, 66 api: api, 67 stream: stream, 68 Mutex: &sync.Mutex{}, 69 } 70 return c 71 } 72 73 // Connect to the IsolatorServer using a HTTP websocket 74 func (c *Connection) Connect(ctx context.Context) (err error) { 75 //log.Infof("Connecting to %s", c.pool.target) 76 77 // Create a new TCP(/TLS) connection ( no use of net.http ) 78 c.ws, _, err = c.pool.client.dialer.DialContext( 79 ctx, 80 c.pool.target, 81 http.Header{"X-SECRET-KEY": {c.pool.secretKey}}, 82 ) 83 84 if err != nil { 85 return err 86 } 87 88 log.Infof("Connected to %s", c.pool.target) 89 defer log.Info("Connection closed ...") 90 91 // Send the greeting message with proxy id and wanted pool size. 92 greeting := fmt.Sprintf( 93 "%s_%d", 94 c.pool.client.cfg.ID, 95 c.pool.client.cfg.PoolIdleSize, 96 ) 97 if err = c.WriteMessage(websocket.TextMessage, []byte(greeting)); err != nil { 98 log.Error("greeting error :", err.Error()) 99 c.Close() 100 return err 101 } 102 103 c.serve(ctx) 104 105 return 106 } 107 108 // the main loop it : 109 // - wait to receive HTTP requests from the Server 110 // - execute HTTP requests 111 // - send HTTP response back to the Server 112 // 113 // As in the server code there is no buffering of HTTP request/response body 114 // As is the server if any error occurs the connection is closed/throwed 115 func (c *Connection) serve(ctx context.Context) { 116 117 // Keep connection alive 118 go func() { 119 timer := time.NewTicker(time.Second * 10) 120 defer timer.Stop() 121 for { 122 select { 123 case t := <-timer.C: 124 err := c.ws.WriteControl(websocket.PingMessage, []byte{}, t.Add(time.Second)) 125 if err != nil { 126 c.Close() 127 return 128 } 129 } 130 } 131 }() 132 133 var jsonRequest []byte 134 var err error 135 var messageType int 136 137 for { 138 // Read request 139 c.status = IDLE 140 messageType, jsonRequest, err = c.ws.ReadMessage() 141 142 if messageType == -1 { 143 break 144 } 145 146 if err != nil { 147 log.Errorf("Unable to read request", err) 148 break 149 } 150 151 c.status = RUNNING 152 153 // Trigger a pool refresh to open new connections if needed 154 go c.pool.connector(ctx) 155 156 if strings.Contains(string(jsonRequest), "WS:") { 157 // get user 158 accessToken := string(jsonRequest) 159 accessToken = strings.ReplaceAll(accessToken, "WS:", "") 160 if accessToken == "" { 161 log.Error(apperr.ErrUnauthorized.Error()) 162 return 163 } 164 var user *m.User 165 user, err = c.pool.GetUser(accessToken) 166 if err != nil { 167 log.Error(apperr.ErrAccessDenied.Error()) 168 return 169 } 170 c.stream.NewConnection(c.ws, user) 171 return 172 } 173 174 // Deserialize request 175 httpRequest := new(common.HTTPRequest) 176 err = json.Unmarshal(jsonRequest, httpRequest) 177 if err != nil { 178 c.error(fmt.Sprintf("Unable to deserialize json http request : %s\n", err)) 179 break 180 } 181 182 req, err := common.UnserializeHTTPRequest(httpRequest) 183 if err != nil { 184 c.error(fmt.Sprintf("Unable to deserialize http request : %v\n", err)) 185 break 186 } 187 188 //log.Infof("[%s] %s", req.Method, req.URL.String()) 189 190 // Pipe request body 191 _, bodyReader, err := c.ws.NextReader() 192 if err != nil { 193 log.Errorf("Unable to get response body reader : %v", err) 194 break 195 } 196 req.Body = io.NopCloser(bodyReader) 197 198 // Execute request 199 //resp, err := c.pool.client.client.Do(req) 200 //if err != nil { 201 // err = c.error(fmt.Sprintf("Unable to execute request : %v\n", err)) 202 // if err != nil { 203 // break 204 // } 205 // continue 206 //} 207 208 //todo fix 209 req.RequestURI = req.URL.String() 210 resp := httptest.NewRecorder() 211 c.api.Echo().ServeHTTP(resp, req) 212 213 // Serialize response 214 jsonResponse, err := json.Marshal(common.SerializeHTTPResponse(resp.Result())) 215 if err != nil { 216 err = c.error(fmt.Sprintf("Unable to serialize response : %v\n", err)) 217 if err != nil { 218 break 219 } 220 continue 221 } 222 223 // Write response 224 err = c.WriteMessage(websocket.TextMessage, jsonResponse) 225 if err != nil { 226 log.Errorf("Unable to write response : %v", err) 227 break 228 } 229 230 // Pipe response body 231 bodyWriter, err := c.ws.NextWriter(websocket.BinaryMessage) 232 if err != nil { 233 log.Errorf("Unable to get response body writer : %v", err) 234 break 235 } 236 _, err = io.Copy(bodyWriter, resp.Body) 237 if err != nil { 238 log.Errorf("Unable to get pipe response body : %v", err) 239 break 240 } 241 bodyWriter.Close() 242 } 243 } 244 245 func (c *Connection) error(msg string) (err error) { 246 resp := common.NewHTTPResponse() 247 resp.StatusCode = 527 248 249 log.Error(msg) 250 251 resp.ContentLength = int64(len(msg)) 252 253 // Serialize response 254 jsonResponse, err := json.Marshal(resp) 255 if err != nil { 256 log.Errorf("Unable to serialize response : %v", err) 257 return 258 } 259 260 // Write response 261 err = c.WriteMessage(websocket.TextMessage, jsonResponse) 262 if err != nil { 263 log.Errorf("Unable to write response : %v", err) 264 return 265 } 266 267 // Write response body 268 err = c.WriteMessage(websocket.BinaryMessage, []byte(msg)) 269 if err != nil { 270 log.Errorf("Unable to write response body : %v", err) 271 return 272 } 273 274 return 275 } 276 277 // Close close the ws/tcp connection and remove it from the pool 278 func (c *Connection) Close() { 279 280 if c.ws != nil { 281 if err := c.WriteMessage(websocket.CloseMessage, []byte{}); err != nil { 282 //debug.PrintStack() 283 //log.Error(err.Error()) 284 } 285 c.ws.Close() 286 } 287 } 288 289 func (c *Connection) WriteMessage(messageType int, data []byte) (err error) { 290 //todo: fix, it not work 291 c.Lock() 292 defer c.Unlock() 293 err = c.ws.WriteMessage(messageType, data) 294 return 295 }