Description

在给gin服务添加超时控制的时候,由于一个接口需要返回重定向,然后导致了进程panic,怀疑是

// Redirect returns an HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) {
    c.Render(-1, render.Redirect{
        Code:     code,
        Location: location,
        Request:  c.Request,
    })
}

返回-1导致的,请问一下这里为啥写死是-1? 报错信息

Error:invalid http status code: -1
Call Stack:/gopath/pkg/mod/[github.com/gin-contrib/timeout@v0.0.3/timeout.go:63](http://github.com/gin-contrib/timeout@v0.0.3/timeout.go:63) (0xe89731)

How to reproduce

func timeoutResponse(c *gin.Context) {
    c.JSON(http.StatusGatewayTimeout, gin.H{
        "code": 12,
        "msg":  "timeout",
    })
}

// Timeout timeout中间件
// sample: [0, 100] 表示采样比例,可以通过修改采样比例来灰度
func Timeout(sample int32) gin.HandlerFunc {
    num := rand.Int31n(101)
    if num < sample {
        return timeout.New(
            timeout.WithTimeout(6*time.Second),
            timeout.WithHandler(func(c *gin.Context) {
                c.Next()
            }),
            timeout.WithResponse(timeoutResponse),
        )
    }

    return func(c *gin.Context) {
        c.Next()
    }
}

Expectations

返回504 timeout

Actual result

panic

Environment

  • go version: 1.18
  • gin version (or commit ref): 1.8.1
  • operating system: linux

Comment From: fatelei

please apply panic traceback

Comment From: BigeYoung

Test Code

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "time"

    "github.com/gin-contrib/timeout"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.Use(timeout.New(
        timeout.WithTimeout(time.Second),
        timeout.WithHandler(func(c *gin.Context) {
            c.Next()
        }),
        timeout.WithResponse(func(c *gin.Context) {
            c.AbortWithStatus(http.StatusGatewayTimeout)
        }),
    ))
    r.GET("/redirect", func(c *gin.Context) {
        c.Redirect(http.StatusFound, "https://www.github.com/")
    })

    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/redirect", nil)
    r.ServeHTTP(w, req)

    fmt.Print(w.Body.String())
}

Result:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /redirect                 --> main.main.func3 (4 handlers)


2023/06/01 12:01:27 [Recovery] 2023/06/01 - 12:01:27 panic recovered:
GET /redirect HTTP/1.1


invalid http status code: -1
/Users/xxx/go/pkg/mod/github.com/gin-contrib/timeout@v0.0.3/timeout.go:63 (0x1027f6be3)
        New.func1: panic(p)
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f110f)
        (*Context).Next: c.handlers[c.index](c)
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/recovery.go:102 (0x1027f10f0)
        CustomRecoveryWithWriter.func1: c.Next()
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f037f)
        (*Context).Next: c.handlers[c.index](c)
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/logger.go:240 (0x1027f035c)
        LoggerWithConfig.func1: c.Next()
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027ef497)
        (*Context).Next: c.handlers[c.index](c)
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:620 (0x1027ef16c)
        (*Engine).handleHTTPRequest: c.Next()
/Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:576 (0x1027eeec7)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/Users/xxx/Desktop/gorm-playground/main.go:30 (0x1027f79ef)
        main: r.ServeHTTP(w, req)
/usr/local/go/src/runtime/proc.go:250 (0x1025d180f)
        main: fn()
/usr/local/go/src/runtime/asm_arm64.s:1270 (0x1025fe833)
        goexit: MOVD    R0, R0  // NOP

[GIN] 2023/06/01 - 12:01:27 | 500 |    1.914833ms |                 | GET      "/redirect"

Comment From: fatelei

go StatusGatewayTimeout

