Переглянути джерело

websocket not accept registrations

master
sp 4 роки тому
джерело
коміт
abad453b31
8 змінених файлів з 291 додано та 75 видалено
  1. +59
    -0
      apiV1PayoutEx.go
  2. +1
    -1
      apiV1PeopleList.go
  3. +2
    -4
      apiV1logout.go
  4. +128
    -38
      apiv1.go
  5. +50
    -12
      websocket.go
  6. +34
    -0
      websocket_incoming.go
  7. +5
    -3
      websocket_message.go
  8. +12
    -17
      websocket_queue.go

+ 59
- 0
apiV1PayoutEx.go Переглянути файл

package main

import (
"biukop.com/sfm/loan"
log "github.com/sirupsen/logrus"
"net/http"
"strconv"
)

func apiV1PayOutExGet(w http.ResponseWriter, r *http.Request, ss *loan.Session) {
strId := r.URL.Path[len(apiV1Prefix+"payout-ex/"):]
ret := loan.PayOutEx{}
id, e := strconv.Atoi(strId)
if e != nil {
log.Error("invalid id for PayOutEx", strId, e.Error())
apiV1Client403Error(w, r, ss)
}
e = ret.Read(int64(id))
if e != nil {
log.Error("failed to read PayOutEx", strId, e.Error())
apiV1Server500Error(w, r)
}

apiV1SendJson(ret, w, r, ss)
return
}

func apiV1PayOutExListGet(w http.ResponseWriter, r *http.Request, ss *loan.Session) {
// strId := r.URL.Path[len(apiV1Prefix+"payout-ex-list/"):]
input, e := decodeJsonFullLoanOverview(r)

// TODO; Generalize that input, and return a list of filters and sort

// Todo: do some serious query for the data in the Grid, and make it a pattern. Get it done!

if e != nil {
apiV1EmptyResponse(w, r, ss)
} else {

switch ss.GetRole() {
case "broker":
input.Filter.Filters = append(input.Filter.Filters, loan.FullLoanSummaryFilter{
Field: "broker_ids",
Operator: "contains",
Value: loan.JsonString(ss.User)})
break
case "user":
input.Filter.Filters = append(input.Filter.Filters, loan.FullLoanSummaryFilter{
Field: "client_ids",
Operator: "contains",
Value: loan.JsonString(ss.User)})
break
}

data := loan.QFullLLoanSummary(input)
//send out
apiV1SendJson(data, w, r, ss)
}
}

+ 1
- 1
apiV1PeopleList.go Переглянути файл

id := r.URL.Path[len(apiV1Prefix+"people-extra/"):] id := r.URL.Path[len(apiV1Prefix+"people-extra/"):]
ret := UserExtra{} ret := UserExtra{}
ret.Role = loan.GetRoleById(id) ret.Role = loan.GetRoleById(id)
//TODO; check manager and account role
switch ret.Role { switch ret.Role {
case "people": case "people":
apiV1SendJson(ret, w, r, ss) apiV1SendJson(ret, w, r, ss)

+ 2
- 4
apiV1logout.go Переглянути файл

res := apiV1ResponseBlank() res := apiV1ResponseBlank()
WsNotifyLogout(ss) WsNotifyLogout(ss)
ss.Expire = time.Now().Add(-10000) //make sure it expired ss.Expire = time.Now().Add(-10000) //make sure it expired

ssEmpty := loan.Session{}
log.Info("Logout user ", ss.User, " from session ", ss.Id) log.Info("Logout user ", ss.User, " from session ", ss.Id)
//send out //send out
apiV1AddTrackingCookie(w, r, &ssEmpty) //always the last one to set cookies
res.sendJson(w)
apiV1AddTrackingCookie(w, r, ss) //always the last one to set cookies
_, _ = res.sendJson(w)
} }

+ 128
- 38
apiv1.go Переглянути файл

{"GET", "login-available/", apiV1LoginAvailable}, {"GET", "login-available/", apiV1LoginAvailable},


