package wsserver import ( "context" "encoding/json" "flag" "fmt" "log" "net/http" "strings" gamelangpb "git.alfi.li/gamelang/protobuf/gamelang" um "git.alfi.li/gamelang/systems/usermanager" "github.com/gorilla/websocket" ) var wsaddr = flag.String("wsaddr", "localhost:12345", "websocket service address") type WSMsg struct { Mtype string `json:"mtype"` Payload string `json:"payload"` } type Handler struct { MType string Callback func(WSMsg, gamelangpb.User) (WSMsg, error) } type WSServer struct { usermanager *um.UserManager userclient gamelangpb.UserServiceClient upgrader *websocket.Upgrader handler map[string]func(WSMsg, gamelangpb.User) (WSMsg, error) writeChans map[string]chan WSMsg } func NewWSServer(umBroker []string, userclient gamelangpb.UserServiceClient, usermanager *um.UserManager) *WSServer { upgrader := websocket.Upgrader{} // use default options wss := WSServer{upgrader: &upgrader, writeChans: map[string]chan WSMsg{}} if userclient != nil { log.Print("init userclient") wss.userclient = userclient } else if usermanager != nil { log.Print("using usermanager") wss.usermanager = usermanager } else { log.Print("init usermanager") usermanager := um.NewUserManager(umBroker) wss.usermanager = usermanager } return &wss } func (wss *WSServer) Write(name string, sendMsg WSMsg) { chans := wss.GetWriteChans(name) if len(chans) == 0 { log.Println("WSServer:Write - no sendChans for", name) return } for _, con := range chans { con <- sendMsg } } func (wss *WSServer) GetWriteChans(searchname string) []chan WSMsg { cons := []chan WSMsg{} for key, con := range wss.writeChans { username := strings.Split(key, "\uffff")[0] if username == searchname { cons = append(cons, con) } } return cons } func (wss *WSServer) CleanWriteChan(name string) { delete(wss.writeChans, name) } func (wss *WSServer) RegisterHandler(handlers []Handler) { if wss.handler == nil { wss.handler = map[string]func(WSMsg, gamelangpb.User) (WSMsg, error){} } for _, handler := range handlers { log.Println("Registrering handler for MType: ", handler.MType) wss.handler[handler.MType] = handler.Callback } } type rawMsg struct { mtype int p []byte } func (wss WSServer) WebsocketHandler(w http.ResponseWriter, r *http.Request) { log.Println("request on /ws from", r.RemoteAddr) c, err := wss.upgrader.Upgrade(w, r, nil) if err != nil { log.Print("upgrade:", err) return } user := "not logged in" defer wss.CleanWriteChan(fmt.Sprintf("%s\uffff%s", user, r.RemoteAddr)) defer c.Close() var userObj *gamelangpb.User var writeChan chan WSMsg for { mt, message, err := c.ReadMessage() if err != nil { log.Println("read:", err) break } msg := WSMsg{} err = json.Unmarshal(message, &msg) if err != nil { log.Println("error unmarshalling message:", err) log.Printf("recv: %s", message) return } if user == "not logged in" && msg.Mtype != "login" { msg := WSMsg{Mtype: "login-needed", Payload: ""} jsonMsg, _ := json.Marshal(msg) c.WriteMessage(mt, jsonMsg) continue } if msg.Mtype == "login" { log.Println("login request:", msg.Payload) if user == "not logged in" { creds := strings.Split(msg.Payload, "\uffff") if len(creds) != 2 { log.Printf("login failed for \"%s\"", msg.Payload) msg := WSMsg{Mtype: "login-failed", Payload: ""} wss.answer(mt, c, msg) return } username := creds[0] password := creds[1] var ok bool if wss.userclient != nil { log.Print("checking user via userclient") checkuser := gamelangpb.User{Name: username, Password: []byte(password)} var err error var tmpUserObj *gamelangpb.User tmpUserObj, err = wss.userclient.CheckUser(context.Background(), &checkuser) if err != nil { ok = false log.Print(err.Error()) } else { userObj = tmpUserObj ok = true } } else { log.Print("checking user via usermanager") var tmpUserObj gamelangpb.User tmpUserObj, ok = wss.usermanager.CheckUser(username, password) if ok { userObj = &tmpUserObj } } if !ok { log.Printf("login failed for \"%s\" with \"%s\"", username, msg.Payload) msg := WSMsg{Mtype: "login-failed", Payload: ""} wss.answer(mt, c, msg) return } user = username writeChan = make(chan WSMsg, 20) wss.writeChans[fmt.Sprintf("%s\uffff%s", username, r.RemoteAddr)] = writeChan go func() { for { newMsg := <-writeChan err := wss.answer(websocket.BinaryMessage, c, newMsg) if err != nil { if strings.Contains(err.Error(), "use of closed network connection") || strings.Contains(err.Error(), "connection reset by peer") { delete(wss.writeChans, fmt.Sprintf("%s\uffff%s", username, r.RemoteAddr)) } log.Println("websocket writer error:", err.Error()) } } }() log.Printf("login succeeded for \"%s\"", username) msg := WSMsg{Mtype: "login-succeeded", Payload: username} writeChan <- msg } } else { handler, ok := wss.handler[msg.Mtype] if !ok { log.Println("unknown MType", msg.Mtype) keys := "" for k := range wss.handler { keys = fmt.Sprintf("%s\n%s", keys, k) } log.Println("available MTypes:\n", keys, "----") remsg := WSMsg{Mtype: "UNKNOWN", Payload: ""} writeChan <- remsg return } remsg, err := handler(msg, *userObj) if err != nil { log.Println("handler failed", err.Error()) writeChan <- msg } writeChan <- remsg } } } func (wss WSServer) answer(mt int, c *websocket.Conn, msg WSMsg) error { jsonMsg, err := json.Marshal(msg) if err != nil { log.Printf("write: err marshalling \"%s\"\n%s", msg, err.Error()) return err } err = c.WriteMessage(mt, jsonMsg) if err != nil { log.Println("write:", err) return err } return nil }