zeromicro / go-zero

A cloud-native Go microservices framework with cli tool for productivity.
https://go-zero.dev
MIT License
29.41k stars 3.97k forks source link

go-zero/gateway don't support form-data request params in v1.4.0 #4326

Open ren544735689 opened 3 months ago

ren544735689 commented 3 months ago

(因为内容比较多就中文写了) 我所使用的gozero框架是v1.4.0 我想问下gozero/gateway目录下的http网关,为什么不接受form-data类型的传参?

我写了一个demo程序,其中rpc的proto定义了:

message PingReq {
  string ping = 1;
}

message PongRsp {
  string pong = 1;
}

service Demo {
  rpc Ping(PingReq) returns(PongRsp);
}

我使用postman对这个rpc服务的http网关进行调试:

  1. 在post方法中使用rawbody形式传json字符串是可以的 image
  2. 但是使用form-data会报错"400 EOF" image

我可以保证在两次测试中,我仅修改了请求方的传参方式,没有修改任何代码。

经过自己对源码的研究,在go-zero/gateway/internal/requestparser.go中,找到了报错的原因:

// NewRequestParser creates a new request parser from the given http.Request and resolver.
func NewRequestParser(r *http.Request, resolver jsonpb.AnyResolver) (grpcurl.RequestParser, error) {
    vars := pathvar.Vars(r)
    params, err := httpx.GetFormValues(r)
    if err != nil {
        return nil, err
    }

    for k, v := range vars {
        params[k] = v
    }

    body, ok := getBody(r)
    if !ok {
        return buildJsonRequestParser(params, resolver)
    }

    if len(params) == 0 {
        return grpcurl.NewJSONRequestParser(body, resolver), nil
    }

    m := make(map[string]interface{})
    if err := json.NewDecoder(body).Decode(&m); err != nil {
        return nil, err
    }

    for k, v := range params {
        m[k] = v
    }

    return buildJsonRequestParser(m, resolver)
}

上述代码我认为先进行了GetFormValues()将formdata的数据存入了params里,同时我也确认此时params已经拿到了我的传参数据,但是在接下来的逻辑中,会运行json.NewDecoder(body).Decode(&m),这一行中的Decode会报错。

经过我自己的测试,如果删去这行代码,并直接将params传参给buildJsonRequestParser(m, resolver),就可以正常解析form-data传参的参数,并且自己已经写demo确认过。

想问下作者这里是不是有bug,还是说有什么其他的考虑呢?迫切希望拨冗指点迷津

kevwan commented 3 months ago

Would you please give a runnable demo to reproduce the problem? Thanks!

ren544735689 commented 3 months ago

TestDemo.zip

这是一个可以直接运行的程序,我把所需要的vendor依赖直接加在了里面。同时我也编译出了可以直接运行的二进制。

如果想复现上述问题,可以直接运行二进制,他会占用18888和18889端口。 然后只需要用postman进行post方式调用,即可复现问题:

curl --location --request POST 'http://0.0.0.0:18889/Ping' --form 'ping="hello"'

image