웹소켓 채팅 예제
웹소켓을 활용한 간단한 실시간 채팅 예제입니다.
개요
Spine의 웹소켓(WebSocket) 지원을 이용해 실시간 채팅 서버를 구현하는 방법을 보여주는 예제입니다. 한 클라이언트로부터 받은 메시지를 모든 접속된 클라이언트에게 브로드캐스트합니다.
Controller
go
package controller
import (
"context"
"encoding/json"
"strings"
"sync"
"time"
"github.com/NARUBROWN/spine/pkg/ws"
)
type ChatController struct {
mu sync.RWMutex
clients map[string]ws.Sender
}
func NewChatController() *ChatController {
return &ChatController{
clients: make(map[string]ws.Sender),
}
}
type ChatMessage struct {
Message string `json:"message"`
}
type ChatEvent struct {
Type string `json:"type"`
From string `json:"from"`
Message string `json:"message"`
At string `json:"at"`
}
func (c *ChatController) OnMessage(
ctx context.Context,
connID ws.ConnectionID,
msg ChatMessage,
) error {
sender, ok := ctx.Value(ws.SenderKey).(ws.Sender)
if ok && sender != nil {
c.mu.Lock()
c.clients[connID.Value] = sender
c.mu.Unlock()
}
message := strings.TrimSpace(msg.Message)
if message == "" {
return nil
}
payload, err := json.Marshal(ChatEvent{
Type: "message",
From: connID.Value,
Message: message,
At: time.Now().UTC().Format(time.RFC3339),
})
if err != nil {
return err
}
c.mu.RLock()
clients := make(map[string]ws.Sender, len(c.clients))
for id, client := range c.clients {
clients[id] = client
}
c.mu.RUnlock()
var firstErr error
for id, client := range clients {
if err := client.Send(ws.TextMessage, payload); err != nil {
if firstErr == nil {
firstErr = err
}
c.mu.Lock()
delete(c.clients, id)
c.mu.Unlock()
}
}
return firstErr
}Routes
go
package routes
import (
"github.com/NARUBROWN/spine"
"github.com/NARUBROWN/spine-simple-chat-demo/controller"
)
func RegisterChatRoutes(app spine.App) error {
return app.WebSocket().Register("/ws/chat", (*controller.ChatController).OnMessage)
}Main
go
package main
import (
"log"
"time"
"github.com/NARUBROWN/spine"
"github.com/NARUBROWN/spine-simple-chat-demo/controller"
"github.com/NARUBROWN/spine-simple-chat-demo/routes"
"github.com/NARUBROWN/spine/pkg/boot"
)
func main() {
app := spine.New()
app.Constructor(
controller.NewChatController,
)
if err := routes.RegisterChatRoutes(app); err != nil {
log.Fatal(err)
}
if err := app.Run(boot.Options{
Address: ":8080",
EnableGracefulShutdown: true,
ShutdownTimeout: 10 * time.Second,
HTTP: &boot.HTTPOptions{},
}); err != nil {
log.Fatal(err)
}
}