HTTP client receives status code 200 when server panics with gin-gonic and gin-contrib/gzip

2.1k Views Asked by At

When accessing the gin-gonic server below, the HTTP client should receive the code 500, but receives the code 200.

package main

import (
    "github.com/gin-contrib/gzip"
    "github.com/gin-gonic/gin"
)

func main() {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()
    r.Use(gin.Logger())
    r.Use(gin.Recovery())
    r.Use(gzip.Gzip(gzip.DefaultCompression))

    r.POST("/test", func(c *gin.Context) {
        panic("test")                        // Server panic and client should receive code 500.
    })

    r.Run(":8080")
}

When accessing /test from a HTTP client, the go server log is as below and looks return the code 500.

[GIN] 2020/09/28 - 10:23:14 | 500 |     67.2995ms |             ::1 | POST     "/test"

2020/09/28 10:23:14 [Recovery] 2020/09/28 - 10:23:14 panic recovered:
test
C:/path/to/myproject/main.go:16 (0x8f193f)
    main.func1: panic("test")

But HTTP client receives the code 200.

enter image description here

When I remove r.Use(gzip.Gzip(gzip.DefaultCompression)), the HTTP client receives the code 500.

Why the client receives code 200 with r.Use(gzip.Gzip(gzip.DefaultCompression)), How can I fix this?

2

There are 2 best solutions below

0
On BEST ANSWER

Adding the recovery middleware last seems to fix this.

package main

import (
    "github.com/gin-contrib/gzip"
    "github.com/gin-gonic/gin"
)

func main() {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()
    r.Use(gin.Logger())
    r.Use(gzip.Gzip(gzip.DefaultCompression))
    r.Use(gin.Recovery())

    r.POST("/test", func(c *gin.Context) {
        panic("test")                        // Server panic and client should receive code 500.
    })

    r.Run(":8080")
}
0
On

I've reproduce your case. Postman got code 200 but the server results 500 instead.

The server will call c.Next() to execute 4 handlers when receive post requests. The sequence is as follow:

gin.Logger
gin.Recovery
gzip.Gzip(gzip.DefaultCompression)
your handler

Here is gin responseWriter writes response header and it will write header only once.

func (w *responseWriter) WriteHeaderNow() {
    if !w.Written() {
        w.size = 0
        w.ResponseWriter.WriteHeader(w.status)
    }
}

Both gzip.Gzip(gzip.DefaultCompression) and gin.Recovery has defer func to write response header. Golang's deferred calls are executed in last-in-first-out order. So gzip.Gzip(gzip.DefaultCompression) will write response header to 200, and gin.Recovery won't write reponse header to 500 as expected.

So to solve this problem, you should change the order of handlers and make sure gin.Recovery is the last handler to load.