코드 정리 및 websocket도 http와 비슷하게 api handler로 통일
This commit is contained in:
139
wshandler/api_handler.go
Normal file
139
wshandler/api_handler.go
Normal file
@ -0,0 +1,139 @@
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientConnected = "ClientConnected"
|
||||
ClientDisconnected = "ClientDisconnected"
|
||||
)
|
||||
|
||||
type apiFuncType func(ApiCallContext)
|
||||
|
||||
type WebsocketApiHandler struct {
|
||||
methods map[string]apiFuncType
|
||||
connfunc apiFuncType
|
||||
disconnfunc apiFuncType
|
||||
}
|
||||
|
||||
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 apiFuncType
|
||||
var disconnfunc apiFuncType
|
||||
|
||||
for i := 0; i < tp.NumMethod(); i++ {
|
||||
method := tp.Method(i)
|
||||
if method.Type.NumIn() != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if method.Type.In(0) != tp {
|
||||
continue
|
||||
}
|
||||
|
||||
if method.Type.In(1) != reflect.TypeOf((*ApiCallContext)(nil)).Elem() {
|
||||
continue
|
||||
}
|
||||
|
||||
funcptr := method.Func.Pointer()
|
||||
p1 := unsafe.Pointer(&funcptr)
|
||||
p2 := unsafe.Pointer(&p1)
|
||||
testfunc := (*func(*T, ApiCallContext))(p2)
|
||||
|
||||
if method.Name == ClientConnected {
|
||||
connfunc = func(ctx ApiCallContext) {
|
||||
(*testfunc)(receiver, ctx)
|
||||
}
|
||||
} else if method.Name == ClientDisconnected {
|
||||
disconnfunc = func(ctx ApiCallContext) {
|
||||
(*testfunc)(receiver, ctx)
|
||||
}
|
||||
} else {
|
||||
methods[receiverName+"."+method.Name] = func(ctx ApiCallContext) {
|
||||
(*testfunc)(receiver, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WebsocketApiHandler{
|
||||
methods: methods,
|
||||
connfunc: connfunc,
|
||||
disconnfunc: disconnfunc,
|
||||
}
|
||||
}
|
||||
|
||||
type WebsocketApiBroker struct {
|
||||
methods map[string]apiFuncType
|
||||
connFuncs []apiFuncType
|
||||
disconnFuncs []apiFuncType
|
||||
}
|
||||
|
||||
func (hc *WebsocketApiBroker) AddHandler(receiver WebsocketApiHandler) {
|
||||
if hc.methods == nil {
|
||||
hc.methods = make(map[string]apiFuncType)
|
||||
}
|
||||
|
||||
for k, v := range receiver.methods {
|
||||
logger.Println("http api registered :", k)
|
||||
hc.methods[k] = v
|
||||
}
|
||||
|
||||
if receiver.connfunc != nil {
|
||||
hc.connFuncs = append(hc.connFuncs, receiver.connfunc)
|
||||
}
|
||||
|
||||
if receiver.disconnfunc != nil {
|
||||
// disconnfunc은 역순
|
||||
hc.disconnFuncs = append([]apiFuncType{receiver.disconnfunc}, hc.disconnFuncs...)
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *WebsocketApiBroker) Call(callby *Sender, funcname string, r io.Reader) {
|
||||
if funcname == ClientConnected {
|
||||
for _, v := range hc.connFuncs {
|
||||
v(ApiCallContext{
|
||||
CallBy: callby,
|
||||
Arguments: nil,
|
||||
})
|
||||
}
|
||||
} else if funcname == ClientDisconnected {
|
||||
for _, v := range hc.disconnFuncs {
|
||||
v(ApiCallContext{
|
||||
CallBy: callby,
|
||||
Arguments: nil,
|
||||
})
|
||||
}
|
||||
} else 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)
|
||||
}
|
||||
}
|
||||
30
wshandler/api_handler_test.go
Normal file
30
wshandler/api_handler_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
// package main ...
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestReceiver struct {
|
||||
}
|
||||
|
||||
func (tr *TestReceiver) Func1([]any) {
|
||||
|
||||
}
|
||||
|
||||
func (tr *TestReceiver) Func2(args []any) {
|
||||
fmt.Println(args...)
|
||||
}
|
||||
|
||||
func TestExpTable(t *testing.T) {
|
||||
// src := []any{"a", 1, false}
|
||||
// payload, _ := json.Marshal(src)
|
||||
|
||||
// tr := new(TestReceiver)
|
||||
// receiver := MakeWebsocketApiHandler(tr, "test")
|
||||
|
||||
// var con WebsocketApiBroker
|
||||
// con.AddHandler(receiver)
|
||||
|
||||
}
|
||||
@ -90,30 +90,11 @@ const (
|
||||
CloseMessage = WebSocketMessageType(websocket.CloseMessage)
|
||||
PingMessage = WebSocketMessageType(websocket.PingMessage)
|
||||
PongMessage = WebSocketMessageType(websocket.PongMessage)
|
||||
Connected = WebSocketMessageType(100)
|
||||
Disconnected = WebSocketMessageType(101)
|
||||
)
|
||||
|
||||
type Sender struct {
|
||||
Accid primitive.ObjectID
|
||||
Alias string
|
||||
disconnectedCallbacks map[string]func()
|
||||
}
|
||||
|
||||
func (s *Sender) RegistDisconnectedCallback(name string, f func()) (old func()) {
|
||||
if s.disconnectedCallbacks == nil {
|
||||
s.disconnectedCallbacks = make(map[string]func())
|
||||
}
|
||||
|
||||
old = s.disconnectedCallbacks[name]
|
||||
s.disconnectedCallbacks[name] = f
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Sender) PopDisconnectedCallback(name string) func() {
|
||||
old := s.disconnectedCallbacks[name]
|
||||
delete(s.disconnectedCallbacks, name)
|
||||
return old
|
||||
Accid primitive.ObjectID
|
||||
Alias string
|
||||
}
|
||||
|
||||
type EventReceiver interface {
|
||||
@ -136,10 +117,10 @@ type WebsocketHandler struct {
|
||||
deliveryChan chan any
|
||||
localDeliveryChan chan any
|
||||
sendMsgChan chan send_msg_queue_elem
|
||||
callReceiver EventReceiver
|
||||
connWaitGroup sync.WaitGroup
|
||||
receiverChain []EventReceiver
|
||||
sessionConsumer session.Consumer
|
||||
|
||||
wsApiBroker WebsocketApiBroker
|
||||
connWaitGroup sync.WaitGroup
|
||||
sessionConsumer session.Consumer
|
||||
}
|
||||
|
||||
type wsConfig struct {
|
||||
@ -156,13 +137,13 @@ func init() {
|
||||
gob.Register([]any{})
|
||||
}
|
||||
|
||||
func NewWebsocketHandler(consumer session.Consumer) (*WebsocketHandler, error) {
|
||||
func NewWebsocketHandler(consumer session.Consumer, redisUrl string) (*WebsocketHandler, error) {
|
||||
var config wsConfig
|
||||
if err := gocommon.LoadConfig(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
redisSync, err := gocommon.NewRedisClient(config.Redis["wshandler"])
|
||||
redisSync, err := gocommon.NewRedisClient(redisUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -196,49 +177,11 @@ func NewWebsocketHandler(consumer session.Consumer) (*WebsocketHandler, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ws *WebsocketHandler) RegisterReceiver(receiver EventReceiver) {
|
||||
ws.receiverChain = append(ws.receiverChain, receiver)
|
||||
}
|
||||
|
||||
type nilReceiver struct{}
|
||||
|
||||
func (r *nilReceiver) OnClientMessageReceived(sender *Sender, messageType WebSocketMessageType, body io.Reader) {
|
||||
}
|
||||
func (r *nilReceiver) OnRoomCreated(name string) {}
|
||||
func (r *nilReceiver) OnRoomDestroyed(name string) {}
|
||||
|
||||
type chainReceiver struct {
|
||||
chain []EventReceiver
|
||||
}
|
||||
|
||||
func (r *chainReceiver) OnClientMessageReceived(sender *Sender, messageType WebSocketMessageType, body io.Reader) {
|
||||
for _, cr := range r.chain {
|
||||
cr.OnClientMessageReceived(sender, messageType, body)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *chainReceiver) OnRoomCreated(name string) {
|
||||
for _, cr := range r.chain {
|
||||
cr.OnRoomCreated(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *chainReceiver) OnRoomDestroyed(name string) {
|
||||
for _, cr := range r.chain {
|
||||
cr.OnRoomDestroyed(name)
|
||||
}
|
||||
func (ws *WebsocketHandler) RegisterApiHandler(handler WebsocketApiHandler) {
|
||||
ws.wsApiBroker.AddHandler(handler)
|
||||
}
|
||||
|
||||
func (ws *WebsocketHandler) Start(ctx context.Context) {
|
||||
chain := ws.receiverChain
|
||||
if len(chain) == 0 {
|
||||
ws.callReceiver = &nilReceiver{}
|
||||
} else if len(chain) == 1 {
|
||||
ws.callReceiver = chain[0]
|
||||
} else {
|
||||
ws.callReceiver = &chainReceiver{chain: ws.receiverChain}
|
||||
}
|
||||
|
||||
ws.connWaitGroup.Add(1)
|
||||
go ws.mainLoop(ctx)
|
||||
}
|
||||
@ -334,19 +277,14 @@ func (ws *WebsocketHandler) mainLoop(ctx context.Context) {
|
||||
room = makeRoom(name, roomDestroyChan, ws.sendMsgChan)
|
||||
rooms[name] = room
|
||||
room.start(ctx)
|
||||
go ws.callReceiver.OnRoomCreated(name)
|
||||
//go ws.callReceiver.OnRoomCreated(name)
|
||||
}
|
||||
return room
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, conn := range entireConns {
|
||||
var roomnames []string
|
||||
for _, room := range conn.joinedRooms {
|
||||
roomnames = append(roomnames, room.name)
|
||||
}
|
||||
bt, _ := json.Marshal(roomnames)
|
||||
ws.callReceiver.OnClientMessageReceived(conn.sender, Disconnected, bytes.NewBuffer(bt))
|
||||
ws.wsApiBroker.Call(conn.sender, ClientDisconnected, nil)
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
@ -442,7 +380,7 @@ func (ws *WebsocketHandler) mainLoop(ctx context.Context) {
|
||||
|
||||
case destroyedRoom := <-roomDestroyChan:
|
||||
delete(rooms, destroyedRoom)
|
||||
go ws.callReceiver.OnRoomDestroyed(destroyedRoom)
|
||||
//go ws.callReceiver.OnRoomDestroyed(destroyedRoom)
|
||||
|
||||
case usermsg := <-ws.localDeliveryChan:
|
||||
// 로컬에 connection이 있는지 먼저 확인해 보기 위한 채널
|
||||
@ -509,18 +447,14 @@ func (ws *WebsocketHandler) mainLoop(ctx context.Context) {
|
||||
case c := <-ws.connInOutChan:
|
||||
if c.Conn == nil {
|
||||
delete(entireConns, c.sender.Accid.Hex())
|
||||
var roomnames []string
|
||||
for _, room := range c.joinedRooms {
|
||||
roomnames = append(roomnames, room.name)
|
||||
room.out(c)
|
||||
}
|
||||
c.joinedRooms = nil
|
||||
|
||||
bt, _ := json.Marshal(roomnames)
|
||||
go ws.callReceiver.OnClientMessageReceived(c.sender, Disconnected, bytes.NewBuffer(bt))
|
||||
go ws.wsApiBroker.Call(c.sender, ClientDisconnected, nil)
|
||||
} else {
|
||||
entireConns[c.sender.Accid.Hex()] = c
|
||||
go ws.callReceiver.OnClientMessageReceived(c.sender, Connected, nil)
|
||||
go ws.wsApiBroker.Call(c.sender, ClientConnected, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -549,16 +483,12 @@ func upgrade_core(ws *WebsocketHandler, conn *websocket.Conn, accid primitive.Ob
|
||||
break
|
||||
}
|
||||
|
||||
if messageType == websocket.TextMessage {
|
||||
// 유저가 직접 보낸 메시지
|
||||
ws.callReceiver.OnClientMessageReceived(c.sender, TextMessage, r)
|
||||
} else if messageType == websocket.BinaryMessage {
|
||||
ws.callReceiver.OnClientMessageReceived(c.sender, BinaryMessage, r)
|
||||
}
|
||||
}
|
||||
if c.sender.disconnectedCallbacks != nil {
|
||||
for _, f := range c.sender.disconnectedCallbacks {
|
||||
f()
|
||||
if messageType == websocket.BinaryMessage {
|
||||
var size [1]byte
|
||||
r.Read(size[:])
|
||||
cmd := make([]byte, size[0])
|
||||
r.Read(cmd)
|
||||
ws.wsApiBroker.Call(newconn.sender, string(cmd), r)
|
||||
}
|
||||
}
|
||||
ws.connWaitGroup.Done()
|
||||
|
||||
Reference in New Issue
Block a user