Gin 重定向c.Render(-1设置http status code为-1会导致timeout超时控制panic

-1 is not a valid http status code

Comment From: x-lambda

Test Code

```go package main

import ( "fmt" "net/http" "net/http/httptest" "time"

"github.com/gin-contrib/timeout" "github.com/gin-gonic/gin" )

func main() { r := gin.Default() r.Use(timeout.New( timeout.WithTimeout(time.Second), timeout.WithHandler(func(c gin.Context) { c.Next() }), timeout.WithResponse(func(c gin.Context) { c.AbortWithStatus(http.StatusGatewayTimeout) }), )) r.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusFound, "https://www.github.com/") })

w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/redirect", nil) r.ServeHTTP(w, req)

fmt.Print(w.Body.String()) } ```

Result:

``` [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /redirect --> main.main.func3 (4 handlers)

2023/06/01 12:01:27 [Recovery] 2023/06/01 - 12:01:27 panic recovered: GET /redirect HTTP/1.1

invalid http status code: -1 /Users/xxx/go/pkg/mod/github.com/gin-contrib/timeout@v0.0.3/timeout.go:63 (0x1027f6be3) New.func1: panic(p) /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f110f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/recovery.go:102 (0x1027f10f0) CustomRecoveryWithWriter.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f037f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/logger.go:240 (0x1027f035c) LoggerWithConfig.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027ef497) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:620 (0x1027ef16c) (Engine).handleHTTPRequest: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:576 (0x1027eeec7) (*Engine).ServeHTTP: engine.handleHTTPRequest(c) /Users/xxx/Desktop/gorm-playground/main.go:30 (0x1027f79ef) main: r.ServeHTTP(w, req) /usr/local/go/src/runtime/proc.go:250 (0x1025d180f) main: fn() /usr/local/go/src/runtime/asm_arm64.s:1270 (0x1025fe833) goexit: MOVD R0, R0 // NOP

[GIN] 2023/06/01 - 12:01:27 | 500 | 1.914833ms | | GET "/redirect" ```

我看了是其实是github.com/gin-contrib/timeout库的问题,gin使用-1跳过状态码赋值,但是这个库没有处理,参考这个老哥的pr https://github.com/gin-contrib/timeout/pull/37/files

Comment From: fatelei

Test Code ```go package main

import ( "fmt" "net/http" "net/http/httptest" "time"

"github.com/gin-contrib/timeout"
"github.com/gin-gonic/gin"

)

func main() { r := gin.Default() r.Use(timeout.New( timeout.WithTimeout(time.Second), timeout.WithHandler(func(c gin.Context) { c.Next() }), timeout.WithResponse(func(c gin.Context) { c.AbortWithStatus(http.StatusGatewayTimeout) }), )) r.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusFound, "https://www.github.com/") })

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/redirect", nil)
r.ServeHTTP(w, req)

fmt.Print(w.Body.String())

} ```

Result: ``` [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /redirect --> main.main.func3 (4 handlers)

2023/06/01 12:01:27 [Recovery] 2023/06/01 - 12:01:27 panic recovered: GET /redirect HTTP/1.1

invalid http status code: -1 /Users/xxx/go/pkg/mod/github.com/gin-contrib/timeout@v0.0.3/timeout.go:63 (0x1027f6be3) New.func1: panic(p) /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f110f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/recovery.go:102 (0x1027f10f0) CustomRecoveryWithWriter.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f037f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/logger.go:240 (0x1027f035c) LoggerWithConfig.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027ef497) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:620 (0x1027ef16c) (Engine).handleHTTPRequest: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:576 (0x1027eeec7) (*Engine).ServeHTTP: engine.handleHTTPRequest(c) /Users/xxx/Desktop/gorm-playground/main.go:30 (0x1027f79ef) main: r.ServeHTTP(w, req) /usr/local/go/src/runtime/proc.go:250 (0x1025d180f) main: fn() /usr/local/go/src/runtime/asm_arm64.s:1270 (0x1025fe833) goexit: MOVD R0, R0 // NOP

[GIN] 2023/06/01 - 12:01:27 | 500 | 1.914833ms | | GET "/redirect" ```

我看了是其实是github.com/gin-contrib/timeout库的问题,gin使用-1跳过状态码赋值,但是这个库没有处理,参考这个老哥的pr https://github.com/gin-contrib/timeout/pull/37/files

so this is a gin bug

Comment From: x-lambda

Test Code ```go package main

import ( "fmt" "net/http" "net/http/httptest" "time"

"github.com/gin-contrib/timeout" "github.com/gin-gonic/gin" )

func main() { r := gin.Default() r.Use(timeout.New( timeout.WithTimeout(time.Second), timeout.WithHandler(func(c gin.Context) { c.Next() }), timeout.WithResponse(func(c gin.Context) { c.AbortWithStatus(http.StatusGatewayTimeout) }), )) r.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusFound, "https://www.github.com/") })

w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/redirect", nil) r.ServeHTTP(w, req)

fmt.Print(w.Body.String()) } ```

Result: ``` [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /redirect --> main.main.func3 (4 handlers)

2023/06/01 12:01:27 [Recovery] 2023/06/01 - 12:01:27 panic recovered: GET /redirect HTTP/1.1

invalid http status code: -1 /Users/xxx/go/pkg/mod/github.com/gin-contrib/timeout@v0.0.3/timeout.go:63 (0x1027f6be3) New.func1: panic(p) /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f110f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/recovery.go:102 (0x1027f10f0) CustomRecoveryWithWriter.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f037f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/logger.go:240 (0x1027f035c) LoggerWithConfig.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027ef497) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:620 (0x1027ef16c) (Engine).handleHTTPRequest: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:576 (0x1027eeec7) (*Engine).ServeHTTP: engine.handleHTTPRequest(c) /Users/xxx/Desktop/gorm-playground/main.go:30 (0x1027f79ef) main: r.ServeHTTP(w, req) /usr/local/go/src/runtime/proc.go:250 (0x1025d180f) main: fn() /usr/local/go/src/runtime/asm_arm64.s:1270 (0x1025fe833) goexit: MOVD R0, R0 // NOP

[GIN] 2023/06/01 - 12:01:27 | 500 | 1.914833ms | | GET "/redirect" ```

我看了是其实是github.com/gin-contrib/timeout库的问题,gin使用-1跳过状态码赋值,但是这个库没有处理,参考这个老哥的pr https://github.com/gin-contrib/timeout/pull/37/files

so this is a gin bug

gin

// Redirect returns an HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) {
    c.Render(-1, render.Redirect{
        Code:     code,
        Location: location,
        Request:  c.Request,
    })
}

