package core import ( "context" "errors" "io" "net" "net/http" "strings" "time" "github.com/go-redis/redis/v8" "github.com/gorilla/websocket" "repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/metric" "repositories.action2quare.com/ayo/gocommon/session" "repositories.action2quare.com/ayo/gocommon/wshandler" "go.mongodb.org/mongo-driver/bson" ) var devflag = flagx.Bool("dev", false, "") type TavernConfig struct { session.SessionConfig `json:",inline"` Group map[string]configDocument `json:"tavern_group_types"` MaingateApiToken string `json:"maingate_api_token"` RedisURL string `json:"tavern_redis_url"` macAddr string } var config TavernConfig type Tavern struct { wsh *wshandler.WebsocketHandler mongoClient gocommon.MongoClient redison *gocommon.RedisonHandler httpApiBorker gocommon.HttpApiBroker } func getMacAddr() (string, error) { ifas, err := net.Interfaces() if err != nil { return "", err } for _, ifa := range ifas { a := ifa.HardwareAddr.String() if a != "" { a = strings.ReplaceAll(a, ":", "") return a, nil } } return "", errors.New("no net interface") } // New : func New(context context.Context, wsh *wshandler.WebsocketHandler) (*Tavern, error) { if err := gocommon.LoadConfig(&config); err != nil { return nil, logger.ErrorWithCallStack(err) } macaddr, err := getMacAddr() if err != nil { return nil, logger.ErrorWithCallStack(err) } config.macAddr = macaddr tv := &Tavern{ wsh: wsh, } if err = tv.prepare(context); err != nil { logger.Println("tavern prepare() failed :", err) return nil, logger.ErrorWithCallStack(err) } return tv, nil } func (tv *Tavern) Cleanup() { metric.ConcurrentUser.Set(0) tv.mongoClient.Close() } func (tv *Tavern) prepare(ctx context.Context) error { redisClient, err := gocommon.NewRedisClient(config.RedisURL) if err != nil { return logger.ErrorWithCallStack(err) } tv.redison = gocommon.NewRedisonHandler(redisClient.Context(), redisClient) tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(tv, "tv")) if cfg, ok := config.Group["chat"]; ok { chat := new(groupChat) if err := chat.Initialize(tv, cfg); err != nil { return logger.ErrorWithCallStack(err) } tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(chat, "chat")) tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(chat, "chat")) } if cfg, ok := config.Group["party"]; ok { party := new(groupParty) if err := party.Initialize(tv, cfg); err != nil { return logger.ErrorWithCallStack(err) } tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(party, "party")) tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(party, "party")) } instant := new(groupInstant) if err := instant.Initialize(tv); err != nil { return logger.ErrorWithCallStack(err) } tv.httpApiBorker.AddHandler(gocommon.MakeHttpApiHandler(instant, "instant")) tv.wsh.AddHandler(wshandler.MakeWebsocketApiHandler(instant, "instant")) return nil } func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error { // tv.wsh.RegisterReceiver(tv) pattern := gocommon.MakeHttpHandlerPattern(prefix, "api") serveMux.HandleFunc(pattern, tv.api) return nil } func (tv *Tavern) EnterChannel(ctx wshandler.ApiCallContext) { tv.wsh.EnterRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid) } func (tv *Tavern) LeaveChannel(ctx wshandler.ApiCallContext) { tv.wsh.LeaveRoom(ctx.Arguments[0].(string), ctx.CallBy.Accid) } func (tv *Tavern) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) { metric.ConcurrentUser.Add(1) tv.redison.Del(tv.redison.Context(), callby.Accid.Hex()) _, err := tv.redison.JSONSet(callby.Accid.Hex(), "$", bson.M{"_ts": time.Now().UTC().Unix()}) if err != nil { logger.Println("OnClientMessageReceived HSet error :", err) } } func (tv *Tavern) ClientDisconnected(msg string, callby *wshandler.Sender) { metric.ConcurrentUser.Add(-1) tv.redison.Del(tv.redison.Context(), callby.Accid.Hex()).Result() } func (tv *Tavern) OnRoomCreated(name string) { cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, 1).Result() if err != nil && !errors.Is(err, redis.Nil) { logger.Println("OnRoomCreated JSONSet failed :", err) return } if cnt == 1 { tv.redison.JSONSet(name, "$", map[string]any{}, gocommon.RedisonSetOptionNX) } } func (tv *Tavern) OnRoomDestroyed(name string) { cnt, err := tv.redison.IncrBy(tv.redison.Context(), "_ref_"+name, -1).Result() if err != nil { logger.Println("OnRoomDestroyed JSONNumIncrBy failed :", err) } else if cnt == 0 { tv.redison.Del(tv.redison.Context(), "_ref_"+name) tv.redison.JSONDel(name, "$") } } func (tv *Tavern) api(w http.ResponseWriter, r *http.Request) { defer func() { s := recover() if s != nil { logger.Error(s) } io.Copy(io.Discard, r.Body) r.Body.Close() }() // 서버에서 오는 요청만 처리 apitoken := r.Header.Get("MG-X-API-TOKEN") if apitoken != config.MaingateApiToken { // 서버가 보내는 쿼리만 허용 logger.Println("MG-X-API-TOKEN is missing") w.WriteHeader(http.StatusBadRequest) return } tv.httpApiBorker.Call(w, r) }