在 grpc-gateway 中手动编译 gRPC 路由规则并实现纯 HTTP 接口
更新:从 v2
版本开始或者自 2020-06-09 以后的版本自带 ServeMux.HandlePath
方法了,不需要再繁琐地使用我下文的方法了。
参考 add mux.HandlePath method (v2)。
平常我们都是像下面这样在 Protocol Buffer 的 .proto
文件中定义 gRPC 函数的对应的 HTTP 路由规则:
1 2 3 4 5 |
|
然后结合 grpc-ecosystem 的 grpc-gateway 插件,可以自动生成 HTTP 转 RPC 的代码。
下面这张官方的示意图很好地解释了其工作方式:
生成的 HTTP 转 gRPC 代码(位于后缀为 .gw.go
的文件中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
其中的路由 pattern_API_ListGroupUsers_0
是一个由 gRPC 中定义的 HTTP 路由规则编译而来的模式。
在 gRPC 中被定义为:
"/v1/groups/{name=*}/users"
编译成模式后:
1
|
|
其中的1
是版本号,当前始终不变;[]int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}
是内部的模式判断操作码(字节码),用于判断 HTTP 的资源路径是否和这个模式匹配,如果匹配,参数的值也会求出来;[]string{"v1", "groups", "name", "users"}
是前面的操作码操作的对象。
func(w http.ResponseWriter, req *http.Request, pathParams map[string]string
是一个回调函数的原型。pathParams
是一个map[string]string
,即是上面的模式中匹配出来的参数,参数类型全部是字符串。
我目前的项目需要用到一些纯 HTTP 实现的接口,它们不方便用 gRPC 实现)。比如:图片上传。因为 gRPC 是基于 message 的,不方便处理 HTTP 的文件上传。
为了不单独再开一个 HTTP 服务器,所以直接复用这个 HTTP 转 gRPC 的 grpc-gateway 提供的 HTTP 路由器:runtime.ServeMux
。
现在还需要一个东西,那就是怎么把路由规则转换成模式。这需要借助 grpc-gateway 提供的httprule
包。
为了讲解,我分成了下面几个步骤:
-
编译路由规则为一个模板
1 2 3 4 5 6 7
compileTemplate := func(rule string) httprule.Template { if compiler, err := httprule.Parse(rule); err != nil { panic(err) } else { return compiler.Compile() } }
它接受一个形如
/v1/groups/{name=*}/users
的参数, 并把它转换成下面这样的一个模板:1 2 3 4 5 6 7 8 9 10 11 12 13 14
type Template struct { // Version is the version number of the format. Version int // OpCodes is a sequence of operations. OpCodes []int // Pool is a constant pool Pool []string // Verb is a VERB part in the template. Verb string // Fields is a list of field paths bound in this template. Fields []string // Original template (example: /v1/a_bit_of_everything) Template string }
这个 Template 里面的
OpCodes
和Pool
正是构成上面的模式需要的几个参数。 -
从模板生成模式
1 2 3 4 5 6 7 8
compilePattern := func(rule string) runtime.Pattern { t := compileTemplate(rule) pattern, err := runtime.NewPattern(1, t.OpCodes, t.Pool, t.Verb) if err != nil { panic(err) } return pattern }
-
添加自己的处理函数
1 2 3 4
handle := func(method string, rule string, handler runtime.HandlerFunc) { pattern := compilePattern(rule) mux.Handle(method, pattern, handler) }
最终合并后的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 |
|
成功运行。
后话:不得不说,Go语言的包做得真是不错。一开始真没有想到可以把编译时的东西直接拿到运行时来用。
注:本文未实现异常处理、授权管理等,可以自己在 handler 链里面添加中间件实现。