Skip to content

Commit b6c3833

Browse files
authored
Created websocket server (#104)
* updated ui * added websocket server for ui connections
1 parent d5583b4 commit b6c3833

7 files changed

Lines changed: 258 additions & 8 deletions

File tree

api/api_handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ func NewAPIHandler(
309309
r.Get("/sessions/{sessionId}", r.GetSession)
310310

311311
// Erigon Node data
312+
r.Get("/v2/sessions/{sessionId}/nodes/{nodeId}/ws", r.HandleWebSocket)
312313
r.Get("/sessions/{sessionId}/nodes/{nodeId}/logs/{file}", r.Log)
313314
r.Get("/sessions/{sessionId}/nodes/{nodeId}/dbs/*", r.Tables)
314315
r.Get("/sessions/{sessionId}/nodes/{nodeId}/reorgs", r.ReOrg)

api/bridge_handler.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const (
3737
var wsBufferPool = new(sync.Pool)
3838

3939
func (h BridgeHandler) Bridge(w http.ResponseWriter, r *http.Request) {
40-
4140
//Sends a success Message to the Node client, to receive more information
4241
ctx, cancel := context.WithCancel(r.Context())
4342
defer cancel()
@@ -131,8 +130,6 @@ func (h BridgeHandler) Bridge(w http.ResponseWriter, r *http.Request) {
131130
continue
132131
}
133132

134-
//fmt.Printf("Sending request %s\n", string(bytes))
135-
136133
requestMutex.Lock()
137134
requestMap[rpcRequest.Id] = request
138135
requestMutex.Unlock()

api/internal/end_points.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ package internal
33
const (
44
HealthCheckEndPoint = "/healthcheck"
55
BridgeEndPoint = "/bridge"
6+
WSEndPoint = "/ws"
67
)

api/websocket_handler.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"sync"
8+
"time"
9+
10+
"github.com/gorilla/websocket"
11+
)
12+
13+
const (
14+
ActionSubscribe = "subscribe"
15+
ActionUnsubscribe = "unsubscribe"
16+
)
17+
18+
// SubscriptionResponse is the response sent back to the client after an action is processed.
19+
type ClientResponse struct {
20+
Status string `json:"status"`
21+
Message string `json:"message,omitempty"`
22+
Data *string `json:"data,omitempty"`
23+
}
24+
25+
type WebsocketHandler struct {
26+
mu sync.Mutex
27+
writeQueue chan []byte
28+
conn *websocket.Conn
29+
closeChan chan struct{}
30+
}
31+
32+
// **NewWebsocketHandler initializes WebsocketHandler**
33+
func NewWebsocketHandler(conn *websocket.Conn) *WebsocketHandler {
34+
handler := &WebsocketHandler{
35+
writeQueue: make(chan []byte, 100),
36+
conn: conn,
37+
closeChan: make(chan struct{}),
38+
}
39+
40+
go handler.startWriter() // Start dedicated writer goroutine
41+
return handler
42+
}
43+
44+
// **Sends response safely**
45+
func (h *WebsocketHandler) sendResponse(response *ClientResponse) {
46+
resp, err := json.Marshal(response)
47+
if err != nil {
48+
fmt.Printf("Error marshaling response: %v\n", err)
49+
return
50+
}
51+
52+
select {
53+
case h.writeQueue <- resp:
54+
default:
55+
fmt.Println("Warning: writeQueue is full, dropping message")
56+
}
57+
}
58+
59+
// **Dedicated writer goroutine**
60+
func (h *WebsocketHandler) startWriter() {
61+
for {
62+
select {
63+
case msg := <-h.writeQueue:
64+
h.mu.Lock()
65+
err := h.conn.WriteMessage(websocket.TextMessage, msg)
66+
h.mu.Unlock()
67+
68+
if err != nil {
69+
fmt.Printf("Error writing response: %v\n", err)
70+
return
71+
}
72+
73+
case <-h.closeChan:
74+
fmt.Println("Writer goroutine stopped")
75+
return
76+
}
77+
}
78+
}
79+
80+
// **Close WebSocket connection and stop writer**
81+
func (h *WebsocketHandler) closeConnection() {
82+
close(h.closeChan)
83+
h.conn.Close()
84+
}
85+
86+
// **WebSocket handler function**
87+
func (h *APIHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
88+
type wsMessage struct {
89+
Service string `json:"service"`
90+
Action string `json:"action"`
91+
}
92+
93+
upgrader := websocket.Upgrader{
94+
CheckOrigin: func(r *http.Request) bool { return true },
95+
}
96+
97+
conn, err := upgrader.Upgrade(w, r, nil)
98+
if err != nil {
99+
fmt.Println("WebSocket upgrade failed:", err)
100+
return
101+
}
102+
defer conn.Close()
103+
104+
handler := NewWebsocketHandler(conn)
105+
defer handler.closeConnection()
106+
107+
channel := make(chan []byte)
108+
109+
// **Goroutine to forward messages from the channel to the client**
110+
go func() {
111+
for {
112+
select {
113+
case <-r.Context().Done():
114+
return
115+
case <-handler.closeChan: // Graceful shutdown
116+
return
117+
case message := <-channel:
118+
handler.sendResponse(&ClientResponse{
119+
Status: "success",
120+
Message: string(message),
121+
})
122+
}
123+
}
124+
}()
125+
126+
// **Enable Ping/Pong Handling**
127+
conn.SetPongHandler(func(appData string) error {
128+
return nil
129+
})
130+
131+
go func() {
132+
ticker := time.NewTicker(10 * time.Second)
133+
defer ticker.Stop()
134+
135+
for {
136+
select {
137+
case <-handler.closeChan:
138+
return
139+
case <-ticker.C:
140+
handler.mu.Lock()
141+
err := conn.WriteMessage(websocket.PingMessage, nil)
142+
handler.mu.Unlock()
143+
144+
if err != nil {
145+
fmt.Println("Ping failed, closing connection:", err)
146+
handler.closeConnection()
147+
return
148+
}
149+
}
150+
}
151+
}()
152+
153+
for {
154+
_, msg, err := conn.ReadMessage()
155+
if err != nil {
156+
fmt.Println("Error reading message:", err)
157+
break
158+
}
159+
fmt.Printf("Received: %s\n", msg)
160+
161+
client, err := h.findNodeClient(r)
162+
if err != nil {
163+
handler.sendResponse(&ClientResponse{
164+
Status: "error",
165+
Message: "Client not found: " + err.Error(),
166+
})
167+
return
168+
}
169+
170+
var inMsg wsMessage
171+
if err := json.Unmarshal(msg, &inMsg); err != nil {
172+
handler.sendResponse(&ClientResponse{
173+
Status: "error",
174+
Message: "Invalid JSON: " + err.Error(),
175+
})
176+
continue
177+
}
178+
179+
switch inMsg.Action {
180+
case ActionSubscribe:
181+
go client.Subscribe(r.Context(), channel, inMsg.Service)
182+
case ActionUnsubscribe:
183+
client.Unsubscribe(r.Context(), channel, inMsg.Service)
184+
default:
185+
handler.sendResponse(&ClientResponse{
186+
Status: "error",
187+
Message: "Unknown action " + inMsg.Action,
188+
})
189+
}
190+
}
191+
}

cmd/diagnostics/main.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ func main() {
5959
}
6060
}()
6161

62-
packagePath := "github.com/erigontech/erigonwatch"
63-
version, err := GetPackageVersion(packagePath)
64-
if err == nil {
65-
fmt.Printf("Diagnostics version: %s\n", version)
66-
}
62+
printUIVersion()
6763

6864
fmt.Printf("Diagnostics UI is running on http://%s:%d\n", listenAddr, listenPort)
6965
//open(fmt.Sprintf("http://%s:%d", listenAddr, listenPort))
@@ -81,6 +77,14 @@ func main() {
8177
}
8278
}
8379

80+
func printUIVersion() {
81+
packagePath := "github.com/erigontech/erigonwatch"
82+
version, err := GetPackageVersion(packagePath)
83+
if err == nil {
84+
fmt.Printf("Diagnostics version: %s\n", version)
85+
}
86+
}
87+
8488
// open opens the specified URL in the default browser of the user.
8589
/*func open(url string) error {
8690
var cmd string

internal/erigon_node/erigon_client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,7 @@ type Client interface {
9696
FindProfile(ctx context.Context, profile string) ([]byte, error)
9797

9898
fetch(ctx context.Context, method string, params url.Values) (*NodeRequest, error)
99+
100+
Subscribe(ctx context.Context, channel chan []byte, service string) error
101+
Unsubscribe(ctx context.Context, channel chan []byte, service string) error
99102
}

internal/erigon_node/subscribe.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package erigon_node
2+
3+
import (
4+
"context"
5+
)
6+
7+
func (c *NodeClient) Subscribe(ctx context.Context, channel chan []byte, service string) error {
8+
request, err := c.fetch(ctx, "subscribe/"+service, nil)
9+
10+
if err != nil {
11+
return err
12+
}
13+
14+
for {
15+
more, result, err := request.nextResult(ctx)
16+
17+
if err != nil {
18+
return err
19+
}
20+
21+
channel <- result
22+
23+
if !more {
24+
break
25+
}
26+
}
27+
28+
return nil
29+
}
30+
31+
func (c *NodeClient) Unsubscribe(ctx context.Context, channel chan []byte, service string) error {
32+
request, err := c.fetch(ctx, "unsubscribe/"+service, nil)
33+
34+
if err != nil {
35+
return err
36+
}
37+
38+
for {
39+
more, result, err := request.nextResult(ctx)
40+
41+
if err != nil {
42+
return err
43+
}
44+
45+
channel <- result
46+
47+
if !more {
48+
break
49+
}
50+
}
51+
52+
return nil
53+
}

0 commit comments

Comments
 (0)