package main import ( "crypto/sha256" "fmt" "io" "log" "net/http" "slices" "strconv" "strings" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) type Message struct { Event string `json:"event"` Data string `json:"data"` Id string `json:"id"` } type ClientChan chan Message type Client struct { Id string `json:"id"` Ip string `json:"ip"` Chan ClientChan `json:"chan"` Events []string `json:"events"` } type Event struct { Clients []Client `json:"clients"` Id int `json:"id"` Events []string `json:"events"` } func main() { r := gin.Default() r.Use(cors.Default()) event := NewServer() api := r.Group("/api") // api.GET("/sse", HeadersMiddleware(), event.serveHTTP(), event.retvalSSE()) api.GET("/sse", HeadersMiddleware(), event.Stream) api.GET("/msg", event.SendMsg) api.GET("/message", event.SendMessage) api.GET("/ping", event.SendPing) api.GET("/pong", event.SendPong) sse := r.Group("/sse") sse.Static("/", "./static/sse") app := r.Group("/_app") app.Static("/", "./static/_app") static := r.Group("/static") static.Static("/", "./static/static") r.StaticFile("/", "./static/index.html") r.StaticFile("/favicon.png", "./static/favicon.png") r.ForwardedByClientIP = true r.SetTrustedProxies([]string{"127.0.0.1"}) r.Run(":8888") } func NewServer() *Event { return &Event{ Clients: []Client{}, Id: 0, Events: []string{ "ping", "pong", "msg", "message", }, } } func HeadersMiddleware() gin.HandlerFunc { return func(c *gin.Context) { headers := c.Writer.Header() headers.Set("Content-Type", "text/event-stream") headers.Set("Cache-Control", "no-cache") headers.Set("Connection", "keep-alive") headers.Set("Transfer-Encoding", "chunked") c.Next() } } func (e *Event) Stream(c *gin.Context) { client := &Client{ Ip: c.Request.RemoteAddr, Chan: make(ClientChan), Events: c.QueryArray("event"), } client.Id = Id(client.Ip, client.Events, 8) log.Printf("Client: %+v", client) e.Clients = append(e.Clients, *client) defer func() { e.Clients = remove(e.Clients, client) close(client.Chan) log.Printf("Client %s disconnected - tot: %d", client.Ip, len(e.Clients)) }() log.Printf("Client %s connected - tot: %d", client.Ip, len(e.Clients)) c.Stream(func(w io.Writer) bool { msg, ok := <-client.Chan if ok { c.SSEvent(msg.Event, msg) } return ok }) } func (e *Event) Send(msg Message) { e.Id++ msg.Id = strconv.Itoa(e.Id) for _, client := range e.Clients { if slices.Contains(client.Events, msg.Event) || slices.Contains(client.Events, "*") { client.Chan <- msg } } } func (e *Event) SendMessage(c *gin.Context) { e.Send(Message{ Event: "message", Data: "Ciao", }) e.Send(Message{ Data: "Senza event", }) } func (e *Event) SendMsg(c *gin.Context) { e.Send(Message{ Event: "msg", Data: "hello", }) c.JSON(http.StatusOK, gin.H{"status": "msg"}) } func (e *Event) SendPing(c *gin.Context) { e.Send(Message{ Event: "ping", Data: "Ping", }) c.JSON(http.StatusOK, gin.H{"status": "ping"}) } func (e *Event) SendPong(c *gin.Context) { e.Send(Message{ Event: "pong", Data: "Pong", }) c.JSON(http.StatusOK, gin.H{"status": "pong"}) } func remove(slice []Client, s *Client) []Client { for i, v := range slice { if v.Id == s.Id { return append(slice[:i], slice[i+1:]...) } } return slice } func Id(ip string, events []string, len int) string { str := ip + strings.Join(events, "|") id := sha256.Sum256([]byte(str)) return fmt.Sprintf("%X", id)[0:len] } // func (e *Event) serveHTTP() gin.HandlerFunc { // return func(c *gin.Context) { // // Initialize client channel // clientChan := make(ClientChan) // // Send new connection to event server // e.NewClient <- clientChan // defer func() { // // Send closed connection to event server // e.ClosedClient <- clientChan // }() // c.Set("clientChan", clientChan) // c.Next() // } // } // func (e *Event) retvalSSE() gin.HandlerFunc { // return func(c *gin.Context) { // v, ok := c.Get("clientChan") // if !ok { // return // } // clientChan, ok := v.(ClientChan) // if !ok { // return // } // c.Stream(func(w io.Writer) bool { // msg, ok := <-clientChan // if ok { // c.SSEvent("message", msg) // } // return ok // }) // } // } // func (e *Event) Broadcast() { // id := 0 // for { // select { // case client := <-e.NewClient: // e.TotalClients[client] = true // case client := <-e.ClosedClient: // delete(e.TotalClients, client) // close(client) // case message := <-e.Message: // id++ // for client := range e.TotalClients { // // msg, _ := json.Marshal(message) // // client <- string(msg) // client <- message // } // } // } // }