package cl_104 import ( "context" "encoding/json" "net/http" "sync" "sync/atomic" "time" "github.com/gorilla/websocket" ) const upConnLimit int64 = 10 var upConnNum int64 func (h *CL104) serveUpstream(res http.ResponseWriter, req *http.Request) { select { case <-h.close: res.WriteHeader(http.StatusGone) return default: if atomic.AddInt64(&upConnNum, 1) > upConnLimit { atomic.AddInt64(&upConnNum, -1) res.WriteHeader(http.StatusTooManyRequests) return } } defer atomic.AddInt64(&upConnNum, -1) upConn, err := upgrader.Upgrade(res, req, nil) if err != nil { h.Log.Error(err) return } defer upConn.Close() clConn, _, err := websocket.DefaultDialer.Dial(h.CLURL, nil) if err != nil { h.Log.Error("dial clURL:", err) return } defer clConn.Close() stopCtx, stopCancel := context.WithCancel(context.Background()) defer stopCancel() upSession := &wsSession{ conn: upConn, ctx: stopCtx, cancel: stopCancel, ctrlCh: make(chan wsMsg, 2), } clSession := &wsSession{ conn: clConn, ctx: stopCtx, cancel: stopCancel, ctrlCh: make(chan wsMsg, 2), } if err := h.setupSessionConn(upSession); err != nil { h.Log.Error(err) return } if err := h.setupSessionConn(clSession); err != nil { h.Log.Error(err) return } upChan := make(chan []byte, 16) clChan := make(chan []byte, 16) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() h.startUpWorkersWithCh(upSession, upChan, clChan) }() go func() { defer wg.Done() h.startClWorkersWithCh(clSession, upChan, clChan) }() wg.Wait() } func (h *CL104) startUpWorkers(session *wsSession) { upChan := make(chan []byte, 16) clChan := make(chan []byte, 16) h.startUpWorkersWithCh(session, upChan, clChan) } func (h *CL104) startUpWorkersWithCh(session *wsSession, upChan, clChan chan []byte) { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() h.monitorUpWrite(session, upChan) }() wg.Add(1) go func() { defer wg.Done() h.monitorUpRead(session, clChan) }() wg.Add(1) go func() { defer wg.Done() h.sendPing(session) }() wg.Wait() } func (h *CL104) monitorUpWrite(session *wsSession, upChan chan []byte) { var err error for { select { case <-session.ctx.Done(): return case ctrl := <-session.ctrlCh: err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait))) if err != nil { session.cancel() return } err = session.conn.WriteControl(ctrl.mt, ctrl.data, time.Now().Add(time.Duration(h.WriteWait))) if err != nil { session.cancel() return } case msg := <-upChan: err = session.conn.SetWriteDeadline(time.Now().Add(time.Duration(h.WriteWait))) if err != nil { session.cancel() return } err = session.conn.WriteMessage(websocket.TextMessage, msg) if err != nil { session.cancel() return } } } } func (h *CL104) monitorUpRead(session *wsSession, clChan chan []byte) { for { select { case <-session.ctx.Done(): return default: mt, rm, err := session.conn.ReadMessage() if err != nil { if websocket.IsCloseError(err, websocket.CloseNormalClosure) { session.cancel() return } if ce, ok := err.(*websocket.CloseError); ok { h.Log.Infof("client closed with code:", ce.Code, "text:", ce.Text) session.cancel() return } h.Log.Error("server read:", err) session.cancel() return } switch mt { case websocket.TextMessage: if err := h.fromUpstream(rm, clChan); err != nil { h.Log.Error(err) } default: h.Log.Info("not text:", string(rm)) } } } } func (h *CL104) fromUpstream(m []byte, clChan chan []byte) error { msg := new(msg) if err := json.Unmarshal(m, msg); err != nil { return err } if msg.TI >= 45 && msg.TI <= 69 || msg.TI >= 100 && msg.TI <= 109 { select { case clChan <- m: case <-time.After(time.Second * 5): h.Log.Error("drop cl msg:", msg) } } return nil }