在绑定之前显式处理 gzipped json
Explicitly handle gzipped json before binding
我想写一个 api 将用 POST 发送 gzipped 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 版本:
go version go1.17.2 darwin/amd64
- gin 版本(或提交参考):
v1.7.4
- 操作系统:MacOS Monterey
Do we need to explicitly handle the decompression before using c.ShouldBindJSON ?
当然可以。 Gin ShouldBindJSON
不知道您的有效负载如何编码或不编码。 It expects JSON input,顾名思义。
如果您希望编写可重用的代码,您可以实现 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-Encoding
的完整示例
curl 尝试使用它
$ echo '{"data" : "hello"}' | gzip | curl -X POST -H "Content-Type: application/json" -H "Content-Encoding: gzip" --data-binary @- localhost:8080/json
hello
$ curl -X POST -H "Content-Type: application/json" --data-raw '{"data" : "hello"}' localhost:8080/json
hello
package main
import (
"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)
}
我想写一个 api 将用 POST 发送 gzipped 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 版本:
go version go1.17.2 darwin/amd64
- gin 版本(或提交参考):
v1.7.4
- 操作系统:MacOS Monterey
Do we need to explicitly handle the decompression before using c.ShouldBindJSON ?
当然可以。 Gin ShouldBindJSON
不知道您的有效负载如何编码或不编码。 It expects JSON input,顾名思义。
如果您希望编写可重用的代码,您可以实现 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-Encoding
的完整示例
curl 尝试使用它
$ echo '{"data" : "hello"}' | gzip | curl -X POST -H "Content-Type: application/json" -H "Content-Encoding: gzip" --data-binary @- localhost:8080/json
hello
$ curl -X POST -H "Content-Type: application/json" --data-raw '{"data" : "hello"}' localhost:8080/json
hello
package main
import (
"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)
}