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
78.96k stars 8.02k forks source link

is it possible to add extra keys to response body in a middleware #2493

Open suxiaohun opened 4 years ago

suxiaohun commented 4 years ago

Hi,

I want add custom keys to all apis, but I do not want to add it for every func.

e.g:

The origin code: context.JSON(200, gin.H{ "message": "ha", })

Then I get response: { "message": "ha" }

I expect: { "key1": 1000, "key2": 2000, "message": "ha" }

I had write a middleware that log the response body to custom logger, but I want to know if there is a way can rewrite the gin response body

I had search some docs but still not figure that out, e.g: #572

Thanks

NicholasBlaskey commented 4 years ago

This isn't exactly what you want but, one option could be to wrap the gin.H call in another function

package main

import "github.com/gin-gonic/gin"

func custH(h gin.H) gin.H {
    h["key1"] = 1000
    h["key2"] = 2000
    return h
}

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, custH(gin.H{"message": "ha"}))
    })
    r.Run()
}
suxiaohun commented 4 years ago

@NicholasBlaskey Thanks for the answer.

I write an util package to wrap the gin context.json func JSON(c *gin.Context, code int, h gin.H) { h["code"] = 1000 requestId := c.Writer.Header().Get("X-Request-ID") h["request_id"] = requestId c.JSON(code, h) } Not expected, hope find a better way later.

athe0i commented 4 years ago

@suxiaohun Hi, i don't think this is possible in any meaningful way. you might want to use Context.Set() in some middleware in the begining of the request and then redefine JSON in a way that it would use context to get your previously set values, think its more flexible/in-control way of doing this. Or you can make a middleware that returns your response and set your response inside action and combine it with previous suggestion, ie something like this:

func setupRouter() *gin.Engine {
    r := gin.Default()

    // Ping test
    r.GET("/ping", func(c *gin.Context) {
        c.Set("resp", gin.H{"message": "pong"})
    }, respMiddleware)

    return r
}

func respMiddleware(c *gin.Context) {
    resp, _ := c.Get("resp")
    c.JSON(http.StatusOK, resp)
    c.Next()
}
dennypenta commented 4 years ago

I propose to add renderable object to gin.Context. It will allow us to write middleware that will solve this task. In my case I can check each response on vulnerability

ringsaturn commented 3 years ago

It's possible if you use a different ResponseWriter that will save all info before actual response.

Take a look at this demo of changing header https://github.com/ringsaturn/ginmiddlewares/blob/master/cmd/example/main.go

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/ringsaturn/ginmiddlewares/xinject"
    "github.com/ringsaturn/ginmiddlewares/xresponsetime"
    "github.com/ringsaturn/ginmiddlewares/xservername"
)

func Ping(c *gin.Context) {
    c.String(200, "pong")
}

func main() {
    router := gin.New()

    router.Use(xinject.Handler) // always add this at begining
    router.Use(xresponsetime.Handler)
    router.Use(xservername.Handler)

    router.GET("/ping", Ping)

    server := http.Server{
        Addr:    "localhost:8999",
        Handler: router,
    }
    server.ListenAndServe()
}

You just need to introducing an middleware to inject a different ResponseWriter which I copy&paste from https://github.com/vearne/gin-timeout/blob/master/writer.go