grpc-gateway转化grpc与http api规则

1、介绍

gRPC Transcoding 是一种定义了grpc与http rest api转化规则,这种规则适用于 Google APIs, Cloud Endpoints, gRPC Gateway等,目前已被广泛使用。

英文文档见:

https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#google.api.HttpRule

代码可见:

https://github.com/googleapis/googleapis/blob/master/google/api/http.proto

本篇算是对以上文档的翻译与主要观点提炼,再加上个人测试结果。

2、环境准备

2.1 protoc版本

由于我写的是proto3,所以protoc选用了3.11,这个只要选用最新的,差异不大

2.2 protoc-gen-go与protoc-gen-grpc-gateway版本

这两个我踩坑了。。.主要之前安装过protoc-gen-go,它是生成pb.go的工具,后来再安装的protoc-gen-grpc-gateway时不清楚之前protoc-gen-go的版本,所以直接安装最新了,代码跑起来就会报各种方法未实现。研究了一下protoc-gen-grpc-gateway是生成pb.gw.go的工具,由于protoc-gen-go有版本v1和版本v2,且两个差异较大,相应的protoc-gen-grpc-gateway也应该使用对应的版本。

怎么区分protoc-gen-go新旧版本呢?

了解到v1版的protoc-gen-go,在执行protoc-gen-go --version命令后会一直挂起,而v2版本执行完此命令后会立刻返回版本号。因为我的protoc-gen-go是v1版本,在下载github.com/grpc-ecosystem/grpc-gateway后,把tag切到v1后,重新go build 生成protoc-gen-grpc-gateway ,移到本机GOPATH下

最后还遇到个问题仅供参考:undefined: runtime.ServerTransportStream,github.com/grpc-ecosystem/grpc-gateway错误,查询官网把

grpc-gateway版本改成 v1.14.8就可以解决了。

最后推荐https://github.com/jergoo/go-grpc-tutorial这个git项目,适合初学grpc的,我是直接在它项目上改改验证此次结论的。

3、http 映射规则

3.1  测试代码

//request
message msgtype{
    string msgname = 1;
}
message HelloURLPathRequest{
    string name = 1;
    repeated string  msg = 2;
    msgtype  msgfield = 3;
}

//response
message HelloHTTPResponse {
    string message = 1;
}

//grpc func
func (h helloHTTPService) SayHelloURLPath(ctx context.Context, in *pb.HelloURLPathRequest) (*pb.HelloHTTPResponse, error) {
    resp := new(pb.HelloHTTPResponse)
    resp.Message = "SayHelloURLPath: Hello " + in.Name + ". "
    for i, msg := range in.Msg {
        resp.Message += "msg" + fmt.Sprintf("%d: %s. ", i, msg)
    }
    if in.Msgfield != nil {
        resp.Message += "msgfield.msgname: " + in.Msgfield.Msgname
    }
    return resp, nil
}

3.2  url path转化规则

官网说明:url path 可以引用gRPC请求消息中的一个或多个字段。但是这些字段必须是原生基础类型的非数组字段不支持消息类型的字段也不支持数组字段

测试结果:

test1:

rpc SayHelloURLPath(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v1/{name}"
        };
    }
请求: curl -X POST http://localhost:8080/v1/test?msg=a\&msg=b\&msgfield.msgname=c
输出: {"message":"SayHelloURLPath: Hello test. msg0: a. msg1: b. msgfield.msgname: c"}

test2:

