| res := apiV1ResponseBlank() | res := apiV1ResponseBlank() | ||||
| l := loginForm{} | l := loginForm{} | ||||
| e := l.getFromClient(r) | |||||
| e := l.retrieveLoginForm(r) | |||||
| if e != nil { | if e != nil { | ||||
| log.Warn("Failed login - cannot analyze request " + e.Error()) | log.Warn("Failed login - cannot analyze request " + e.Error()) | ||||
| res.add("login", false) | res.add("login", false) | ||||
| return | return | ||||
| } | } | ||||
| func (m *loginForm) getFromClient(r *http.Request) (e error) { | |||||
| func (m *loginForm) retrieveLoginForm(r *http.Request) (e error) { | |||||
| e = apiV1DecodeRequestBody(m, r) | e = apiV1DecodeRequestBody(m, r) | ||||
| if e != nil { | if e != nil { |
| func apiV1InitSession(r *http.Request) (session loan.Session) { | func apiV1InitSession(r *http.Request) (session loan.Session) { | ||||
| session.MarkEmpty() | session.MarkEmpty() | ||||
| //track browser, and take session from cookie | |||||
| cookieSession, e := apiV1InitSessionByCookie(r) | |||||
| if e == nil { | |||||
| session = cookieSession | |||||
| } | |||||
| //try session login first, if not an empty session will be created | //try session login first, if not an empty session will be created | ||||
| headerSession, e := apiV1InitSessionByHttpHeader(r) | headerSession, e := apiV1InitSessionByHttpHeader(r) | ||||
| if e == nil { | if e == nil { | ||||
| session = headerSession | session = headerSession | ||||
| } else { // if session from header failed, we try cookie session | |||||
| // track browser, and take session from cookie | |||||
| // cookie has disadvantage that multiple tab will get overwritten | |||||
| cookieSession, e := apiV1InitSessionByCookie(r) | |||||
| if e == nil { | |||||
| session = cookieSession | |||||
| } | |||||
| } | } | ||||
| if session.IsEmpty() { | if session.IsEmpty() { | ||||
| session.RenewIfExpireSoon() | session.RenewIfExpireSoon() | ||||
| } | } | ||||
| //we have a session anyway | //we have a session anyway | ||||
| session.Add("Biukop-Mid", apiV1GetMachineId(r)) //set machine id | |||||
| session.SetRemote(r) //make sure they are using latest remote | |||||
| session.Add("Biukop-Mid", apiV1GetMachineId(r)) //set machine id | |||||
| session.Add("Biukop-Socket", apiV1GetSocketId(r)) //set machine id | |||||
| session.SetRemote(r) //make sure they are using latest remote | |||||
| return | return | ||||
| } | } | ||||
| (*w).Header().Set("Access-Control-Allow-Origin", origin) //for that specific origin | (*w).Header().Set("Access-Control-Allow-Origin", origin) //for that specific origin | ||||
| (*w).Header().Set("Access-Control-Allow-Credentials", "true") | (*w).Header().Set("Access-Control-Allow-Credentials", "true") | ||||
| (*w).Header().Set("Access-Control-Allow-Methods", removeDupHeaderOptions("POST, GET, OPTIONS, PUT, DELETE, "+method)) | (*w).Header().Set("Access-Control-Allow-Methods", removeDupHeaderOptions("POST, GET, OPTIONS, PUT, DELETE, "+method)) | ||||
| (*w).Header().Set("Access-Control-Allow-Headers", removeDupHeaderOptions("Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cookie, Biukop-Session, Biukop-Session-Token, Biukop-Session-Expire, "+requestedHeaders)) | |||||
| (*w).Header().Set("Access-Control-Allow-Headers", removeDupHeaderOptions("Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cookie, Biukop-Session, Biukop-Socket , "+requestedHeaders)) | |||||
| } | } | ||||
| func apiV1GetMachineId(r *http.Request) string { | func apiV1GetMachineId(r *http.Request) string { | ||||
| return mid | return mid | ||||
| } | } | ||||
| func apiV1GetSocketId(r *http.Request) string { | |||||
| socketId := r.Header.Get("Biukop-Socket") | |||||
| return socketId | |||||
| } | |||||
| func apiV1GetCORSHeaders(r *http.Request) (ret string) { | func apiV1GetCORSHeaders(r *http.Request) (ret string) { | ||||
| requestedHeaders := r.Header.Get("Access-control-Request-Headers") | requestedHeaders := r.Header.Get("Access-control-Request-Headers") | ||||
| ret = "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cookie, Biukop-Session, Biukop-Session-Token, Biukop-Session-Expire," + requestedHeaders | ret = "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cookie, Biukop-Session, Biukop-Session-Token, Biukop-Session-Expire," + requestedHeaders | ||||
| sessionId := "" | sessionId := "" | ||||
| if session == nil { | if session == nil { | ||||
| log.Warn("non-exist session, empty Id sent", session) | log.Warn("non-exist session, empty Id sent", session) | ||||
| w.Header().Add("Biukop-Session", "") | |||||
| // w.Header().Add("Biukop-Session", "") | |||||
| } else { | } else { | ||||
| if session.Id == "" { | if session.Id == "" { | ||||
| log.Warn("empty session, empty Id sent", session) | log.Warn("empty session, empty Id sent", session) |
| ssh root@c5016.biukop.com.au '/usr/local/lib/sfmapi/local_update.sh' | ssh root@c5016.biukop.com.au '/usr/local/lib/sfmapi/local_update.sh' | ||||
| # remove binary file | # remove binary file | ||||
| rm -f /home/sp/go/src/SFM_Loan_RestApi/api | |||||
| rm -f /home/sp/go/src/SFM_Loan_RestApi/apiv1 |
| wsq.wsDoBroadcast(msg, "") | wsq.wsDoBroadcast(msg, "") | ||||
| } | } | ||||
| func WsSend(To string, msg map[string]string) { | |||||
| func WsSend(To string, msg map[string]string) (e error) { | |||||
| conn := wsq.getConnBySessionId(To) | |||||
| return wsSendByConn(conn, msg) | |||||
| } | |||||
| func wsSendByConn(c *websocket.Conn, msg map[string]string) (e error) { | |||||
| b, e := json.Marshal(msg) | b, e := json.Marshal(msg) | ||||
| if e != nil { | if e != nil { | ||||
| log.Error("cannot broadcast websocket message, cannot convert to json", msg) | log.Error("cannot broadcast websocket message, cannot convert to json", msg) | ||||
| } | } | ||||
| conn := wsq.getConnBySessionId(To) | |||||
| if conn != nil { | |||||
| err := conn.WriteMessage(websocket.TextMessage, b) | |||||
| if err != nil { | |||||
| wsq.del(conn) | |||||
| } | |||||
| e = c.WriteMessage(websocket.TextMessage, b) | |||||
| if e != nil { | |||||
| wsq.del(c) | |||||
| } | } | ||||
| return | |||||
| } | } | ||||
| func WsBroadCastExceptMe(me string, msg map[string]string) { | func WsBroadCastExceptMe(me string, msg map[string]string) { |
| msg["T"] = "login" | msg["T"] = "login" | ||||
| msg["Mid"] = apiV1GetMachineIdFromSession(ss) | msg["Mid"] = apiV1GetMachineIdFromSession(ss) | ||||
| msg["Sid"] = ss.Id | msg["Sid"] = ss.Id | ||||
| msg["SocketId"] = ss.GetStr("Biukop-Socket") | |||||
| msg["Uid"] = ss.User | msg["Uid"] = ss.User | ||||
| msg["Role"] = ss.GetRole() | msg["Role"] = ss.GetRole() | ||||
| WsBroadCastExceptMe(ss.Id, msg) | WsBroadCastExceptMe(ss.Id, msg) | ||||
| msg := make(map[string]string) | msg := make(map[string]string) | ||||
| msg["T"] = "logout" | msg["T"] = "logout" | ||||
| msg["Mid"] = apiV1GetMachineIdFromSession(ss) | msg["Mid"] = apiV1GetMachineIdFromSession(ss) | ||||
| msg["Uid"] = ss.User | |||||
| msg["Sid"] = ss.Id | msg["Sid"] = ss.Id | ||||
| msg["SocketId"] = ss.GetStr("Biukop-Socket") | |||||
| msg["Uid"] = ss.User | |||||
| msg["Role"] = ss.GetRole() | msg["Role"] = ss.GetRole() | ||||
| WsBroadCastExceptMe(ss.Id, msg) | WsBroadCastExceptMe(ss.Id, msg) | ||||
| log.Info(ss, msg) | log.Info(ss, msg) |
| package main | package main | ||||
| import ( | import ( | ||||
| "github.com/brianvoe/gofakeit/v6" | |||||
| "github.com/gorilla/websocket" | "github.com/gorilla/websocket" | ||||
| log "github.com/sirupsen/logrus" | |||||
| "sync" | "sync" | ||||
| ) | ) | ||||
| type wsQ struct { | type wsQ struct { | ||||
| wsMutex sync.Mutex | |||||
| Clients map[*websocket.Conn]string | |||||
| ClientsBySession map[string]*websocket.Conn | |||||
| wsMutex sync.Mutex | |||||
| Clients map[*websocket.Conn]string | |||||
| ClientsBySocketId map[string]*websocket.Conn | |||||
| ClientToSessionId map[*websocket.Conn]string | |||||
| } | } | ||||
| var wsq = wsQ{ | var wsq = wsQ{ | ||||
| Clients: make(map[*websocket.Conn]string), | |||||
| ClientsBySession: make(map[string]*websocket.Conn), | |||||
| Clients: make(map[*websocket.Conn]string), | |||||
| ClientsBySocketId: make(map[string]*websocket.Conn), | |||||
| ClientToSessionId: make(map[*websocket.Conn]string), | |||||
| } | } | ||||
| func (m *wsQ) addConnection(c *websocket.Conn) { | |||||
| func (m *wsQ) addConnection(c *websocket.Conn) (socketId string) { | |||||
| m.wsMutex.Lock() | m.wsMutex.Lock() | ||||
| m.Clients[c] = "" | |||||
| socketId = gofakeit.UUID() | |||||
| m.Clients[c] = socketId | |||||
| m.ClientsBySocketId[socketId] = c | |||||
| m.wsMutex.Unlock() | m.wsMutex.Unlock() | ||||
| m.debugNumOfClients() | |||||
| m.informClient(c, socketId) | |||||
| return | |||||
| } | |||||
| func (m *wsQ) informClient(conn *websocket.Conn, socketId string) { | |||||
| msg := make(map[string]string) | |||||
| msg["T"] = "assign-socketId" | |||||
| msg["socketId"] = socketId | |||||
| _ = wsSendByConn(conn, msg) | |||||
| } | |||||
| func (m *wsQ) debugNumOfClients() { | |||||
| m.wsMutex.Lock() | |||||
| numOfClients := len(m.Clients) | |||||
| numOfClientsById := len(m.ClientsBySocketId) | |||||
| m.wsMutex.Unlock() | |||||
| log.Info("number of concurrent websocket is ", numOfClients, numOfClientsById) | |||||
| } | } | ||||
| func (m *wsQ) MapSessionToConnection(sid string, c *websocket.Conn) { | func (m *wsQ) MapSessionToConnection(sid string, c *websocket.Conn) { | ||||
| m.wsMutex.Lock() | m.wsMutex.Lock() | ||||
| m.ClientsBySession[sid] = c | |||||
| m.Clients[c] = sid | |||||
| m.ClientToSessionId[c] = sid | |||||
| m.wsMutex.Unlock() | |||||
| } | |||||
| func (m *wsQ) RemoveSession(sessionId string, c *websocket.Conn) { | |||||
| m.wsMutex.Lock() | |||||
| for conn, v := range m.ClientToSessionId { | |||||
| if v == sessionId { | |||||
| delete(m.ClientToSessionId, conn) // remove connection to session map | |||||
| if socketId, exist := m.Clients[conn]; exist { | |||||
| delete(m.Clients, conn) | |||||
| delete(m.ClientsBySocketId, socketId) | |||||
| } | |||||
| } | |||||
| } | |||||
| m.wsMutex.Unlock() | m.wsMutex.Unlock() | ||||
| } | } | ||||
| func (m *wsQ) getConnBySessionId(sid string) (conn *websocket.Conn) { | func (m *wsQ) getConnBySessionId(sid string) (conn *websocket.Conn) { | ||||
| conn = nil | conn = nil | ||||
| m.wsMutex.Lock() | m.wsMutex.Lock() | ||||
| for k, v := range m.ClientsBySession { | |||||
| for k, v := range m.ClientsBySocketId { | |||||
| if k == sid { | if k == sid { | ||||
| conn = v | conn = v | ||||
| break | break | ||||
| m.wsMutex.Lock() | m.wsMutex.Lock() | ||||
| if sid, exist := m.Clients[conn]; exist { | if sid, exist := m.Clients[conn]; exist { | ||||
| delete(m.Clients, conn) | delete(m.Clients, conn) | ||||
| delete(m.ClientsBySession, sid) | |||||
| delete(m.ClientsBySocketId, sid) | |||||
| } | } | ||||
| m.wsMutex.Unlock() | m.wsMutex.Unlock() | ||||
| m.debugNumOfClients() | |||||
| } | } | ||||
| func (m *wsQ) delBySessionId(sid string) { | func (m *wsQ) delBySessionId(sid string) { | ||||
| m.wsMutex.Lock() | m.wsMutex.Lock() | ||||
| if conn, exist := m.ClientsBySession[sid]; exist { | |||||
| delete(m.ClientsBySession, sid) | |||||
| if conn, exist := m.ClientsBySocketId[sid]; exist { | |||||
| delete(m.ClientsBySocketId, sid) | |||||
| delete(m.Clients, conn) | delete(m.Clients, conn) | ||||
| } | } | ||||
| m.wsMutex.Unlock() | m.wsMutex.Unlock() | ||||
| m.debugNumOfClients() | |||||
| } | } | ||||
| func (m *wsQ) wsDoBroadcast(msg string, except string) { | func (m *wsQ) wsDoBroadcast(msg string, except string) { | ||||
| toBeRemoved := make([]*websocket.Conn, 0, 20) | toBeRemoved := make([]*websocket.Conn, 0, 20) | ||||
| m.wsMutex.Lock() | m.wsMutex.Lock() | ||||
| for k, _ := range m.Clients { | for k, _ := range m.Clients { | ||||
| if m.ClientsBySession[except] == k { | |||||
| if m.ClientsBySocketId[except] == k { | |||||
| continue | continue | ||||
| } | } | ||||
| err := k.WriteMessage(websocket.TextMessage, []byte(msg)) | err := k.WriteMessage(websocket.TextMessage, []byte(msg)) |