2023-05-24 16:10:00 +09:00
|
|
|
package core
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2023-07-14 01:27:11 +09:00
|
|
|
"encoding/json"
|
2023-05-24 16:10:00 +09:00
|
|
|
"errors"
|
|
|
|
|
"io"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
2023-07-19 09:37:02 +09:00
|
|
|
"time"
|
2023-05-24 16:10:00 +09:00
|
|
|
|
2023-07-19 09:37:02 +09:00
|
|
|
"github.com/go-redis/redis/v8"
|
2023-07-06 00:53:53 +09:00
|
|
|
"repositories.action2quare.com/ayo/gocommon"
|
2023-05-24 16:10:00 +09:00
|
|
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
|
|
|
|
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
|
|
|
|
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
defaultMaxMemory = 32 << 10 // 32 KB
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func writeBsonArr(w io.Writer, src []bson.M) error {
|
|
|
|
|
return writeBsonDoc(w, bson.M{
|
|
|
|
|
"r": src,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeBsonDoc[T any](w io.Writer, src T) error {
|
|
|
|
|
rw, err := bsonrw.NewBSONValueWriter(w)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enc, err := bson.NewEncoder(rw)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return enc.Encode(src)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readBsonDoc(r io.Reader, src any) error {
|
|
|
|
|
body, err := io.ReadAll(r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(body) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decoder, err := bson.NewDecoder(bsonrw.NewBSONDocumentReader(body))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = decoder.Decode(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TavernConfig struct {
|
2023-07-10 15:39:56 +09:00
|
|
|
gocommon.RegionStorageConfig `json:",inline"`
|
2023-05-24 16:10:00 +09:00
|
|
|
|
|
|
|
|
GroupTypes map[string]*groupConfig `json:"tavern_group_types"`
|
|
|
|
|
MaingateApiToken string `json:"maingate_api_token"`
|
2023-07-06 00:53:53 +09:00
|
|
|
RedisURL string `json:"tavern_redis_url"`
|
2023-05-24 16:10:00 +09:00
|
|
|
macAddr string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var config TavernConfig
|
|
|
|
|
|
|
|
|
|
type Tavern struct {
|
|
|
|
|
subTaverns []*subTavern
|
|
|
|
|
wsh *wshandler.WebsocketHandler
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type subTavern struct {
|
2023-07-10 15:39:56 +09:00
|
|
|
mongoClient gocommon.MongoClient
|
2023-07-19 09:37:02 +09:00
|
|
|
redisClient *redis.Client
|
2023-05-24 16:10:00 +09:00
|
|
|
wsh *wshandler.WebsocketHandler
|
|
|
|
|
region string
|
|
|
|
|
groups map[string]group
|
|
|
|
|
methods map[string]reflect.Method
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, inconfig *TavernConfig) (*Tavern, error) {
|
|
|
|
|
if inconfig == nil {
|
|
|
|
|
var loaded TavernConfig
|
2023-07-10 15:39:56 +09:00
|
|
|
if err := gocommon.LoadConfig(&loaded); err != nil {
|
2023-05-24 16:10:00 +09:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
inconfig = &loaded
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config = *inconfig
|
|
|
|
|
macaddr, err := getMacAddr()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
config.macAddr = macaddr
|
2023-07-10 15:39:56 +09:00
|
|
|
tv := &Tavern{
|
2023-05-24 16:10:00 +09:00
|
|
|
wsh: wsh,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = tv.prepare(context); err != nil {
|
|
|
|
|
logger.Println("tavern prepare() failed :", err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-10 15:39:56 +09:00
|
|
|
return tv, nil
|
2023-05-24 16:10:00 +09:00
|
|
|
}
|
|
|
|
|
|
2023-07-10 15:39:56 +09:00
|
|
|
func (tv *Tavern) Cleanup() {
|
2023-05-24 16:10:00 +09:00
|
|
|
for _, st := range tv.subTaverns {
|
|
|
|
|
st.mongoClient.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tv *Tavern) prepare(ctx context.Context) error {
|
2023-07-19 09:37:02 +09:00
|
|
|
for region, addr := range config.RegionStorage {
|
2023-07-10 15:39:56 +09:00
|
|
|
var dbconn gocommon.MongoClient
|
2023-05-24 16:10:00 +09:00
|
|
|
var err error
|
|
|
|
|
var groupinstance group
|
|
|
|
|
|
|
|
|
|
var tmp *subTavern
|
|
|
|
|
methods := make(map[string]reflect.Method)
|
|
|
|
|
tp := reflect.TypeOf(tmp)
|
|
|
|
|
for i := 0; i < tp.NumMethod(); i++ {
|
|
|
|
|
method := tp.Method(i)
|
|
|
|
|
methods[method.Name] = method
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 09:37:02 +09:00
|
|
|
redisClient, err := gocommon.NewRedisClient(addr.Redis["tavern"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 16:10:00 +09:00
|
|
|
sub := &subTavern{
|
|
|
|
|
wsh: tv.wsh,
|
|
|
|
|
mongoClient: dbconn,
|
2023-07-19 09:37:02 +09:00
|
|
|
redisClient: redisClient,
|
2023-05-24 16:10:00 +09:00
|
|
|
region: region,
|
|
|
|
|
methods: methods,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groups := make(map[string]group)
|
|
|
|
|
for typename, cfg := range config.GroupTypes {
|
|
|
|
|
cfg.Name = typename
|
|
|
|
|
if cfg.Transient {
|
2023-07-10 15:39:56 +09:00
|
|
|
groupinstance, err = cfg.prepareInMemory(ctx, typename, sub)
|
|
|
|
|
//} else {
|
2023-07-06 00:53:53 +09:00
|
|
|
// TODO : db
|
|
|
|
|
// if !dbconn.Connected() {
|
2023-07-10 15:39:56 +09:00
|
|
|
// dbconn, err = gocommon.NewMongoClient(ctx, url.Mongo, region)
|
2023-07-06 00:53:53 +09:00
|
|
|
// if err != nil {
|
|
|
|
|
// return err
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// groupinstance, err = cfg.preparePersistent(ctx, region, dbconn, tv.wsh)
|
2023-05-24 16:10:00 +09:00
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
groups[typename] = groupinstance
|
|
|
|
|
}
|
|
|
|
|
sub.groups = groups
|
|
|
|
|
|
|
|
|
|
tv.subTaverns = append(tv.subTaverns, sub)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tv *Tavern) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error {
|
|
|
|
|
for _, sub := range tv.subTaverns {
|
2023-07-19 09:37:02 +09:00
|
|
|
tv.wsh.RegisterReceiver(sub.region, sub)
|
2023-05-24 16:10:00 +09:00
|
|
|
var pattern string
|
|
|
|
|
if sub.region == "default" {
|
2023-07-10 15:39:56 +09:00
|
|
|
pattern = gocommon.MakeHttpHandlerPattern(prefix, "api")
|
2023-05-24 16:10:00 +09:00
|
|
|
} else {
|
2023-07-10 15:39:56 +09:00
|
|
|
pattern = gocommon.MakeHttpHandlerPattern(prefix, sub.region, "api")
|
2023-05-24 16:10:00 +09:00
|
|
|
}
|
|
|
|
|
serveMux.HandleFunc(pattern, sub.api)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 09:37:02 +09:00
|
|
|
func (sub *subTavern) OnClientMessageReceived(sender *wshandler.Sender, messageType wshandler.WebSocketMessageType, body io.Reader) {
|
2023-07-10 15:39:56 +09:00
|
|
|
if messageType == wshandler.Connected {
|
2023-07-19 09:37:02 +09:00
|
|
|
logger.Println("OnClientMessageReceived : connected ", sender.Accid.Hex())
|
2023-07-10 15:39:56 +09:00
|
|
|
} else if messageType == wshandler.Disconnected {
|
2023-07-19 09:37:02 +09:00
|
|
|
logger.Println("OnClientMessageReceived : disconnected ", sender.Accid.Hex())
|
2023-07-14 01:27:11 +09:00
|
|
|
} else if messageType == wshandler.BinaryMessage {
|
2023-07-14 15:09:20 +09:00
|
|
|
var msg map[string][]any
|
2023-07-14 01:27:11 +09:00
|
|
|
dec := json.NewDecoder(body)
|
|
|
|
|
if err := dec.Decode(&msg); err == nil {
|
2023-07-14 15:09:20 +09:00
|
|
|
for cmd, args := range msg {
|
|
|
|
|
switch cmd {
|
|
|
|
|
case "EnterChannel":
|
|
|
|
|
sub.wsh.EnterRoom(sub.region, args[0].(string), sender.Accid)
|
|
|
|
|
|
|
|
|
|
case "LeaveChannel":
|
|
|
|
|
sub.wsh.LeaveRoom(sub.region, args[0].(string), sender.Accid)
|
|
|
|
|
|
|
|
|
|
case "UpdateGroupMemberDocument":
|
|
|
|
|
typename := args[0].(string)
|
|
|
|
|
gidobj, _ := primitive.ObjectIDFromHex(args[1].(string))
|
|
|
|
|
doc := args[2].(map[string]any)
|
|
|
|
|
if group := sub.groups[typename]; group != nil {
|
|
|
|
|
group.UpdateMemberDocument(gidobj, sender.Accid, doc)
|
|
|
|
|
}
|
2023-07-17 17:47:07 +09:00
|
|
|
|
|
|
|
|
case "UpdateGroupDocument":
|
|
|
|
|
typename := args[0].(string)
|
|
|
|
|
gidobj, _ := primitive.ObjectIDFromHex(args[1].(string))
|
|
|
|
|
doc := args[2].(map[string]any)
|
|
|
|
|
if group := sub.groups[typename]; group != nil {
|
|
|
|
|
group.UpdateGroupDocument(gidobj, doc)
|
|
|
|
|
}
|
2023-07-14 15:09:20 +09:00
|
|
|
}
|
2023-07-14 01:27:11 +09:00
|
|
|
}
|
|
|
|
|
}
|
2023-07-10 15:39:56 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 09:37:02 +09:00
|
|
|
func (sub *subTavern) OnRoomCreated(region, name string) {
|
|
|
|
|
_, err := sub.redisClient.Persist(context.Background(), name).Result()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Println("OnRoomCreate Persist failed :", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (sub *subTavern) OnRoomDestroyed(region, name string) {
|
|
|
|
|
_, err := sub.redisClient.Expire(context.Background(), name, 3600*time.Second).Result()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Println("OnRoomDestroyed Persist failed :", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 16:10:00 +09:00
|
|
|
func (sub *subTavern) 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
operation := r.URL.Query().Get("operation")
|
|
|
|
|
if len(operation) == 0 {
|
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method, ok := sub.methods[operation]
|
|
|
|
|
if !ok {
|
|
|
|
|
// 없는 operation
|
|
|
|
|
logger.Println("fail to call api. operation is not valid :", operation)
|
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.PostForm == nil {
|
|
|
|
|
r.ParseMultipartForm(defaultMaxMemory)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args := []reflect.Value{
|
|
|
|
|
reflect.ValueOf(sub),
|
|
|
|
|
reflect.ValueOf(w),
|
|
|
|
|
reflect.ValueOf(r),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method.Func.Call(args)
|
|
|
|
|
}
|