Go сервер падает с nil pointer dereference при отключении клиента по websocket протоколу

Написал (вру, взял из примеров для fasthttp :-)) код для работы по websocket протоколу. При закрытии подключения со стороны читателя (использовал плагин websocket для хрома) мой код падает с этой ошибкой:

ws.conn.Close()  
    panic: runtime error: invalid memory address or nil pointer dereference
    goroutine 96 [running]: github.com/fasthttp/websocket.(*Conn).Close(...)

При этом непонятно что там может быть nil - я ведь даже проверку сделал. Лог выглядит так:

ERRO[02/19 21:46:34]    wshandler.go: 163 [HANDLER  ] ws:txt:read::websocket: close 1005 (no status)
INFO[02/19 21:46:34]    wshandler.go: 143 [WEBSOCKET] ws:reader:close
INFO[02/19 21:46:41]    wshandler.go: 130 [WEBSOCKET] ws:ping

И далее там самая ошибка... использую fiber и middleware webscoket для него, который по сути обертка над fasthttp/websoсket, который обертка над gorilla/websocket.

Сам код работает нормально пока не закроешь подключение c клиента. P.S. Правильно обрабатывать ошибки websocket протокола пока не умею. Просьба пояснить, где я сильно натупил.

const (
    // Time allowed to write a message to the peer.
    writeWait = 30 * time.Second

    // Time allowed to read the next pong message from the peer.
    pongWait = 15 * time.Second

    // Send pings to peer with this period. Must be less than pongWait.
    pingPeriod = (pongWait * 9) / 10

    // Maximum message size allowed from peer.
    maxMessageSize = 512
)


func (ws *WS) writer() {

    ticker := time.NewTicker(pingPeriod)

    defer func() {
        ticker.Stop()
        if ws.conn != nil {
            ws.conn.Close()  
            // panic: runtime error: invalid memory address or nil pointer dereference
            // goroutine 96 [running]: github.com/fasthttp/websocket.(*Conn).Close(...)
            ws.t.Log().Infof(FmtWSInfo, "ws:writer:close")
        } 
    }()

    for {
        select {
        case msg, ok := <-ws.data:

            ws.conn.SetWriteDeadline(time.Now().Add(writeWait))
            if !ok {
                ws.t.Log().Errorf(FmtWSInfo, "ws:deadline")
                ws.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }

            main, err := ws.t.translate(ws.params,msg)
            if err != nil {
                ws.t.log.Errorf(FmtHandlerError, "ws:txt:translate:", err)
                return
            }

            payload := []byte(main.OutText)
            if err = ws.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
                ws.t.Log().Errorf(FmtHandlerError, "ws:txt:write:", err)
                return
            }

        case <-ticker.C:
            ws.t.Log().Infof(FmtWSInfo, "ws:ping")
            ws.conn.SetWriteDeadline(time.Now().Add(writeWait))

            if err := ws.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
                ws.t.Log().Errorf(FmtHandlerError, "ws:ping:", err)
                return
            }
        }
    }
}

func (ws *WS) reader() {
    defer func() {
        ws.t.Log().Infof(FmtWSInfo, "ws:reader:close")
        ws.conn.Close()
    }()
    
    //fmt.Printf("%#v\n", ws)
    
    //ws.conn.SetReadLimit(maxMessageSize)
    ws.conn.SetReadDeadline(time.Now().Add(pongWait))
    ws.conn.SetPongHandler(func(string) error {
        ws.conn.SetReadDeadline(time.Now().Add(pongWait))
        return nil
    })

    for {
        _, msg, err := ws.conn.ReadMessage()
        if err != nil {
            if websocket.IsUnexpectedCloseError(err,
                websocket.CloseGoingAway,
                websocket.CloseAbnormalClosure) {

                ws.t.Log().Errorf(FmtHandlerError, "ws:txt:read:", err)
            }
            break
        }

        ws.data <- msg
    }
}

type WS struct {
    data chan []byte
    conn *websocket.Conn
    t    *Translator
    params map[string]string
}

func (t *Translator) WSHandler() fiber.Handler {

    return websocket.New(func(conn *websocket.Conn) {
        
        p := make(map[string]string)
        p["lang"] = conn.Query("lang")
        p["as"] = conn.Query("as")
        p["format"] = conn.Query("format")
        
        ws := &WS{
            data: make(chan []byte),
            conn: conn,
            t:    t,
            params: p,
        }
        fmt.Printf("%#v\n", ws)
        
        go ws.writer()
        ws.reader()
    })
}

Новый вариант writer, который не вызывает падения:

func (ws *WS) writer() {

    ticker := time.NewTicker(pingPeriod)

    defer func() {
        ticker.Stop()
        if ws.conn.Conn != nil {
            ws.conn.Conn.Close()  
            ws.t.Log().Infof(FmtWSInfo, "ws:writer:close")
        } 
    }()

    for {
        select {
        case msg, ok := <-ws.data:
            //канал ws.data теперь закрывается в reader при выходе
            if !ok {
                ws.t.Log().Errorf(FmtWSInfo, "ws:writer:close chan")
                if ws.conn.Conn == nil {
                    ws.t.Log().Errorf(FmtWSInfo, "ws:writer:connect nil")
                    return
                }
                ws.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }
            
            ws.conn.SetWriteDeadline(time.Now().Add(writeWait)) 
            
            
            main, err := ws.t.translate(ws.params,msg)
            if err != nil {
                ws.t.log.Errorf(FmtHandlerError, "ws:txt:translate:", err)
                return
            }

            payload := []byte(main.OutText)
            if err = ws.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
                ws.t.Log().Errorf(FmtHandlerError, "ws:txt:write:", err)
                return
            }

        case <-ticker.C:
            ws.t.Log().Infof(FmtWSInfo, "ws:ping")
            ws.conn.SetWriteDeadline(time.Now().Add(writeWait))

            if err := ws.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
                ws.t.Log().Errorf(FmtHandlerError, "ws:ping:", err)
                return
            }
        }
    }
}

Лог:

ERRO[02/20 00:09:27]    wshandler.go: 170 [HANDLER  ] ws:txt:read::websocket: close 1005 (no status)
INFO[02/20 00:09:27]    wshandler.go: 150 [WEBSOCKET] ws:reader:close
ERRO[02/20 00:09:27]    wshandler.go: 111 [WEBSOCKET] ws:writer:close chan
ERRO[02/20 00:09:27]    wshandler.go: 113 [WEBSOCKET] ws:writer:connect nil

Ответы (0 шт):