Skip to content

Commit eaa9561

Browse files
cmaglielucarin91
andauthored
Added monitor API handler
Co-authored-by: Luca Rinaldi <lucarin@protonmail.com>
1 parent 104ed54 commit eaa9561

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131
github.com/goccy/go-yaml v1.18.0
3232
github.com/google/go-cmp v0.7.0
3333
github.com/google/renameio/v2 v2.0.0
34+
github.com/gorilla/websocket v1.5.0
3435
github.com/gosimple/slug v1.15.0
3536
github.com/jedib0t/go-pretty/v6 v6.6.8
3637
github.com/jub0bs/cors v0.7.0
@@ -141,7 +142,6 @@ require (
141142
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
142143
github.com/google/uuid v1.6.0 // indirect
143144
github.com/gorilla/mux v1.8.1 // indirect
144-
github.com/gorilla/websocket v1.5.0 // indirect
145145
github.com/gosimple/unidecode v1.0.1 // indirect
146146
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
147147
github.com/h2non/filetype v1.1.3 // indirect

internal/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,7 @@ func NewHTTPRouter(
7070

7171
mux.Handle("GET /v1/docs/", http.StripPrefix("/v1/docs/", handlers.DocsServer(docsFS)))
7272

73+
mux.Handle("GET /v1/monitor/ws", handlers.HandleMonitorWS())
74+
7375
return mux
7476
}

internal/api/handlers/monitor.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package handlers
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"log/slog"
8+
"net"
9+
"net/http"
10+
"time"
11+
12+
"github.com/gorilla/websocket"
13+
14+
"github.com/arduino/arduino-app-cli/internal/api/models"
15+
"github.com/arduino/arduino-app-cli/pkg/render"
16+
)
17+
18+
var upgrader = websocket.Upgrader{
19+
ReadBufferSize: 1024,
20+
WriteBufferSize: 1024,
21+
}
22+
23+
func monitorStream(mon net.Conn, ws *websocket.Conn) {
24+
logWebsocketError := func(msg string, err error) {
25+
// Do not log simple close or interruption errors
26+
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived, websocket.CloseAbnormalClosure) {
27+
if e, ok := err.(*websocket.CloseError); ok {
28+
slog.Error(msg, slog.String("closecause", fmt.Sprintf("%d: %s", e.Code, err)))
29+
} else {
30+
slog.Error(msg, slog.String("error", err.Error()))
31+
}
32+
}
33+
}
34+
logSocketError := func(msg string, err error) {
35+
if !errors.Is(err, net.ErrClosed) && !errors.Is(err, io.EOF) {
36+
slog.Error(msg, slog.String("error", err.Error()))
37+
}
38+
}
39+
go func() {
40+
defer mon.Close()
41+
defer ws.Close()
42+
for {
43+
// Read from websocket and write to monitor
44+
_, msg, err := ws.ReadMessage()
45+
if err != nil {
46+
logWebsocketError("Error reading from websocket", err)
47+
return
48+
}
49+
if _, err := mon.Write(msg); err != nil {
50+
logSocketError("Error writing to monitor", err)
51+
return
52+
}
53+
}
54+
}()
55+
go func() {
56+
defer mon.Close()
57+
defer ws.Close()
58+
buff := [1024]byte{}
59+
for {
60+
// Read from monitor and write to websocket
61+
n, err := mon.Read(buff[:])
62+
if err != nil {
63+
logSocketError("Error reading from monitor", err)
64+
return
65+
}
66+
67+
if err := ws.WriteMessage(websocket.BinaryMessage, buff[:n]); err != nil {
68+
logWebsocketError("Error writing to websocket", err)
69+
return
70+
}
71+
}
72+
}()
73+
}
74+
75+
func HandleMonitorWS() http.HandlerFunc {
76+
return func(w http.ResponseWriter, r *http.Request) {
77+
// Connect to monitor
78+
mon, err := net.DialTimeout("tcp", "127.0.0.1:7500", time.Second)
79+
if err != nil {
80+
slog.Error("Unable to connect to monitor", slog.String("error", err.Error()))
81+
render.EncodeResponse(w, http.StatusServiceUnavailable, models.ErrorResponse{Details: "Unable to connect to monitor: " + err.Error()})
82+
return
83+
}
84+
85+
// Upgrade the connection to websocket
86+
conn, err := upgrader.Upgrade(w, r, nil)
87+
if err != nil {
88+
// Remember to close monitor connection if websocket upgrade fails.
89+
mon.Close()
90+
91+
render.EncodeResponse(w, http.StatusInternalServerError, map[string]string{"error": "Failed to upgrade connection"})
92+
return
93+
}
94+
95+
// Now the connection is managed by the websocket library, let's move the handlers in the goroutine
96+
go monitorStream(mon, conn)
97+
98+
// and return nothing to the http library
99+
}
100+
}

0 commit comments

Comments
 (0)