在绑定之前显式处理压缩的 json

我想编写一个 api,它将通过 POST 发送 gzip 压缩的 json 数据。虽然下面可以处理正文中的简单 json,但如果 json 被压缩,则不处理。


使用前是否需要明确处理解压c.ShouldBindJSON?


如何重现

package main


import (

    "github.com/gin-gonic/gin"

    "log"

    "net/http"

)


func main() {

    r := gin.Default()


    r.POST("/postgzip", func(c *gin.Context) {

        

        type PostData struct {

            Data string `binding:"required" json:"data"`

        }

        

        var postdata PostData

        if err := c.ShouldBindJSON(&postdata); err != nil {

            log.Println("Error parsing request body", "error", err)

            c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})

            return

        }

        log.Printf("%s", postdata)

        if !c.IsAborted() {

            c.String(200, postdata.Data)

        }

    })

    r.Run()

}

❯ echo '{"data" : "hello"}' | curl -X POST -H "Content-Type: application/json" -d @- localhost:8080/postgzip

hello

期望

$ echo '{"data" : "hello"}' | gzip | curl -v -i -X POST -H "Content-Type: application/json" -H "Content-Encoding: gzip" --data-binary @- localhost:8080/postgzip

hello

实际结果

$ echo '{"data" : "hello"}' | gzip | curl -v -i -X POST -H "Content-Type: application/json" -H "Content-Encoding: gzip" --data-binary @- localhost:8080/postgzip

{"error":"invalid character '\\x1f' looking for beginning of value"}

环境

  • 去版本:go version go1.17.2 darwin/amd64

  • gin 版本(或提交参考):v1.7.4

  • 操作系统:MacOS Monterey


慕斯709654
浏览 162回答 2
2回答

慕容森

我们是否需要在使用 c.ShouldBindJSON 之前显式处理解压缩?当然。GinShouldBindJSON不知道您的有效负载可能会或可能不会被编码。正如方法名称所暗示的那样,它需要 JSON 输入。如果你想编写可重用的代码,你可以实现这个Binding接口。一个非常小的例子:type GzipJSONBinding struct {}func (b *GzipJSONBinding) Name() string {    return "gzipjson"}func (b *GzipJSONBinding) Bind(req *http.Request, dst interface{}) error {    r, err := gzip.NewReader(req.Body)    if err != nil {        return err    }    raw, err := io.ReadAll(r)    if err != nil {        return err    }    return json.Unmarshal(raw, dst)}然后可用于c.ShouldBindWith,它允许使用任意绑定引擎:err := c.ShouldBindWith(&postData, &GzipJSONBinding{})

阿波罗的战车

打开的完整工作示例Content-Encodingcurl 尝试使用它$ echo '{"data" : "hello"}' | gzip | curl -X POST -H "Content-Type: application/json" -H "Content-Encoding: gzip" --data-binary @- localhost:8080/jsonhello$ curl -X POST -H "Content-Type: application/json" --data-raw '{"data" : "hello"}' localhost:8080/jsonhellopackage mainimport (    "bytes"    "compress/gzip"    "encoding/json"    "fmt"    "github.com/gin-gonic/gin"    "github.com/gin-gonic/gin/binding"    "io"    "log"    "net/http")type PostData struct {    Data string `binding:"required" json:"data"`}func main() {    r := gin.Default()    r.POST("/json", func(c *gin.Context) {        var postdata PostData        contentEncodingHeader := c.GetHeader("Content-Encoding")        switch contentEncodingHeader {        case "gzip":            if err := c.ShouldBindBodyWith(&postdata, gzipJSONBinding{}); err != nil {                log.Println("Error parsing GZIP JSON request body", "error", err)                c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})                return            }        case "":            if err := c.ShouldBindJSON(&postdata); err != nil {                log.Println("Error parsing JSON request body", "error", err)                c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})                return            }        default:            log.Println("unsupported Content-Encoding")            c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "unsupported Content-Encoding"})            return        }        log.Printf("%s", postdata)        if !c.IsAborted() {            c.String(200, postdata.Data)        }    })    r.Run()}type gzipJSONBinding struct{}func (gzipJSONBinding) Name() string {    return "gzipjson"}func (gzipJSONBinding) Bind(req *http.Request, obj interface{}) error {    if req == nil || req.Body == nil {        return fmt.Errorf("invalid request")    }    r, err := gzip.NewReader(req.Body)    if err != nil {        return err    }    raw, err := io.ReadAll(r)    if err != nil {        return err    }    return json.Unmarshal(raw, obj)}func (gzipJSONBinding) BindBody(body []byte, obj interface{}) error {    r, err := gzip.NewReader(bytes.NewReader(body))    if err != nil {        return err    }    return decodeJSON(r, obj)}func decodeJSON(r io.Reader, obj interface{}) error {    decoder := json.NewDecoder(r)    if err := decoder.Decode(obj); err != nil {        return err    }    return validate(obj)}func validate(obj interface{}) error {    if binding.Validator == nil {        return nil    }    return binding.Validator.ValidateStruct(obj)}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go