Robust websockets
This commit is contained in:
55
src/main.go
55
src/main.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}}
|
||||||
107
views/index.html
107
views/index.html
@@ -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}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user