basic htmx with server sending content to the client over a websocket is now working. This only worked when text message where being sent so the websocket handling had to be made configurable with a 'text' boolean field.

This commit is contained in:
Erik Brakkee 2024-07-29 23:56:44 +02:00
parent f6ea7a56a9
commit 0e8ed86be3
13 changed files with 106 additions and 20 deletions

View File

@ -258,7 +258,7 @@ func main() {
log.Println("WebSocket connection error:", err) log.Println("WebSocket connection error:", err)
return return
} }
wsConn := websocketutil.NewWebSocketConn(conn) wsConn := websocketutil.NewWebSocketConn(conn, false)
defer wsConn.Close() defer wsConn.Close()
serverInfo, err := comms.AgentInitialization(wsConn, comms.NewAgentInfo()) serverInfo, err := comms.AgentInitialization(wsConn, comms.NewAgentInfo())

View File

@ -9,12 +9,11 @@ import (
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
_ "net/http/pprof"
"os" "os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
_ "net/http/pprof"
) )
func parsePublicId(path string) (publicId string, _ error) { func parsePublicId(path string) (publicId string, _ error) {
@ -97,6 +96,8 @@ func main() {
log.Printf("Using username '%s' and password '%s'", userPassword.Username, userPassword.Password) log.Printf("Using username '%s' and password '%s'", userPassword.Username, userPassword.Password)
admin := converge.NewAdmin() admin := converge.NewAdmin()
// For agents connecting
registrationService := websocketutil.WebSocketService{ registrationService := websocketutil.WebSocketService{
Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) { Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) {
publicId, err := parsePublicId(r.URL.Path) publicId, err := parsePublicId(r.URL.Path)
@ -112,6 +113,8 @@ func main() {
} }
}, },
} }
// For users connecting with ssh
clientService := websocketutil.WebSocketService{ clientService := websocketutil.WebSocketService{
Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) { Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) {
publicId, err := parsePublicId(r.URL.Path) publicId, err := parsePublicId(r.URL.Path)
@ -127,9 +130,16 @@ func main() {
}, },
} }
// for the web browser getting live status updates.
sessionService := websocketutil.WebSocketService{
Handler: sessionHandler,
Text: true,
}
// websocket endpoints // websocket endpoints
http.HandleFunc("/agent/", registrationService.Handle) http.HandleFunc("/agent/", registrationService.Handle)
http.HandleFunc("/client/", clientService.Handle) http.HandleFunc("/client/", clientService.Handle)
http.HandleFunc("/ws/sessions", sessionService.Handle)
// create filehandler with templating for html files. // create filehandler with templating for html files.
http.Handle("/docs/", http.StripPrefix("/docs/", http.HandlerFunc(pageHandler))) http.Handle("/docs/", http.StripPrefix("/docs/", http.HandlerFunc(pageHandler)))

View File

@ -32,7 +32,7 @@ func pageHandler(w http.ResponseWriter, r *http.Request) {
case "index.html": case "index.html":
templates.AboutTab().Render(r.Context(), w) templates.AboutTab().Render(r.Context(), w)
case "usage.html": case "usage.html":
templates.UsageTab(secure, r.URL.Host, username).Render(r.Context(), w) templates.UsageTab(secure, r.Host, username).Render(r.Context(), w)
case "downloads.html": case "downloads.html":
templates.DownloadsTab().Render(r.Context(), w) templates.DownloadsTab().Render(r.Context(), w)
case "sessions.html": case "sessions.html":

View File

@ -0,0 +1,55 @@
package main
import (
"encoding/json"
"log"
"net"
"net/http"
"strconv"
"time"
)
type Message struct {
Type string `json:"type"`
Content string `json:"content"`
}
func sessionHandler(w http.ResponseWriter, r *http.Request, conn net.Conn) {
log.Println("Got sessions websocket connection")
i := 0
for {
time.Sleep(1 * time.Second)
message := Message{
Type: "update",
Content: `
<div id="mycontent">
New data: ` + strconv.Itoa(i) + `
</div>
`,
}
_, err := json.Marshal(message)
if err != nil {
log.Printf("ERROR marshalling json: %v", err)
return
}
//conn.Write(data)
go func() {
for {
b := make([]byte, 1024)
_, err := conn.Read(b)
if err != nil {
return
}
}
}()
_, err = conn.Write([]byte(message.Content))
if err == nil {
_, err = conn.Write([]byte("\n"))
}
if err != nil {
log.Printf("ERROR sending message: %v", err)
return
}
i++
}
}

View File

@ -36,7 +36,7 @@ func handleConnection(conn net.Conn, wsURL string, insecure bool) {
log.Println("WebSocket connection error:", err) log.Println("WebSocket connection error:", err)
return return
} }
wsConn := websocketutil.NewWebSocketConn(_wsConn) wsConn := websocketutil.NewWebSocketConn(_wsConn, false)
defer wsConn.Close() defer wsConn.Close()
iowrappers.SynchronizeStreams(wsConn, conn) iowrappers.SynchronizeStreams(wsConn, conn)

View File

@ -70,7 +70,7 @@ func main() {
log.Println("WebSocket connection error:", err) log.Println("WebSocket connection error:", err)
panic(err) panic(err)
} }
wsConn := websocketutil.NewWebSocketConn(_wsConn) wsConn := websocketutil.NewWebSocketConn(_wsConn, false)
defer wsConn.Close() defer wsConn.Close()
iowrappers.SynchronizeStreams(wsConn, Stdio{}) iowrappers.SynchronizeStreams(wsConn, Stdio{})

