[Go] panic: runtime error: invalid memory address or nil pointer dereference μ—λŸ¬ ν•΄κ²°

2024. 8. 6. 00:11ㆍBackend/🩡 Go

κ°œμš”

fiber ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜μ—¬ μ›Ήμ†ŒμΌ“ μ±„νŒ… μ„œλ²„λ₯Ό 개발 쀑 http μ—μ„œ websocket 으둜 ν”„λ‘œν† μ½œμ„ μ—…κ·Έλ ˆμ΄λ“œ ν•˜λŠ” κ³Όμ •μ—μ„œ λ°œμƒν•œ 문제.

 

μ—λŸ¬ λ°œμƒ

주석에 달아놓은 것 처럼 websocket handler λ‚΄λΆ€μ—μ„œ fiber.Ctx 에 μ ‘κ·Όν•˜μžλ§ˆμž μ—λŸ¬κ°€ λ°œμƒν–ˆλ‹€.

μœ νš¨ν•˜μ§€ μ•Šμ€ λ©”λͺ¨λ¦¬ μ£Όμ†Œμ΄κ±°λ‚˜ nil pointer μ—λŸ¬κ°€ λ°œμƒν–ˆλ‹€λŠ”λ°, ServeHttp() λ©”μ„œλ“œ μ§„μž…μ‹œμ μ—μ„œ c λ₯Ό 둜그둜 ν™•μΈν–ˆμ„ 땐 정상 값이 좜λ ₯됐닀.

즉 websocker ν•Έλ“€λŸ¬ λ‚΄λΆ€μ—μ„œ fiber.Ctx λ₯Ό μ ‘κ·Όν•˜μ§€ λͺ»ν•˜λŠ” 것이 원인이라 μƒκ°ν–ˆλ‹€.

func (r *Room) ServeHTTP(c *fiber.Ctx) error {
   if r == nil {
     return fiber.NewError(fiber.StatusInternalServerError, "Room is not initialized")
   }
   return websocket.New(func(conn *websocket.Conn) {
     if conn == nil {
       log.Println("WebSocket connection failed")
       return
     }
     authCookie := c.Cookies("auth") // log 확인 κ²°κ³Ό ν•΄λ‹Ή λΌμΈμ—μ„œ μ—λŸ¬ λ°œμƒ
     if authCookie == "" {
       log.Println("auth cookie is failed")
       return
     }

     client := &Client{
       Socket: conn,
       Send:   make(chan *message, messageBufferSize),
       Room:   r,
       Name:   authCookie,
     }

     if client == nil {
       log.Println("Failed to create client")
       return
     }

     r.Join <- client

     defer func() {
       if r != nil && client != nil {
         r.Leave <- client
       }
     }()

     go client.Write()
     client.Read()
   })(c)
}

 

원인

원인은 webSocket.New() ν•Έλ“€λŸ¬ ν•¨μˆ˜ λ‚΄μ—μ„œ c *fiber.Ctx에 μ•‘μ„ΈμŠ€ν•  수 μ—†λŠ” λ¬Έμ œμ˜€λ‹€.

μ΄λŠ” WebSocket 연결이 HTTP 연결을 μ—…κ·Έλ ˆμ΄λ“œν•˜λ―€λ‘œ 기쑴의 μ»¨ν…μŠ€νŠΈλ₯Ό 더 이상 μ‚¬μš©ν•  수 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

 

μ™œ fiber의 Context λŠ” websocket λ‚΄μ—μ„œ μ‚¬μš©ν•  수 μ—†μ„κΉŒ?

λ³€μˆ˜ cκ°€ WebSocket ν•Έλ“€λŸ¬ λ‚΄λΆ€μ—μ„œ μ‚¬μš©ν•  수 μ—†λŠ” μ΄μœ λŠ” WebSocket ν”„λ‘œν† μ½œμ˜ νŠΉμ„±κ³Ό Fiber ν”„λ ˆμž„μ›Œν¬μ˜ κ΅¬ν˜„ 방식 λ•Œλ¬Έμ΄λ‹€. 

 

1. Httpμ—μ„œ WebSocket 으둜 λ³€ν™˜

