Skip to content

Commit

Permalink
Websocket Extension - Alpha (#22)
Browse files Browse the repository at this point in the history
* wip

* merge

* working again

* refactor/make it a bit cleaner

* fix to only call cb for session id who initiated the event

* support broadcasting events to all clients

* refactor

* refactor into ws extension

* add go mod

* rename module

* fix naming

* refactor

* rename

* merge

* fix manager ws delete, add manager tests

* add metric page

* fixes, add k6 script

* fixes, add k6 script

* deploy docker image

* cleanup

* cleanup

* cleanup
  • Loading branch information
maddalax authored Nov 9, 2024
1 parent 8412623 commit 34e816f
Show file tree
Hide file tree
Showing 44 changed files with 2,029 additions and 6 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/release-ws-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Build and Deploy ws-test

on:
workflow_run:
workflows: [ "Update HTMGO Framework Dependency" ] # The name of the first workflow
types:
- completed
workflow_dispatch:
push:
branches:
- ws-testing

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Get short commit hash
id: vars
run: echo "::set-output name=short_sha::$(echo $GITHUB_SHA | cut -c1-7)"

- name: Build Docker image
run: |
cd ./examples/ws-example && docker build -t ghcr.io/${{ github.repository_owner }}/ws-example:${{ steps.vars.outputs.short_sha }} .
- name: Tag as latest Docker image
run: |
docker tag ghcr.io/${{ github.repository_owner }}/ws-example:${{ steps.vars.outputs.short_sha }} ghcr.io/${{ github.repository_owner }}/ws-example:latest
- name: Log in to GitHub Container Registry
run: echo "${{ secrets.CR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push Docker image
run: |
docker push ghcr.io/${{ github.repository_owner }}/ws-example:latest
1 change: 0 additions & 1 deletion examples/chat/sse/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ func Handle() http.HandlerFunc {
defer manager.Disconnect(sessionId)

defer func() {
fmt.Printf("empting channels\n")
for len(writer) > 0 {
<-writer
}
Expand Down
4 changes: 1 addition & 3 deletions examples/chat/sse/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,14 @@ func (manager *SocketManager) Listen(listener chan SocketEvent) {
}

func (manager *SocketManager) dispatch(event SocketEvent) {
fmt.Printf("dispatching event: %s\n", event.Type)
done := make(chan struct{}, 1)
go func() {
for {
select {
case <-done:
fmt.Printf("dispatched event: %s\n", event.Type)
return
case <-time.After(5 * time.Second):
fmt.Printf("havent dispatched event after 5s, chan blocked: %s\n", event.Type)
fmt.Printf("havent dispatched listener event after 5s, chan blocked: %s\n", event.Type)
}
}
}()
Expand Down
11 changes: 11 additions & 0 deletions examples/ws-example/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Project exclude paths
/tmp/
node_modules/
dist/
js/dist
js/node_modules
go.work
go.work.sum
.idea
!framework/assets/dist
__htmgo
6 changes: 6 additions & 0 deletions examples/ws-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/assets/dist
tmp
node_modules
.idea
__htmgo
dist
38 changes: 38 additions & 0 deletions examples/ws-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Stage 1: Build the Go binary
FROM golang:1.23-alpine AS builder

RUN apk update
RUN apk add git
RUN apk add curl

# Set the working directory inside the container
WORKDIR /app

# Copy go.mod and go.sum files
COPY go.mod go.sum ./

# Download and cache the Go modules
RUN go mod download

# Copy the source code into the container
COPY . .

# Build the Go binary for Linux
RUN GOPRIVATE=github.com/maddalax GOPROXY=direct go run github.com/maddalax/htmgo/cli/htmgo@latest build


# Stage 2: Create the smallest possible image
FROM gcr.io/distroless/base-debian11

# Set the working directory inside the container
WORKDIR /app

# Copy the Go binary from the builder stage
COPY --from=builder /app/dist .

# Expose the necessary port (replace with your server port)
EXPOSE 3000


# Command to run the binary
CMD ["./ws-example"]
20 changes: 20 additions & 0 deletions examples/ws-example/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: '3'

tasks:
run:
cmds:
- go run github.com/maddalax/htmgo/cli/htmgo@latest run
silent: true

build:
cmds:
- go run github.com/maddalax/htmgo/cli/htmgo@latest build

docker:
cmds:
- docker build .

watch:
cmds:
- go run github.com/maddalax/htmgo/cli/htmgo@latest watch
silent: true
13 changes: 13 additions & 0 deletions examples/ws-example/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !prod
// +build !prod

package main

import (
"io/fs"
"ws-example/internal/embedded"
)

func GetStaticAssets() fs.FS {
return embedded.NewOsFs()
}
3 changes: 3 additions & 0 deletions examples/ws-example/assets/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
16 changes: 16 additions & 0 deletions examples/ws-example/assets_prod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build prod
// +build prod

package main

import (
"embed"
"io/fs"
)

//go:embed assets/dist/*
var staticAssets embed.FS

func GetStaticAssets() fs.FS {
return staticAssets
}
18 changes: 18 additions & 0 deletions examples/ws-example/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module ws-example

go 1.23.0

require (
github.com/go-chi/chi/v5 v5.1.0
github.com/maddalax/htmgo/extensions/websocket v0.0.0-20241104193946-1ddeceaa8286
github.com/maddalax/htmgo/framework v1.0.3-0.20241104193946-1ddeceaa8286
)

require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
golang.org/x/sys v0.6.0 // indirect
)
28 changes: 28 additions & 0 deletions examples/ws-example/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/maddalax/htmgo/extensions/websocket v0.0.0-20241104193946-1ddeceaa8286 h1:5Z848JJUQ3OACuKWOX8XbzFb9krKwaiJb7inYSKCRKY=
github.com/maddalax/htmgo/extensions/websocket v0.0.0-20241104193946-1ddeceaa8286/go.mod h1:r6/VqntLp7VlAUpIXy3MWZMHs2EkPKJP5rJdDL8lFP4=
github.com/maddalax/htmgo/framework v1.0.3-0.20241104193946-1ddeceaa8286 h1:Z7L0W9OZyjrICsnCoLu/GVM33cj4YP7GHlj6/fHPplw=
github.com/maddalax/htmgo/framework v1.0.3-0.20241104193946-1ddeceaa8286/go.mod h1:NGGzWVXWksrQJ9kV9SGa/A1F1Bjsgc08cN7ZVb98RqY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
17 changes: 17 additions & 0 deletions examples/ws-example/internal/embedded/os.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package embedded

import (
"io/fs"
"os"
)

type OsFs struct {
}

func (receiver OsFs) Open(name string) (fs.File, error) {
return os.Open(name)
}

func NewOsFs() OsFs {
return OsFs{}
}
Empty file added examples/ws-example/k6.js
Empty file.
48 changes: 48 additions & 0 deletions examples/ws-example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"github.com/maddalax/htmgo/extensions/websocket"
ws2 "github.com/maddalax/htmgo/extensions/websocket/opts"
"github.com/maddalax/htmgo/extensions/websocket/session"
"github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/service"
"io/fs"
"net/http"
"ws-example/__htmgo"
)

func main() {
locator := service.NewLocator()

h.Start(h.AppOpts{
ServiceLocator: locator,
LiveReload: true,
Register: func(app *h.App) {

app.Use(func(ctx *h.RequestContext) {
session.CreateSession(ctx)
})

websocket.EnableExtension(app, ws2.ExtensionOpts{
WsPath: "/ws",
RoomName: func(ctx *h.RequestContext) string {
return "all"
},
SessionId: func(ctx *h.RequestContext) string {
return ctx.QueryParam("sessionId")
},
})

sub, err := fs.Sub(GetStaticAssets(), "assets/dist")

if err != nil {
panic(err)
}

http.FileServerFS(sub)

app.Router.Handle("/public/*", http.StripPrefix("/public", http.FileServerFS(sub)))
__htmgo.Register(app.Router)
},
})
}
57 changes: 57 additions & 0 deletions examples/ws-example/pages/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package pages

import (
"fmt"
"github.com/maddalax/htmgo/extensions/websocket/session"
"github.com/maddalax/htmgo/extensions/websocket/ws"
"github.com/maddalax/htmgo/framework/h"
"ws-example/partials"
)

func IndexPage(ctx *h.RequestContext) *h.Page {
sessionId := session.GetSessionId(ctx)

return h.NewPage(
RootPage(
ctx,
h.Div(
h.Attribute("ws-connect", fmt.Sprintf("/ws?sessionId=%s", sessionId)),
h.Class("flex flex-col gap-4 items-center pt-24 min-h-screen bg-neutral-100"),
h.H3(
h.Id("intro-text"),
h.Text("Repeater Example"),
h.Class("text-2xl"),
),
h.Div(
h.Id("ws-metrics"),
),
partials.CounterForm(ctx, partials.CounterProps{Id: "counter-1"}),
partials.Repeater(ctx, partials.RepeaterProps{
Id: "repeater-1",
OnAdd: func(data ws.HandlerData) {
//ws.BroadcastServerSideEvent("increment", map[string]any{})
},
OnRemove: func(data ws.HandlerData, index int) {
//ws.BroadcastServerSideEvent("decrement", map[string]any{})
},
AddButton: h.Button(
h.Text("+ Add Item"),
),
RemoveButton: func(index int, children ...h.Ren) *h.Element {
return h.Button(
h.Text("Remove"),
h.Children(children...),
)
},
Item: func(index int) *h.Element {
return h.Input(
"text",
h.Class("border border-gray-300 rounded p-2"),
h.Value(fmt.Sprintf("item %d", index)),
)
},
}),
),
),
)
}
26 changes: 26 additions & 0 deletions examples/ws-example/pages/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pages

import (
"github.com/maddalax/htmgo/framework/h"
)

func RootPage(ctx *h.RequestContext, children ...h.Ren) h.Ren {
return h.Html(
h.JoinExtensions(
h.HxExtension(
h.BaseExtensions(),
),
h.HxExtension("ws"),
),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
),
h.Body(
h.Div(
h.Class("flex flex-col gap-2 bg-white h-full"),
h.Fragment(children...),
),
),
)
}
Loading

0 comments on commit 34e816f

Please sign in to comment.