View File

@ -35,7 +35,7 @@ func main() {
func handleWebSocket(w http.ResponseWriter, r *http.Request, tcpConn net.Conn) { func handleWebSocket(w http.ResponseWriter, r *http.Request, tcpConn net.Conn) {
conn, err := upgrader.Upgrade(w, r, nil) conn, err := upgrader.Upgrade(w, r, nil)
wsConn := websocketutil.NewWebSocketConn(conn) wsConn := websocketutil.NewWebSocketConn(conn, false)
if err != nil { if err != nil {
log.Println("Error upgrading to WebSocket:", err) log.Println("Error upgrading to WebSocket:", err)
return return

View File

@ -7,3 +7,6 @@ services:
context: . context: .
ports: ports:
- 8000:8000 - 8000:8000
environment:
CONVERGE_USERNAME: abc
CONVERGE_PASSWORD: "123"

View File

@ -17,9 +17,17 @@ templ BasePage(tab int) {
<link rel="stylesheet" href="../static/css/bootstrap.min.css" <link rel="stylesheet" href="../static/css/bootstrap.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="../static/js/htmx.1.9.12.js"></script>
<script src="../static/js/htmx.ws.1.9.12.js"></script>
<title>Converge</title> <title>Converge</title>
</head> </head>
<body> <body>
<script>
htmx.logAll();
</script>
<script src="../static/js/bootstrap.bundle.min.js" <script src="../static/js/bootstrap.bundle.min.js"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>

View File

@ -2,9 +2,12 @@ package templates
templ Sessions() { templ Sessions() {
<div> <div hx-ext="ws" ws-connect="ws://localhost:8000/ws/sessions">
To be done <div id="mycontent">
Initial content
</div> </div>
</div>
} }

View File

@ -12,12 +12,12 @@ templ Usage(secure string, host string, username string) {
</p> </p>
<pre>{` <pre>{`
# linux # linux
`}curl http{secure}://{host}/docs/agent > agent{` `}curl http{secure}://{host}/static/agent > agent{`
chmod 755 agent chmod 755 agent
`}./agent --id ID ws{secure}://{host}{` `}./agent --id ID ws{secure}://{host}{`
# windows # windows
`}curl http{secure}://{host}/docs/agent.exe > agent.exe{` `}curl http{secure}://{host}/static/agent.exe > agent.exe{`
`}agent --id ID ws{secure}://{host}{` `}agent --id ID ws{secure}://{host}{`
`}</pre> `}</pre>
<p> <p>

View File

@ -9,6 +9,11 @@ import (
type WebSocketConn struct { type WebSocketConn struct {
conn *websocket.Conn conn *websocket.Conn
buf []byte buf []byte
text bool
}
func NewWebSocketConn(conn *websocket.Conn, text bool) *WebSocketConn {
return &WebSocketConn{conn: conn, text: text}
} }
func (websocketConn *WebSocketConn) Read(p []byte) (n int, err error) { func (websocketConn *WebSocketConn) Read(p []byte) (n int, err error) {
@ -26,12 +31,12 @@ func (websocketConn *WebSocketConn) Read(p []byte) (n int, err error) {
return n, err return n, err
} }
func NewWebSocketConn(conn *websocket.Conn) *WebSocketConn {
return &WebSocketConn{conn: conn}
}
func (websocketConn *WebSocketConn) Write(p []byte) (n int, err error) { func (websocketConn *WebSocketConn) Write(p []byte) (n int, err error) {
err = websocketConn.conn.WriteMessage(websocket.BinaryMessage, p) messageType := websocket.BinaryMessage
if websocketConn.text {
messageType = websocket.TextMessage
}
err = websocketConn.conn.WriteMessage(messageType, p)
if err == nil { if err == nil {
n = len(p) n = len(p)
} }
@ -73,5 +78,5 @@ func ConnectWebSocket(conn net.Conn, urlStr string) (net.Conn, error) {
return nil, err return nil, err
} }
return NewWebSocketConn(wsConn), nil return NewWebSocketConn(wsConn, false), nil
} }

View File

@ -22,13 +22,14 @@ var upgrader = websocket.Upgrader{
} }
func handleWebSocket(w http.ResponseWriter, r *http.Request, func handleWebSocket(w http.ResponseWriter, r *http.Request,
handler func(w http.ResponseWriter, r *http.Request, websockerConnection net.Conn)) { handler func(w http.ResponseWriter, r *http.Request, websockerConnection net.Conn),
text bool) {
conn, err := upgrader.Upgrade(w, r, nil) conn, err := upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
log.Println("Error upgrading to WebSocket:", err) log.Println("Error upgrading to WebSocket:", err)
return return
} }
wsConn := NewWebSocketConn(conn) wsConn := NewWebSocketConn(conn, text)
defer wsConn.Close() defer wsConn.Close()
handler(w, r, wsConn) handler(w, r, wsConn)
@ -36,8 +37,9 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request,
type WebSocketService struct { type WebSocketService struct {
Handler func(w http.ResponseWriter, r *http.Request, conn net.Conn) Handler func(w http.ResponseWriter, r *http.Request, conn net.Conn)
Text bool
} }
func (endpoint *WebSocketService) Handle(w http.ResponseWriter, r *http.Request) { func (endpoint *WebSocketService) Handle(w http.ResponseWriter, r *http.Request) {
handleWebSocket(w, r, endpoint.Handler) handleWebSocket(w, r, endpoint.Handler, endpoint.Text)
} }