a-h / templ

A language for writing HTML user interfaces in Go.
https://templ.guide/
MIT License
8.26k stars 272 forks source link

Working with Gorilla Websockets #343

Closed saphal1998 closed 10 months ago

saphal1998 commented 10 months ago

Hi,

Thank you for making such a good library! I've been using this for multiple basic projects now and it's worked great for me!

I was making another app which used websockets extensively but I couldn't find much documentation on how to use Templ when we are using websockets.

Here's how I am processing the websocket requests in Go

package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "net/http"
    "poker-planning/internal/client"
    "poker-planning/internal/server"
    "poker-planning/templates"

    "github.com/a-h/templ"

    "github.com/gorilla/mux"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{} // use default options

func messages(w http.ResponseWriter, r *http.Request) {
    c, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Print("Upgrade:", err)
        return
    }

    defer c.Close()

    var jsonMessage map[string]any
    for {
        err := c.ReadJSON(&jsonMessage)
        if err != nil {
            log.Printf("Could not parse message from the client, or the connection was closed - %s", err.Error())
            break
        }
        log.Printf("Message from client %v", jsonMessage)
        userId, _ := jsonMessage["userId"].(string)
        message, _ := jsonMessage["message"].(string)

        nextWriter, err := c.NextWriter(websocket.BinaryMessage)
        if err != nil {
            log.Printf("Could not parse message from the client, or the connection was closed - %s", err.Error())
            break
        }

        templates.Message(userId, message).Render(context.Background(), nextWriter)
    }
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/chatroom", messages).Methods("GET")
    http.Handle("/", r)
    log.Fatal(http.ListenAndServe(*addr, nil))
}

And my message template is

package templates

templ Message(sender string, message string) {
    <div id="notifications" hx-swap-oob="beforeend">
        New message received
    </div>
    <div id="chat_room" hx-swap-oob="beforeend">
        <p>{ sender } - { message } </p>
    </div>
}

I'm trying to use htmx's websocket extension on the frontend.

I wonder if using the nextWriter is the right way to use this. I don't see any outgoing requests from my server, making me think that I'm definitely doing something wrong?

joerdav commented 10 months ago

I've actually done a little bit in this space before, but I was using turbo instead of htmx, but I don't think that makes much of a difference:

        ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Could not upgrade connection", err)
        w.WriteHeader(500)
        return
    }
    defer ws.Close()
    log.Println("Preparing to stream logs")
    lb := new(bytes.Buffer)
    sn := bufio.NewScanner(o)
    for sn.Scan() {
        fmt.Fprintln(lb, sn.Text())
        html := new(bytes.Buffer)
        err := templates.LogStream(lb.String()).
            Render(r.Context(), html)
        if err != nil {
            return
        }
        err = ws.WriteMessage(websocket.TextMessage, html.Bytes())
        if err != nil {
            log.Println("Error: ", err)
            return
        }
    }

The use case is a little different, but I'm sending messages in the same way.

Original code for more context: https://github.com/joerdav/sebastion/blob/main/webui/web.go

saphal1998 commented 10 months ago

This was very helpful, thank you!