{"POST", "lender-upload/", apiV1UploadsPost}, {"POST", "lender-upload/", apiV1UploadsPost},
{"GET", "lender-upload/", apiV1UploadOriginalFileGet},


{"GET", "upload-analysis/", apiV1UploadAnalysis}, {"GET", "upload-analysis/", apiV1UploadAnalysis},
{"PUT", "upload-analysis/", apiV1UploadCreateAnalysis}, {"PUT", "upload-analysis/", apiV1UploadCreateAnalysis},


{"GET", "lender-list/", apiV1LenderList}, {"GET", "lender-list/", apiV1LenderList},


{"GET", "payout-ex/", apiV1PayOutExGet},

{"GET", "login", apiV1DumpRequest}, {"GET", "login", apiV1DumpRequest},
} }
} else { //production } else { //production
{"GET", "login-available/", apiV1LoginAvailable}, {"GET", "login-available/", apiV1LoginAvailable},


{"POST", "lender-upload/", apiV1UploadsPost}, {"POST", "lender-upload/", apiV1UploadsPost},
{"GET", "lender-upload/", apiV1UploadOriginalFileGet},


{"GET", "upload-analysis/", apiV1UploadAnalysis}, {"GET", "upload-analysis/", apiV1UploadAnalysis},
{"PUT", "upload-analysis/", apiV1UploadCreateAnalysis}, {"PUT", "upload-analysis/", apiV1UploadCreateAnalysis},


{"GET", "lender-list/", apiV1LenderList}, {"GET", "lender-list/", apiV1LenderList},


{"GET", "payout-ex/", apiV1PayOutExGet},

{"GET", "login", apiV1EmptyResponse}, {"GET", "login", apiV1EmptyResponse},
} }
} }
logRequestDebug(httputil.DumpRequest(r, true)) logRequestDebug(httputil.DumpRequest(r, true))
} }


session := apiV1InitSession(r)
if config.Debug {
log.Debugf("session : %+v", session)
session := loan.Session{}
session.MarkEmpty()
bypassSession := apiV1NoNeedSession(r)
if !bypassSession { // no point to create session for preflight
session = apiV1InitSession(r)
if config.Debug {
log.Debugf("session : %+v", session)
}
} }


//search through handler //search through handler
path := r.URL.Path[len(apiV1Prefix):] //strip API prefix path := r.URL.Path[len(apiV1Prefix):] //strip API prefix
handled := false
for _, node := range apiV1Handler { for _, node := range apiV1Handler {
//log.Println(node, path, strings.HasPrefix(path, node.Path))
if (r.Method == node.Method || node.Method == "*") && strings.HasPrefix(path, node.Path) {
if (strings.ToUpper(r.Method) == node.Method || node.Method == "*") && strings.HasPrefix(path, node.Path) {
handled = true
node.Handler(w, r, &session) node.Handler(w, r, &session)
e := session.Write() //finish this session to DB
if e != nil {
log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error())
}
return
break //stop search handler further
}
}

if !bypassSession { // no point to write session for preflight
e := session.Write() //finish this session to DB
if e != nil {
log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error())
} }
} }


//Catch for all UnHandled Request //Catch for all UnHandled Request
e := session.Write() //finish this session to DB
if e != nil {
log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error())
if !handled {
if config.Debug {
apiV1DumpRequest(w, r, &session)
} else {
apiV1EmptyResponse(w, r, &session)
}
} }
if config.Debug {
apiV1DumpRequest(w, r, &session)
} else {
apiV1EmptyResponse(w, r, &session)
}

func apiV1NoNeedSession(r *http.Request) bool {
if r.Method == "OPTIONS" {
return true
} }


path := r.URL.Path[len(apiV1Prefix):] //strip API prefix

