123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- 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
- }
|