写死的-1会修改吗?

Comment From: BigeYoung

Test Code ```go package main

import ( "fmt" "net/http" "net/http/httptest" "time"

"github.com/gin-contrib/timeout"
"github.com/gin-gonic/gin"

)

func main() { r := gin.Default() r.Use(timeout.New( timeout.WithTimeout(time.Second), timeout.WithHandler(func(c gin.Context) { c.Next() }), timeout.WithResponse(func(c gin.Context) { c.AbortWithStatus(http.StatusGatewayTimeout) }), )) r.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusFound, "https://www.github.com/") })

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/redirect", nil)
r.ServeHTTP(w, req)

fmt.Print(w.Body.String())

} ```

Result: ``` [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /redirect --> main.main.func3 (4 handlers)

2023/06/01 12:01:27 [Recovery] 2023/06/01 - 12:01:27 panic recovered: GET /redirect HTTP/1.1

invalid http status code: -1 /Users/xxx/go/pkg/mod/github.com/gin-contrib/timeout@v0.0.3/timeout.go:63 (0x1027f6be3) New.func1: panic(p) /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f110f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/recovery.go:102 (0x1027f10f0) CustomRecoveryWithWriter.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027f037f) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/logger.go:240 (0x1027f035c) LoggerWithConfig.func1: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/context.go:174 (0x1027ef497) (Context).Next: c.handlersc.index /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:620 (0x1027ef16c) (Engine).handleHTTPRequest: c.Next() /Users/xxx/go/pkg/mod/github.com/gin-gonic/gin@v1.9.1/gin.go:576 (0x1027eeec7) (*Engine).ServeHTTP: engine.handleHTTPRequest(c) /Users/xxx/Desktop/gorm-playground/main.go:30 (0x1027f79ef) main: r.ServeHTTP(w, req) /usr/local/go/src/runtime/proc.go:250 (0x1025d180f) main: fn() /usr/local/go/src/runtime/asm_arm64.s:1270 (0x1025fe833) goexit: MOVD R0, R0 // NOP

[GIN] 2023/06/01 - 12:01:27 | 500 | 1.914833ms | | GET "/redirect" ```

我看了是其实是github.com/gin-contrib/timeout库的问题,gin使用-1跳过状态码赋值,但是这个库没有处理,参考这个老哥的pr https://github.com/gin-contrib/timeout/pull/37/files

是啊,github.com/gin-contrib/timeout这个库好多issue和pr都没有处理。相比之下github.com/vearne/gin-timeout活跃得多,相同的问题重定向异常,维护者一天就修复了。gin社区是否可以考虑将github.com/vearne/gin-timeout这个库加入到gin-contrib?