if r.Method == "GET" {
if strings.HasPrefix(path, "avatar/") ||
strings.HasPrefix(path, "upload-original/") ||
strings.HasPrefix(path, "upload-as-image/") ||
strings.HasPrefix(path, "upload-as-analysis/") ||
strings.HasPrefix(path, "upload-as-thumbnail/") ||
strings.HasPrefix(path, "upload-as-pdf/") {
return true
}
}
return false
} }


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 //track browser, and take session from cookie
cookieSession, e := apiV1InitSessionByBrowserId(r)
cookieSession, e := apiV1InitSessionByCookie(r)
if e == nil { if e == nil {
session = cookieSession session = cookieSession
} }
method := r.Header.Get("Access-Control-Request-Method") method := r.Header.Get("Access-Control-Request-Method")
(*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", "POST, GET, OPTIONS, PUT, DELETE, "+method)
(*w).Header().Set("Access-Control-Allow-Headers", "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-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))
} }


func apiV1GetMachineId(r *http.Request) string { func apiV1GetMachineId(r *http.Request) string {
return mid return mid
} }


func apiV1GetCORSHeaders(r *http.Request) (ret string) {
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
return removeDupHeaderOptions(ret)
}

func removeDupHeaderOptions(inStr string) (out string) {
headers := map[string]struct{}{}
strings.ReplaceAll(inStr, " ", "") // remove space
headerArray := strings.Split(inStr, ",") // split
for _, v := range headerArray {
headers[v] = struct{}{} // same key will overwrite each other
}
out = ""
for k, _ := range headers {
if out != "" {
out += ", "
}
out += k
}
return
}

func apiV1GetMachineIdFromSession(ss *loan.Session) string { func apiV1GetMachineIdFromSession(ss *loan.Session) string {
return ss.GetStr("Biukop-Mid") return ss.GetStr("Biukop-Mid")
} }
func apiV1InitSessionByBrowserId(r *http.Request) (session loan.Session, e error) {
func apiV1InitSessionByCookie(r *http.Request) (session loan.Session, e error) {
var sid string var sid string
session.MarkEmpty()
mid := apiV1GetMachineId(r) mid := apiV1GetMachineId(r)
inCookie, e := r.Cookie("Biukop-Session") inCookie, e := r.Cookie("Biukop-Session")
if e == nil { if e == nil {
} }


func apiV1AddTrackingCookie(w http.ResponseWriter, r *http.Request, session *loan.Session) { func apiV1AddTrackingCookie(w http.ResponseWriter, r *http.Request, session *loan.Session) {
//set session header too.
w.Header().Add("Access-Control-Expose-Headers", "Biukop-Session")
if strings.ToUpper(r.Method) == "OPTION" {
return
}
w.Header().Add("Access-Control-Expose-Headers", apiV1GetCORSHeaders(r))
apiV1AddTrackingSession(w, r, session)
apiV1AddTrackingMachineId(w, r, session)
}

func apiV1AddTrackingSession(w http.ResponseWriter, r *http.Request, session *loan.Session) {
sessionId := ""
if session == nil { if session == nil {
log.Warn("non-exist session, empty Id sent", session)
w.Header().Add("Biukop-Session", "") w.Header().Add("Biukop-Session", "")
} else { } else {
w.Header().Add("Biukop-Session", session.Id)
if session.Id == "" {
log.Warn("empty session, empty Id sent", session)
} else {
w.Header().Add("Biukop-Session", session.Id)
sessionId = session.Id
}

} }


//add tracking cookie
expiration := time.Now().Add(365 * 24 * time.Hour)
cookie := http.Cookie{
Name: "Biukop-Session",
Value: sessionId, // may be ""
Expires: time.Now().Add(365 * 24 * time.Hour),
Path: "/",
Secure: true,
SameSite: http.SameSiteNoneMode}
http.SetCookie(w, &cookie)

}
func apiV1AddTrackingMachineId(w http.ResponseWriter, r *http.Request, session *loan.Session) {
mid := apiV1GetMachineId(r) mid := apiV1GetMachineId(r)
cookie := http.Cookie{Name: "Biukop-Mid", Value: mid, Expires: expiration, Path: "/", Secure: true, SameSite: http.SameSiteNoneMode}
expiration := time.Now().Add(365 * 24 * time.Hour)

w.Header().Add("Biukop-Mid", mid)

cookie := http.Cookie{
Name: "Biukop-Mid",
Value: mid,
Expires: expiration,
Path: "/",
Secure: true,
SameSite: http.SameSiteNoneMode}
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)


if session != nil {
cookie = http.Cookie{Name: "Biukop-Session", Value: session.Id, Expires: expiration, Path: "/", Secure: true, SameSite: http.SameSiteNoneMode}
http.SetCookie(w, &cookie)
}
} }


func apiV1InitSessionByHttpHeader(r *http.Request) (ss loan.Session, e error) { func apiV1InitSessionByHttpHeader(r *http.Request) (ss loan.Session, e error) {
sid := r.Header.Get("Biukop-Session")
ss.MarkEmpty() ss.MarkEmpty()
sid := apiV1GetSessionIdFromRequest(r)

//make sure session id is given //make sure session id is given
if sid != "" { if sid != "" {
e = ss.Retrieve(r) e = ss.Retrieve(r)
e = errors.New("session not found: " + sid) e = errors.New("session not found: " + sid)
} }
return return
return
} }


func apiV1ErrorCheck(e error) {
if nil != e {
panic(e.Error()) //TODO: detailed error check, truck all caller
func apiV1GetSessionIdFromRequest(r *http.Request) string {
sid := ""
inCookie, e := r.Cookie("Biukop-Session")
if e == nil {
sid = inCookie.Value
}

headerSid := r.Header.Get("Biukop-Session")
if headerSid != "" {
sid = headerSid
} }
return sid
} }


func apiV1Server500Error(w http.ResponseWriter, r *http.Request) { func apiV1Server500Error(w http.ResponseWriter, r *http.Request) {

+ 50
- 12
websocket.go Переглянути файл

return return
} }


mid := apiV1GetMachineId(r)
wsq.MapMidToConnection(mid, ws)

// helpful log statement to show connections // helpful log statement to show connections
log.Println("Websocket Api/V1: Client Connected", mid)
log.Println("Websocket Api/V1: Client Connected", r.RemoteAddr)


wsReader(ws) wsReader(ws)
} }
log.Println(err) log.Println(err)
return return
} }
// print out that message for clarity
// fmt.Println(string(p))


