package wshandler import ( "encoding/json" "io" "reflect" "strings" "unsafe" "repositories.action2quare.com/ayo/gocommon/logger" ) const ( ClientConnected = "ClientConnected" ClientDisconnected = "ClientDisconnected" ) type apiFuncType func(ApiCallContext) type connFuncType func(*Conn, *Sender) type disconnFuncType func(string, *Sender) type WebsocketApiHandler struct { methods map[string]apiFuncType connfunc connFuncType disconnfunc disconnFuncType originalReceiverName string } type ApiCallContext struct { CallBy *Sender Arguments []any } func MakeWebsocketApiHandler[T any](receiver *T, receiverName string) WebsocketApiHandler { methods := make(map[string]apiFuncType) tp := reflect.TypeOf(receiver) if len(receiverName) == 0 { receiverName = tp.Elem().Name() } var connfunc connFuncType var disconnfunc disconnFuncType for i := 0; i < tp.NumMethod(); i++ { method := tp.Method(i) if method.Type.In(0) != tp { continue } if method.Name == ClientConnected { if method.Type.NumIn() != 3 { continue } if method.Type.In(1) != reflect.TypeOf((*Conn)(nil)) { continue } if method.Type.In(2) != reflect.TypeOf((*Sender)(nil)) { continue } funcptr := method.Func.Pointer() p1 := unsafe.Pointer(&funcptr) p2 := unsafe.Pointer(&p1) connfuncptr := (*func(*T, *Conn, *Sender))(p2) connfunc = func(c *Conn, s *Sender) { (*connfuncptr)(receiver, c, s) } } else if method.Name == ClientDisconnected { if method.Type.NumIn() != 3 { continue } if method.Type.In(1) != reflect.TypeOf("") { continue } if method.Type.In(2) != reflect.TypeOf((*Sender)(nil)) { continue } funcptr := method.Func.Pointer() p1 := unsafe.Pointer(&funcptr) p2 := unsafe.Pointer(&p1) disconnfuncptr := (*func(*T, string, *Sender))(p2) disconnfunc = func(msg string, s *Sender) { (*disconnfuncptr)(receiver, msg, s) } } else { if method.Type.NumIn() != 2 { continue } if method.Type.In(1) != reflect.TypeOf((*ApiCallContext)(nil)).Elem() { continue } funcptr := method.Func.Pointer() p1 := unsafe.Pointer(&funcptr) p2 := unsafe.Pointer(&p1) apifuncptr := (*func(*T, ApiCallContext))(p2) methods[receiverName+"."+method.Name] = func(ctx ApiCallContext) { (*apifuncptr)(receiver, ctx) } } } return WebsocketApiHandler{ methods: methods, connfunc: connfunc, disconnfunc: disconnfunc, originalReceiverName: tp.Elem().Name(), } } type WebsocketApiBroker struct { methods map[string]apiFuncType methods_dup map[string][]apiFuncType connFuncs []connFuncType disconnFuncs []disconnFuncType } func (hc *WebsocketApiBroker) AddHandler(receiver WebsocketApiHandler) { if hc.methods == nil { hc.methods = make(map[string]apiFuncType) hc.methods_dup = make(map[string][]apiFuncType) } for k, v := range receiver.methods { ab := strings.Split(k, ".") logger.Printf("ws api registered : %s.%s -> %s\n", receiver.originalReceiverName, ab[1], k) hc.methods_dup[k] = append(hc.methods_dup[k], v) if len(hc.methods_dup[k]) > 1 { chain := hc.methods_dup[k] hc.methods[k] = func(ctx ApiCallContext) { for _, f := range chain { f(ctx) } } } else { hc.methods[k] = v } } if receiver.connfunc != nil { logger.Printf("ws api registered : %s.ClientConnected\n", receiver.originalReceiverName) hc.connFuncs = append(hc.connFuncs, receiver.connfunc) } if receiver.disconnfunc != nil { // disconnfunc은 역순 logger.Printf("ws api registered : %s.ClientDisconnected\n", receiver.originalReceiverName) hc.disconnFuncs = append([]disconnFuncType{receiver.disconnfunc}, hc.disconnFuncs...) } } func (hc *WebsocketApiBroker) ClientConnected(c *wsconn) { for _, v := range hc.connFuncs { v(c.Conn, c.sender) } } func (hc *WebsocketApiBroker) ClientDisconnected(c *wsconn) { for _, v := range hc.disconnFuncs { v(c.closeMessage, c.sender) } } func (hc *WebsocketApiBroker) Call(callby *Sender, funcname string, r io.Reader) { if found := hc.methods[funcname]; found != nil { var args []any if r != nil { dec := json.NewDecoder(r) if err := dec.Decode(&args); err != nil { logger.Println("WebsocketApiBroker.Call failed. decode returns err :", err) } } found(ApiCallContext{ CallBy: callby, Arguments: args, }) } else { logger.Println("api is not found :", funcname) } }