Robust websockets

This commit is contained in:
JurajKubrican
2024-12-30 13:42:52 +01:00
parent 73054d4325
commit f9ba885019
3 changed files with 114 additions and 55 deletions

View File

@@ -5,6 +5,7 @@ import (
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"sync" "sync"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@@ -98,33 +99,44 @@ func main() {
return c.Render(http.StatusOK, "article", article) return c.Render(http.StatusOK, "article", article)
}) })
e.POST("/box/:id", updateBoxes)
e.GET("/ws", initWs) e.GET("/ws", initWs)
e.Logger.Fatal(e.Start(":54321")) e.Logger.Fatal(e.Start(":54321"))
} }
func updateBoxes(c echo.Context) error { func sendUpdate(index int, checked bool) {
id := c.Param("id") message := formatMessage(index, checked)
checked := c.FormValue("checked") == "on" broadcastMessage(message)
}
index, err := strconv.Atoi(id) func formatMessage(index int, checked bool) string {
if err != nil { message := "u:" + strconv.Itoa(index)
return c.String(http.StatusBadRequest, "Invalid ID")
}
boxes[index] = checked
message := strconv.Itoa(index)
if checked { if checked {
message += ":+" message += ":+"
} else { } else {
message += ":-" message += ":-"
} }
return message
}
broadcastMessage(message) func handleMessage(msg string, ws *websocket.Conn) error {
if strings.Contains(msg, "u:") {
parts := strings.Split(msg, ":")
index, err := strconv.Atoi(parts[1])
if err != nil {
return err
}
checked := parts[2] == "+"
return c.JSON(200, "[]") boxes[index] = checked
sendUpdate(index, checked)
}
if strings.Contains(msg, "ping") {
len := len(wsConnections)
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
}
return nil
} }
func initWs(c echo.Context) error { func initWs(c echo.Context) error {
@@ -133,6 +145,18 @@ func initWs(c echo.Context) error {
wsConnections[ws] = true wsConnections[ws] = true
wsMutex.Unlock() wsMutex.Unlock()
initialState := "i:"
for index, checked := range boxes {
if checked {
initialState += strconv.Itoa(index) + ";"
}
}
if err := websocket.Message.Send(ws, initialState); err != nil {
log.Errorf("Failed to send initial data: %v", err)
return
}
for { for {
var msg string var msg string
if err := websocket.Message.Receive(ws, &msg); err != nil { if err := websocket.Message.Receive(ws, &msg); err != nil {
@@ -142,7 +166,10 @@ func initWs(c echo.Context) error {
ws.Close() ws.Close()
break break
} }
handleMessage(msg, ws)
log.Infof("Received message: %s", msg)
} }
}).ServeHTTP(c.Response(), c.Request()) }).ServeHTTP(c.Response(), c.Request())
return nil return nil
} }

View File

@@ -1,10 +1,5 @@
{{block "boxes" .}} {{block "boxes" .}}
{{range $index, $value := .}} {{range $index, $value := .}}
<input type="checkbox" id="box-{{$index}}" name="checked" {{if $value}}checked{{end}} <input type="checkbox" id="box-{{$index}}" name="checked" {{if $value}}checked{{end}} >
hx-post="/box/{{$index}}"
hx-vals='{"value": this.checked}'
hx-trigger="change"
hx-encoding="none"
>
{{end}} {{end}}
{{end}} {{end}}

View File

@@ -1,56 +1,93 @@
{{block "index" .}} {{block "index" .}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home</title> <title>Home</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.5"></script> <script src="https://unpkg.com/htmx.org@1.9.5"></script>
<style> <style>
body { body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
} }
main { main {
flex: 1; flex: 1;
} }
</style> </style>
</head> </head>
<body class="bg-gray-900 text-white font-sans"> <body class="bg-gray-900 text-white font-sans">
<main id="main" class="max-w-3xl mx-auto p-6 rounded-lg"> <main id="main" class="max-w-3xl mx-auto p-6 rounded-lg">
<section> <section>
<div class="mt-4"> <div class="mt-4">{{template "boxes" .Boxes}}</div>
{{template "boxes" .Boxes}} </section>
</div>
</section>
</main> </main>
<footer class="bg-gray-800 text-center p-4"> <footer class="bg-gray-800 text-center p-4">
<!-- link to github --> <!-- link to github -->
<div class="mt-4"> <div class="mt-4">
<a href="https://github.com/JurajKubrican" class="text-blue-500">Github</a> <a href="https://github.com/JurajKubrican" class="text-blue-500"
</div> >Github</a
<!-- Link to linkedin --> >
<div class="mt-4"> </div>
<a href="https://www.linkedin.com/in/juraj-kubri%C4%8Dan-614b3274/" class="text-blue-500">LinkedIn</a> <!-- Link to linkedin -->
</div> <div class="mt-4">
<a
href="https://www.linkedin.com/in/juraj-kubri%C4%8Dan-614b3274/"
class="text-blue-500"
>LinkedIn</a
>
</div>
</footer> </footer>
</body> </body>
</html> </html>
<script> <script>
const socket = new WebSocket("ws://" + window.location.host+"/ws"); let socket = new WebSocket("ws://" + window.location.host + "/ws");
socket.addEventListener("message", function(event) { socket.addEventListener("message", function (event) {
if (event.data.startsWith("u:")) {
const items = event.data.split(";");
items.forEach((i) => {
const parts = event.data.split(":"); const parts = event.data.split(":");
document.getElementById("box-"+parts[0]).checked = parts[1] === "+"; document.getElementById("box-" + parts[1]).checked = parts[2] === "+";
console.log("box-"+parts[0]) console.log("box-" + parts[1]);
});
return;
}
if (event.data.startsWith("i:")) {
const str = event.data.split("i:")[1];
const items = str.split(";");
if (items.length === 0) {
return;
}
document.querySelectorAll("input").forEach((input) => {
input.checked = false;
});
items.forEach((i) => {
console.log(i);
document.getElementById("box-" + i).checked = true;
});
return;
}
});
setInterval(() => {
socket.send("ping");
}, 10000);
document.querySelectorAll("input").forEach((input) => {
input.addEventListener("change", (event) => {
if (socket.readyState !== socket.OPEN) {
socket = new WebSocket("ws://" + window.location.host + "/ws");
}
const id = event.target.id.split("-")[1];
const value = event.target.checked ? "+" : "-";
socket.send("u:" + id + ":" + value);
}); });
});
</script> </script>
{{end}} {{end}}