if err := conn.WriteMessage(messageType, p); err != nil {
wsq.del(conn)
log.Println(err)
return
// WsEchoIncomingMessage(conn, string(p), messageType)
switch messageType {
case websocket.TextMessage:
WsProcessingTxtMessage(conn, string(p))
break
case websocket.BinaryMessage:
WsProcessingBinaryMessage(conn, p)
break
case websocket.PingMessage:
break
case websocket.PongMessage:
break
} }


if string(p) == "send dummy string for 500 times" {
go wsDummySender(conn)
}
}

func WsProcessingTxtMessage(conn *websocket.Conn, str string) {
if str == "send dummy string for 500 times" {
go wsDummySender(conn)
}

// must be json message with key, value pairs
incoming := make(map[string]string)
e := json.Unmarshal([]byte(str), &incoming)
if e != nil {
log.Error("ws incoming msg error not json", str, e)
return
}

key := incoming["t"]
for _, v := range apiV1WsHandler {
if key == v.Keyword {
v.Handler(conn, incoming)
return // we have already processed
} }
} }
} }


func WsProcessingBinaryMessage(conn *websocket.Conn, data []byte) {
return
}

func WsEchoIncomingMessage(conn *websocket.Conn, msg string, messageType int) {
// print out that message for clarity
fmt.Println(msg)

// this is echo
if err := conn.WriteMessage(messageType, []byte(msg)); err != nil {
wsq.del(conn)
log.Println(err)
return
}
}

func WsBroadCast(msg string) { func WsBroadCast(msg string) {
wsq.wsDoBroadcast(msg, "") wsq.wsDoBroadcast(msg, "")
} }

+ 34
- 0
websocket_incoming.go Переглянути файл

package main

import (
"fmt"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
)

type apiV1WsHandlerMap struct {
Keyword string // keyword
Handler func(conn *websocket.Conn, msg map[string]string)
}

var apiV1WsHandler = []apiV1WsHandlerMap{
{"echo", apiV1WsEcho},
{"register-session", apiV1WsRegisterSession},
}

