package main import ( "log" "sync" "time" "github.com/gorilla/websocket" ) const TIMEOUT = 6 const IDLE_TIMEOUT = TIMEOUT * time.Second const PING_INTERVAL = (TIMEOUT / 2) * time.Second type WSConnection struct { alive bool url string conn *websocket.Conn errChan chan error writeLock sync.Mutex ReadChan chan string WriteChan chan string Dead chan error } func (ws *WSConnection) messageReader() { log.Printf("Starting reader") for { if !ws.alive { break } _, message, err := ws.conn.ReadMessage() ws.conn.SetReadDeadline(time.Now().Add(IDLE_TIMEOUT)) if err != nil { ws.errChan <- err break } log.Printf("Received: %s, %d in output channel", message, len(ws.ReadChan)) ws.ReadChan <- string(message) } log.Printf("Reader done") } func (ws *WSConnection) messageSender() { log.Printf("Starting sender") for { msg, ok := <-ws.WriteChan if !ok { break } ws.doSend(msg) } log.Printf("Sender done") } func (ws *WSConnection) doSend(msg string) { ws.writeLock.Lock() defer ws.writeLock.Unlock() ws.conn.SetWriteDeadline(time.Now().Add(IDLE_TIMEOUT)) log.Printf("Sending: %s, %d in input channel", msg, len(ws.WriteChan)) err := ws.conn.WriteMessage(websocket.TextMessage, []byte(msg)) if err != nil { log.Printf("Error during message writing: %v", err) ws.errChan <- err return } } func (ws *WSConnection) pinger() { log.Printf("Starting pinger, sleeping for %v", PING_INTERVAL) for { if !ws.alive { break } ws.doPing() time.Sleep(PING_INTERVAL) } log.Printf("Pinger done") } func (ws *WSConnection) doPing() { ws.writeLock.Lock() defer ws.writeLock.Unlock() // log.Printf("Ping") err := ws.conn.WriteMessage(websocket.PingMessage, nil) if err != nil { log.Println("Error during ping:", err) ws.errChan <- err return } ws.conn.SetWriteDeadline(time.Now().Add(IDLE_TIMEOUT)) // log.Printf("Ping OK") } func (ws *WSConnection) handleError() { for { err := <-ws.errChan log.Printf("Client error: %+v", err) ws.alive = false ws.conn.Close() close(ws.ReadChan) close(ws.WriteChan) close(ws.errChan) ws.Dead <- err return } } func (ws *WSConnection) Open() { log.Printf("Connecting to %s", ws.url) ws.Dead = make(chan error, 1) conn, _, err := websocket.DefaultDialer.Dial(ws.url, nil) if err != nil { log.Println("Error during connection:", err) ws.Dead <- err return } log.Printf("Connected") ws.conn = conn ws.alive = true ws.errChan = make(chan error, 1) ws.ReadChan = make(chan string, 128) ws.WriteChan = make(chan string, 128) ws.conn.SetReadDeadline(time.Now().Add(IDLE_TIMEOUT)) ws.conn.SetWriteDeadline(time.Now().Add(IDLE_TIMEOUT)) ws.conn.SetPongHandler(func(string) error { // log.Println("Pong") ws.conn.SetReadDeadline(time.Now().Add(IDLE_TIMEOUT)) return nil }) go ws.handleError() go ws.messageReader() go ws.messageSender() go ws.pinger() }