github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/daemon/handle_sockets.go (about) 1 // Copyright 2021 The TrueBlocks Authors. All rights reserved. 2 // Use of this source code is governed by a license that can 3 // be found in the LICENSE file. 4 5 package daemonPkg 6 7 import ( 8 "fmt" 9 "net" 10 "net/http" 11 12 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 13 "github.com/gorilla/websocket" 14 ) 15 16 // MessageType is a message carried in a string 17 type MessageType string 18 19 const ( 20 // CommandErrorMessage is a message send when the server encounters an error 21 CommandErrorMessage MessageType = "command_error" 22 // CommandOutputMessage is currently not used, but may in the future carry the actual data 23 CommandOutputMessage MessageType = "output" 24 // ProgressMessage is a message carried on the stderr stream 25 ProgressMessage MessageType = "progress" 26 ) 27 28 var upgrader = websocket.Upgrader{} 29 30 // Message is a structure used to send messages via websockets 31 type Message struct { 32 Action MessageType `json:"action"` 33 ID string `json:"id"` 34 Content string `json:"content"` 35 } 36 37 // Connection is a structure representing a websocket connection 38 type Connection struct { 39 connection *websocket.Conn 40 pool *ConnectionPool 41 send chan *Message 42 } 43 44 // write the message to the connection 45 func (c *Connection) write() { 46 defer func() { 47 c.connection.Close() 48 }() 49 50 // this is a common pattern for a websocket connection 51 //nolint:staticcheck,gosimple 52 for { 53 select { 54 case message, ok := <-c.send: 55 if !ok { 56 c.Log("Connection closed") 57 _ = c.connection.WriteMessage(websocket.CloseMessage, []byte{}) 58 return 59 } 60 61 err := c.connection.WriteJSON(message) 62 if err != nil { 63 // c.Log("Error while sending message, dropping connection: %s", err.Error()) 64 c.pool.unregister <- c 65 } 66 } 67 } 68 } 69 70 // RemoteAddr is the other end of the connection 71 func (c *Connection) RemoteAddr() net.Addr { 72 return c.connection.RemoteAddr() 73 } 74 75 // Log writes a log messages to the server's stderr 76 func (c *Connection) Log(s string, args ...interface{}) { 77 subMsg := fmt.Sprintf(s, args...) 78 msg := fmt.Sprintf("%s %s", c.RemoteAddr(), subMsg) 79 logger.Info(msg) 80 } 81 82 // ConnectionPool is the collection of all connections 83 type ConnectionPool struct { 84 connections map[*Connection]bool 85 broadcast chan *Message 86 register chan *Connection 87 unregister chan *Connection 88 } 89 90 // closeAndDelete cleans up a connection 91 func closeAndDelete(pool *ConnectionPool, connection *Connection) { 92 delete(pool.connections, connection) 93 close(connection.send) 94 } 95 96 // newConnectionPool returns a new connection structure 97 func newConnectionPool() *ConnectionPool { 98 return &ConnectionPool{ 99 connections: make(map[*Connection]bool), 100 broadcast: make(chan *Message), 101 register: make(chan *Connection), 102 unregister: make(chan *Connection), 103 } 104 } 105 106 // run the web sockets server 107 func (pool *ConnectionPool) run() { 108 // run forever... 109 for { 110 select { 111 // handle a signal to register a new connection 112 case connection := <-pool.register: 113 connection.Log("Connected (Websockets)") 114 pool.connections[connection] = true 115 // handle a signal to unregister a connection 116 case connection := <-pool.unregister: 117 if _, ok := pool.connections[connection]; ok { 118 connection.Log("Unregistering connection") 119 closeAndDelete(pool, connection) 120 } 121 // handle a signal to broadcast a message 122 case message := <-pool.broadcast: 123 for connection := range pool.connections { 124 connection.send <- message 125 } 126 } 127 } 128 } 129 130 // HandleWebsockets handles web sockets 131 func HandleWebsockets(pool *ConnectionPool, w http.ResponseWriter, r *http.Request) { 132 // TODO: the line below allows any connection through WebSockets. Once the server 133 // TODO: is ready, we should implement some rules here 134 upgrader.CheckOrigin = func(r *http.Request) bool { return true } 135 136 c, err := upgrader.Upgrade(w, r, nil) 137 if err != nil { 138 logger.Error("upgrade:", err) 139 return 140 } 141 142 connection := &Connection{connection: c, send: make(chan *Message), pool: pool} 143 pool.register <- connection 144 145 go connection.write() 146 } 147 148 var connectionPool = newConnectionPool() 149 150 // RunWebsocketPool runs the websocket pool 151 func RunWebsocketPool() { 152 go connectionPool.run() 153 }