func apiV1WsEcho(conn *websocket.Conn, msg map[string]string) {
// print out that message for clarity
fmt.Println(msg)

// this is echo
if err := conn.WriteMessage(websocket.TextMessage, []byte(msg["t"])); err != nil {
wsq.del(conn)
log.Println(err)
}
}

func apiV1WsRegisterSession(conn *websocket.Conn, msg map[string]string) {
sid := msg["session"]
wsq.MapSessionToConnection(sid, conn)
log.Info("ws register session:", sid)
}

+ 5
- 3
websocket_message.go Переглянути файл

package main package main


import "biukop.com/sfm/loan"
import (
"biukop.com/sfm/loan"
log "github.com/sirupsen/logrus"
)


func WsNotifyNewLogin(ss *loan.Session) { func WsNotifyNewLogin(ss *loan.Session) {

msg := make(map[string]string) msg := make(map[string]string)
msg["T"] = "login" msg["T"] = "login"
msg["Mid"] = apiV1GetMachineIdFromSession(ss) msg["Mid"] = apiV1GetMachineIdFromSession(ss)
} }


func WsNotifyLogout(ss *loan.Session) { func WsNotifyLogout(ss *loan.Session) {

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["Sid"] = ss.Id msg["Sid"] = ss.Id
msg["Role"] = ss.GetRole() msg["Role"] = ss.GetRole()
WsBroadCastExceptMe(ss.Id, msg) WsBroadCastExceptMe(ss.Id, msg)
log.Info(ss, msg)
} }

+ 12
- 17
websocket_queue.go Переглянути файл



type wsQ struct { type wsQ struct {
wsMutex sync.Mutex wsMutex sync.Mutex
Clients map[*websocket.Conn]struct{}
Clients map[*websocket.Conn]string
ClientsBySession map[string]*websocket.Conn ClientsBySession map[string]*websocket.Conn
} }


var wsq = wsQ{ var wsq = wsQ{
Clients: make(map[*websocket.Conn]struct{}),
Clients: make(map[*websocket.Conn]string),
ClientsBySession: make(map[string]*websocket.Conn), ClientsBySession: make(map[string]*websocket.Conn),
} }


func (m *wsQ) addConnection(c *websocket.Conn) { func (m *wsQ) addConnection(c *websocket.Conn) {
m.wsMutex.Lock() m.wsMutex.Lock()
m.Clients[c] = struct{}{}
m.Clients[c] = ""
m.wsMutex.Unlock() m.wsMutex.Unlock()
} }


func (m *wsQ) MapMidToConnection(mid string, c *websocket.Conn) {
func (m *wsQ) MapSessionToConnection(sid string, c *websocket.Conn) {
m.wsMutex.Lock() m.wsMutex.Lock()
m.ClientsBySession[mid] = c
m.ClientsBySession[sid] = c
m.Clients[c] = sid
m.wsMutex.Unlock() m.wsMutex.Unlock()
} }




func (m *wsQ) del(conn *websocket.Conn) { func (m *wsQ) del(conn *websocket.Conn) {
m.wsMutex.Lock() m.wsMutex.Lock()
delete(m.Clients, conn)
for k, v := range m.ClientsBySession {
if v == conn {
delete(m.ClientsBySession, k)
break
}
if sid, exist := m.Clients[conn]; exist {
delete(m.Clients, conn)
delete(m.ClientsBySession, sid)
} }
m.wsMutex.Unlock() m.wsMutex.Unlock()
} }


func (m *wsQ) delBySessionId(sid string) { func (m *wsQ) delBySessionId(sid string) {
m.wsMutex.Lock() m.wsMutex.Lock()
for k, v := range m.ClientsBySession {
if k == sid {
delete(m.ClientsBySession, k)
break
}
delete(m.Clients, v)
if conn, exist := m.ClientsBySession[sid]; exist {
delete(m.ClientsBySession, sid)
delete(m.Clients, conn)
} }
m.wsMutex.Unlock() m.wsMutex.Unlock()
} }

Завантаження…
Відмінити
Зберегти