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  }