本文介绍 Golang 的 gin 框架接收json数据并解析的2种方法。
某微服务工程,最近测试发现请求超时,由于特殊原因超时较短,如果请求处理耗时超过1秒则认为失败。排查发现,可能是gin接收解析json数据存在耗时,代码使用ctx.ShouldBindJSON
直接解析得到所需结构体,然后通过自实现的FormatJsonStruct
函数格式化并输出到日志。该格式函数如下:
func FormatJsonStruct(str interface{}, format bool) (ret string) {
ret = ""
jsonstr, err := json.Marshal(str)
if err != nil {
return
}
if format {
var out bytes.Buffer
_ = json.Indent(&out, []byte(jsonstr), "", " ")
ret = out.String()
} else {
ret = string(jsonstr)
}
return
}
从上述过程看到,先是调用了ShouldBindJSON
,再调用了Marshal
函数解析成字符串。于是考虑调用ReadAll
读取数据,再用Unmarshal
解析成结构体,直接输出结构体数据。下面模拟2种不同的解析josn方法。
本节结合代码,简单描述模拟程序。详见文附录。
一般地,在gin中,业务处理函数带有*gin.Context
参数,如本文的HandleGinShouldBindJSON
,使用ctx.ShouldBindJSON(&request)
将ctx
中带的数据直接转换成目标结构体。
也可以通过ioutil.ReadAll(ctx.Request.Body)
先读取客户端来的数据,由于约定为json格式数据,所以可以用json.Unmarshal
解析成结构体。
无法哪种方法,其实都很方便,相对而言,前者更便捷。
使用curl模拟请求命令,示例如下:
curl http://127.0.0.1:9000/foo -X POST -H "Content-Type:application/json" -d '{"id":"test_001", "op":"etc", "timestamp":12342134341234, "data":{"name":"foo", "addr":"bar", "code":450481, "age":100}}'
curl http://127.0.0.1:9000/bar -X POST -H "Content-Type:application/json" -d '{"id":"test_001", "op":"etc", "timestamp":12342134341234, "data":{"name":"foo", "addr":"bar", "code":450481, "age":100}}'
服务端输出日志:
=== RUN TestGin
test of gin
run gin
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /foo --> webdemo/test/gin_test.HandleGinShouldBindJSON (1 handlers)
[GIN-debug] POST /bar --> webdemo/test/gin_test.HandleGinUnmarshal (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :9000
ShouldBindJSON: request: #{test_001 etc 12342134341234 {foo bar 450481 100}}
Unmarshal request: #{test_001 etc 12342134341234 {foo bar 450481 100}}
exit status 0xc000013a
就目前测试和修改结果看,本文所述方法并非主因,真正原因待查。
/*
结构体
{
"id": "test_001",
"op": "etc",
"timestamp": 12342134341234,
"data": {
"name": "foo",
"addr": "bar",
"code": 450481,
"age": 100
}
}
curl http://127.0.0.1:9000/foo -X POST -H "Content-Type:application/json" -d '{"id":"test_001", "op":"etc", "timestamp":12342134341234, "data":{"name":"foo", "addr":"bar", "code":450481, "age":100}}'
curl http://127.0.0.1:9000/bar -X POST -H "Content-Type:application/json" -d '{"id":"test_001", "op":"etc", "timestamp":12342134341234, "data":{"name":"foo", "addr":"bar", "code":450481, "age":100}}'
*/
package test
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/gin-gonic/gin"
)
var g_port string = "9000"
type MyRequest_t struct {
Id string `json:"id"`
Op string `json:"op"`
Timestamp int `json:"timestamp"`
Data ReqData_t `json:"data"`
}
type ReqData_t struct {
Name string `json:"name"`
Addr string `json:"addr"`
Code int `json:"code"`
Age int `json:"age"`
}
func routerPost(r *gin.Engine) {
r.POST("/foo", HandleGinShouldBindJSON)
r.POST("/bar", HandleGinUnmarshal)
}
func initGin() {
fmt.Println("run gin")
router := gin.New()
routerPost(router)
router.Run(":" + g_port)
}
func HandleGinShouldBindJSON(ctx *gin.Context) {
var request MyRequest_t
var err error
ctxType := ctx.Request.Header.Get("Content-Type")
if strings.Contains(ctxType, "application/json") { // 纯 json
// 先获取总的json
if err = ctx.ShouldBindJSON(&request); err != nil {
fmt.Printf("ShouldBindJSON failed: %v\n", err)
return
}
fmt.Printf("ShouldBindJSON: request: #%v\n", request)
} else {
fmt.Println("非json")
return
}
}
func HandleGinUnmarshal(ctx *gin.Context) {
var request MyRequest_t
var err error
var reqbuffer []byte
ctxType := ctx.Request.Header.Get("Content-Type")
if strings.Contains(ctxType, "application/json") { // 纯 json
reqbuffer, err = ioutil.ReadAll(ctx.Request.Body)
if err != nil {
fmt.Printf("ReadAll body failed: %v\n", err)
return
}
err = json.Unmarshal(reqbuffer, &request)
if err != nil {
fmt.Printf("Unmarshal to request failed: %v\n", err)
return
}
fmt.Printf("Unmarshal request: #%v\n", request)
} else {
fmt.Println("非json")
return
}
}
func TestGin(t *testing.T) {
fmt.Println("test of gin")
initGin()
}