英文:
Explicitly handle gzipped json before binding
问题
我想编写一个API,它将使用POST方法发送gzip压缩的JSON数据。虽然下面的代码可以处理请求体中的简单JSON数据,但无法处理gzip压缩的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
期望结果
<!-- 你对于 'curl' 命令的期望结果,例如 -->
$ 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
英文:
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
<!-- Your expectation result of 'curl' command, like -->
$ 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
<!-- Actual result showing the problem -->
$ 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
答案1
得分: 1
我们在使用c.ShouldBindJSON
之前需要明确处理解压缩吗?
当然需要。Gin的ShouldBindJSON
对于负载的编码方式一无所知。它期望的输入是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{})
英文:
> 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{})
答案2
得分: 0
完全可用的示例,开启 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)
}
英文:
Fully working example that switches on Content-Encoding
curl to try to use it
$ 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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论