rpc SayHelloURLPath2(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v2/{name=messages/*}"
        };
    }
请求: curl -X POST http://localhost:8080/v2/messages/test?msg=a\&msg=b\&msgfield.msgname=c
输出: {"message":"SayHelloURLPath2: Hello messages/test . msg0: a. msg1: b. msgfield.msgname: c"}

请求url只能是v2/messages/*

test3:

rpc SayHelloURLPath3(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v3/{msg}"
        };
    }
请求: curl -X POST http://localhost:8080/v3/a?msg=b\&msgfield.msgname=c
输出: {"message":"SayHelloURLPath3: Hello  . msg0: a. msgfield.msgname: c"}

官网说不能是数组类型,发现数组类型是url path,编译也没报错,但是其实并没有接受到数组的多个值,只接受到一个值,所以还是不建议这么用

test4:

    rpc SayHelloURLPath4(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v4/{msgfield}"
        };
    }

把消息类型做为url path,直接报错了msgfield is a protobuf message type. Protobuf message types cannot be used as path parameters, use a scalar value type (such as string) instead

3.3 url param转化规则

官网说明:如果没有HTTP请求体,请求消息中没有被url path绑定的任何字段都会自动成为HTTP查询参数。

转化规则:

映射成URL查询参数的字段必须是原生基础类型、原生基础数组类型或非数组消息类型。

原生基础数组类型转化格式:...?param=A¶m=B

非数组消息类型转化格式:...?foo.a=A&foo.b=B&foo.c=C

这个规则3.2的测试已经可以覆盖大部分了

在3.2 test1的基础上给请求加上 repeated  msgtype msgfields = 4,如下:

message HelloURLPathRequest{
    string name = 1;
    repeated string  msg = 2;
    msgtype  msgfield = 3;
    repeated  msgtype msgfields = 4;
}

编译没有报错,但是请求时会报{"error":"unexpected repeated field in msgfields.msgname","code":3,"message":"unexpected repeated field in msgfields.msgname"};从而验证必须是非数组消息类型。

 3.4 http body 转化规则

官网说明:

1. body里指明了映射的字段,则请求body就是指明的字段,其余未被url path绑定的变为url params

2. body里未指明映射字段,用*来定义。这时没有被url path绑定的每个字段都应该映射到请求主体。注意这时,因为没有绑定到路径的所有字段都在BODY中。这使得该选项在定义REST api时在实践中很少使用。*的常见用法是在自定义方法中,这些方法根本不使用URL来传输数据。

test5:

rpc SayHelloBody(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v5/{name}"
            body:"msgfield"
        };
    }
请求: curl -X POST http://localhost:8080/v5/test?msg=a\&msg=b -d '{"msgname":"c"}'
输出: {"message":"SayHelloBody: Hello test . msg0: a. msg1: b. msgfield.msgname: c"}

test6:

     rpc SayHelloBody2(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v6/{name}"
            body:"msgfields" //"msg"
        };
    }

报错:--grpc-gateway_out: repeated field not allowed in field path: msgfields in msgfields//测试"msg"字段也是一样

说明body字段是不支持数组类型的字段

test7:

rpc SayHelloBody3(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v7/{name}"
            body:"*"
        };
    }
请求: curl -X POST http://localhost:8080/v7/test -d '{"msgfield":{"msgname":"d"}, "msg":["b", "c"]}'
输出: {"message":"SayHelloBody3: Hello test . msg0: b. msg1: c. msgfield.msgname: d"}

3.5 additional_bindings

官网说明:

通过使用additional_bindings选项,可以为一个RPC定义多个HTTP方法。

test8:

rpc SayHelloadditional(HelloURLPathRequest) returns (HelloHTTPResponse) {
        option (google.api.http) = {
            post: "/v8/{name}"
            additional_bindings {
                post: "/v9/{name}"
              }
        };
    }
请求1: curl -X POST http://localhost:8080/v8/test?msg=a\&msg=b\&msgfield.msgname=c
输出1: {"message":"SayHelloadditional: Hello test . msg0: a. msg1: b. msgfield.msgname: c"}

请求2: curl -X POST http://localhost:8080/v9/test?msg=a\&msg=b\&msgfield.msgname=c
输出2: {"message":"SayHelloadditional: Hello test . msg0: a. msg1: b. msgfield.msgname: c"}

3.6 总结与补充

总结

1.叶子请求字段(请求消息中的递归扩展嵌套消息)分为三类:

    path模板引用的字段。它们通过URL路径传递。

    被HttpRule.body引用的字段。它们通过HTTP请求体传递。

    所有其他字段都通过URL查询参数传递,参数名称是请求消息中的字段path。数组字段可以表示为相同名称下的多个查询参数。

2. 如果HttpRule。正文为“*”,没有URL查询参数,所有字段通过URL路径和HTTP请求正文传递。

3. 如果HttpRule。省略主体,没有HTTP请求主体,所有字段通过URL路径和URL查询参数传递。

 

补充

路径模板语法:

Template = "/" Segments [ Verb ] ;

Segments = Segment { "/" Segment } ;

Segment  = "*" | "**" | LITERAL | Variable;

Variable = "{" FieldPath [ "=" Segments ] "}" ;

FieldPath = IDENT { "." IDENT } ;

Verb     = ":" LITERAL ;

 

语法*匹配单个URL路径Segment。语法**匹配0个或多个URL路径Segment,这些Segment必须是URL路径的最后一部分,但Verb除外。

Variable匹配模板指定的URL路径的一部分。Variable不能包含其他Variable。如果一个Variable匹配单个路径Segment,它的模板可以省略,例如{var}等价于{var=*}。

语法LITERAL匹配URL路径中的文字文本。如果LITERAL包含任何reserved字符,这些字符应该在匹配之前进行百分比编码。

如果一个Variable只包含一个路径Segment,例如“{var}”或“{var=*}”,当这样的Variable在客户端展开为URL路径时,除[-_.~0-9a-zA-Z]以外的所有字符需要进行百分比编码。服务器端执行反向解码。这些变量在发现文档中显示为{var}。

如果一个Variable包含多个路径Segment,如“{var=foo/*}”或“{var=**}”,当在客户端将该变量展开为URL路径时除[-_.~0-9a-zA-Z]以外的所有字符需要进行百分比编码。服务器端执行反向解码,除非“%2F”和“%2F”保持不变。这些变量在发现文档中显示为{+var}。

你可能感兴趣的:(go,protobuf,golang,restful,grpc)