gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
79.17k stars 8.03k forks source link

How to add a unified response header after the processing function #2406

Open QueryHeaven opened 4 years ago

QueryHeaven commented 4 years ago

I want to add a uniform response header to all the processing functions, but it does not work

As shown in the figure below, I want to add a custom header "X-TIME" after the processing is completed, but it does not work.

package main

import "github.com/gin-gonic/gin"
func main() {
    r:=gin.Default()
    g:=r.Group("/res", func(context *gin.Context) {
        context.Next()

        //I want to add a unified response header here,
        //but it is invalid.
        context.Header("X-TIME","time")
    })
    g.GET("/", func(context *gin.Context) {
        //it is valid.
        context.Header("X-KEY","key")
        context.JSON(200,gin.H{
            "key":"value",
        })
    })
    r.Run(":18080")
}
binbin0325 commented 4 years ago

gin: v1.4.0 golang: 1.14.2 @QueryHeaven I tested it and it seemed to work well.

 func SetupRouter() *gin.Engine {
    router := gin.Default()
    v1 := router.Group("/v1", func(c *gin.Context) {
        c.Next()
        c.Header("X-TIME", "time")
    })
    {
        v1.Any("/log", services.Get)
    }
    return router
 }

image

QueryHeaven commented 4 years ago

go 1.12 gin v1.6.3 @sanxun0325 I don't know how it is written in your services.Get, I tested again and found the following: gin

joaohaas commented 1 year ago

Kind of necroing, but since this is still open, the reason headers don't get written is due to the way Go internals handles response buffering. As soon as a status header (aka status code) is written, the response starts getting written to a buffer, and extra headers cannot be written. In most cases, you can just add the header before the c.Next() call and it will work. If you need to write the header after the status code is written, you'll need to hijack the writer and override the WriteHeader function:

type afterMiddlewareWriter struct {
    gin.ResponseWriter
}

func (w *afterMiddlewareWriter) WriteHeader(statusCode int) {
    w.Header().Add("X-TIME", "time")
    w.ResponseWriter.WriteHeader(statusCode)
}

func AfterMiddleware(c *gin.Context) {
    c.Writer = &afterMiddlewareWriter{c.Writer}
    c.Next()
}

func main() {
    r := gin.Default()
    r.Use(AfterMiddleware)
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "key": "value",
        })
    })
}

For a real X-Time implementation, you can store the initial time on your struct and calculate how many seconds have passed in the hijacked WriteHeader.

coanor commented 1 year ago

For my case, it's simple to add customize header in response:

router := gin.New()
router.Use(func(c *gin.Context) {
  c.Header("X-Your-Header-Name", "your-header-value")
})

// some other middleware...

// add router
router.GET("/some/api", apiHandler)
... // more
maxant commented 1 month ago

here is a more complete version of @joaohaas 's suggestion:

type timingMiddlewareWriter struct {
    gin.ResponseWriter
    start time.Time
}

func (w *timingMiddlewareWriter) WriteHeader(statusCode int) {
    elapsed := time.Since(w.start)
    log.Debug().Msg("timer ended after writing statusCode: " + elapsed.String())
    w.Header().Add("x-time", elapsed.String())
    w.ResponseWriter.WriteHeader(statusCode)
}

func timingMiddleware(c *gin.Context) {
    log.Debug().Msg("timer starting...")

    c.Writer = &timingMiddlewareWriter{ c.Writer, time.Now()}
    c.Next()
    log.Debug().Msg("timer ended after next()")
}