golib/cmd/ssechat/main.go

119 lines
2.5 KiB
Go

package main
import (
_ "embed"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
//go:embed index.html
var indexHTML []byte
type Message struct {
Time string `json:"time"`
Nick string `json:"nick"`
Text string `json:"text"`
}
var sse = NewSSEChannel()
func main() {
mux := http.NewServeMux()
// Serve static HTML
//mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html")
w.Write(indexHTML)
})
// SSE endpoint
mux.HandleFunc("GET /api/events", handleSSE)
// POST endpoint to send messages
mux.HandleFunc("POST /api/send", handleSend)
log.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func handleSSE(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no") // helps nginx / some proxies
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
sse.Subscribe(AddrStr(r.RemoteAddr))
defer sse.Unsubscribe(AddrStr(r.RemoteAddr))
fmt.Fprintf(w, "event: system\ndata: {\"time\":\"%s\",\"text\":\"You joined the room\"}\n\n", time.Now().Format("15:04"))
flusher.Flush()
// Forward messages to this client
for {
select {
case <-r.Context().Done():
return
case msg, ok := <-sse.Member(AddrStr(r.RemoteAddr)).C:
if !ok {
return
}
// SSE format: one data: line with the full JSON
if msg.Event != "" {
fmt.Fprintf(w, "event: %s\n", msg.Event)
}
fmt.Fprintf(w, "%s: %s\n\n", msg.Type, msg.Data)
flusher.Flush()
}
}
}
func handleSend(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad form", http.StatusBadRequest)
return
}
nick := r.FormValue("nick")
text := r.FormValue("text")
if text == "" {
http.Error(w, "Message required", http.StatusBadRequest)
return
}
if nick == "" {
nick = "Anonymous"
}
// In the broadcast loop (handleSend)
msg := Message{
Time: time.Now().Format("15:04"),
Nick: nick,
Text: text,
}
payload, _ := json.Marshal(msg)
sse.Broadcast(payload)
w.WriteHeader(http.StatusAccepted)
}