- websocket 연결은 일반 HTTP μš”μ²­μœΌλ‘œ μ‹œμž‘ν•˜μ—¬ ν”„λ‘œν† μ½œ μ—…κ·Έλ ˆμ΄λ“œλ₯Ό 톡해 이뀄진닀.

- μ—…κ·Έλ ˆμ΄λ“œ μ™„λ£Œ μ‹œ, 컀λ„₯μ…˜μ€ 더 이상 Http κ°€ μ•„λ‹Œ WebSocket ν”„λ‘œν† μ½œμ„ μ‚¬μš©ν•œλ‹€.

 

2. Fiber μ»¨ν…μŠ€νŠΈμ˜ 수λͺ…

- c *fiber.Ctx λŠ” http μš”μ²­μ˜ μ»¨ν…μŠ€νŠΈ 정보λ₯Ό λ‹΄κ³  μžˆλ‹€.

- 이 μ»¨ν…μŠ€νŠΈλŠ” http μš”μ²­μ˜ 수λͺ… μ£ΌκΈ° λ™μ•ˆλ§Œ μœ νš¨ν•˜λ‹€. 즉 websocket 으둜 μ—…κ·Έλ ˆμ΄λ“œ 된 μ‹œμ  λΆ€ν„°λŠ” 더 이상 μœ νš¨ν•˜μ§€ μ•Šλ‹€.

 

μ •λ¦¬ν•˜λ©΄ WebSocket ν•Έλ“€λŸ¬ λ‚΄λΆ€μ—μ„œ c *fiber.Ctxλ₯Ό 직접 μ‚¬μš©ν•˜λŠ” 경우 λŸ°νƒ€μž„ μ—λŸ¬κ°€ λ°œμƒν•  수 μžˆλ‹€.

즉 WebSocket μ—°κ²° 전에 ν•„μš”ν•œ 정보(예: μΏ ν‚€, 헀더 λ“±)λ₯Ό 미리 μΆ”μΆœν•˜μ—¬ WebSocket ν•Έλ“€λŸ¬μ— μ „λ‹¬ν•˜λŠ” 방식을 μ‚¬μš©ν•˜λŠ” μ‹μœΌλ‘œ μ½”λ“œλ₯Ό κ°œμ„ ν•΄μ•Όν•œλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ WebSocket μ—°κ²° λ™μ•ˆ ν•„μš”ν•œ 정보λ₯Ό μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ‹€.

 

 

λ³€κ²½λœ μ½”λ“œ

websocket.New() λ‚΄λΆ€μ—μ„œ 직접 fibet.Ctx λ₯Ό μ°Έμ‘°ν•œ κΈ°μ‘΄ μ½”λ“œμ™€ 달리

κ°œμ„ λœ μ½”λ“œμ—μ„œλŠ”  fiber.Ctx μ—μ„œ ν•„μš”ν•œ 정보λ₯Ό 미리 μΆ”μΆœν•˜λŠ” μ‹μœΌλ‘œ λ³€κ²½ν–ˆλ‹€.

κ·Έλ¦¬ν•˜μ—¬ websocket handler λ‚΄λΆ€μ—μ„œλŠ” ν•„μš”ν•œ μ •λ³΄λ§Œ 전달받아 μ‚¬μš©ν•  수 μžˆλ‹€.

func (r *Room) ServeHTTP(c *fiber.Ctx) error {
    if r == nil {
        return fiber.NewError(fiber.StatusInternalServerError, "Room is not initialized")
    }

    authCookie := c.Cookies("auth")
    if authCookie == "" {
        return fiber.NewError(fiber.StatusUnauthorized, "Auth cookie is missing")
    }

    return websocket.New(func(conn *websocket.Conn) {
        if conn == nil {
            log.Println("WebSocket connection failed")
            return
        }

        client := &Client{
            Socket: conn,
            Send:   make(chan *message, messageBufferSize),
            Room:   r,
            Name:   authCookie, // Use the authCookie we got earlier
        }

        if client == nil {
            log.Println("Failed to create client")
            return
        }

        r.Join <- client

        defer func() {
            if r != nil && client != nil {
                r.Leave <- client
            }
        }()

        go client.Write()
        client.Read()
    })(c)
}