Home > Back-end >  Explicitly handle gzipped json before binding
Explicitly handle gzipped json before binding

Time:11-07

I want to write an api that will be sent gzipped json data with POST. While the below can deal with simple json in the body, it does not deal if the json is gzipped.

Do we need to explicitly handle the decompression before using c.ShouldBindJSON?

How to reproduce

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

Expectations

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

Actual result

$ 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"}

Environment

  • go version: go version go1.17.2 darwin/amd64
  • gin version (or commit ref): v1.7.4
  • operating system: MacOS Monterey

CodePudding user response:

Do we need to explicitly handle the decompression before using c.ShouldBindJSON ?

Of course. Gin ShouldBindJSON knows nothing of how your payload may or may not be encoded. It expects JSON input, as the method name suggests.

If you wish to write reusable code, you can implement the Binding interface.

A very minimal example:

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)
}

And then usable with c.ShouldBindWith, which allows using an arbitrary binding engine:

err := c.ShouldBindWith(&postData, &GzipJSONBinding{})
  • Related