前言
我们通常把RPC用作内部通信,而使用Restful Api进行外部通信。为了避免写两套应用,我们使用grpc-gateway把gRPC转成HTTP。服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON形式返回数据。本篇代码以上篇为基础,最终转成的Restful Api支持bearer token验证、数据验证,并添加swagger文档。
gRPC转成HTTP
编写和编译proto
1.编写simple.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
syntax = "proto3" ; package proto; import "github.com/mwitkow/go-proto-validators/validator.proto" ; import "go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto" ; message InnerMessage { // some_integer can only be in range (1, 100). int32 some_integer = 1 [(validator.field) = {int_gt: 0 , int_lt: 100 }]; // some_float can only be in range (0;1). double some_float = 2 [(validator.field) = {float_gte: 0 , float_lte: 1 }]; } message OuterMessage { // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax). string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$" }]; // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage. InnerMessage inner = 2 [(validator.field) = {msg_exists : true }]; } service Simple{ rpc Route (InnerMessage) returns (OuterMessage){ option (google.api.http) ={ post: "/v1/example/route" body: "*" }; } } |
可以看到,proto变化不大,只是添加了API的路由路径
1
2
3
4
|
option (google.api.http) ={ post: "/v1/example/route" body: "*" }; |
2.编译simple.proto
simple.proto文件引用了google/api/annotations.proto,先要把它编译了。我这里是把google/文件夹直接复制到项目中的proto/目录中进行编译。发现annotations.proto引用了google/api/http.proto,那把它也编译了。
进入annotations.proto所在目录,编译:
1
2
|
protoc --go_out=plugins=grpc:./ . /http .proto protoc --go_out=plugins=grpc:./ . /annotations .proto |
进入simple.proto所在目录,编译:
1
2
3
4
|
#生成simple.validator.pb.go和simple.pb.go protoc --govalidators_out=. --go_out=plugins=grpc:./ . /simple .proto #生成simple.pb.gw.go protoc --grpc-gateway_out=logtostderr= true :./ . /simple .proto |
以上完成proto编译,接着修改服务端代码。
服务端代码修改
1.server/文件夹下新建gateway/目录,然后在里面新建gateway.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package gateway import ( "context" "crypto/tls" "io/ioutil" "log" "net/http" "strings" pb "go-grpc-example/10-grpc-gateway/proto" "go-grpc-example/10-grpc-gateway/server/swagger" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" ) // ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP func ProvideHTTP(endpoint string , grpcServer *grpc.Server) *http.Server { ctx := context.Background() //获取证书 creds, err := credentials.NewClientTLSFromFile( "../tls/server.pem" , "go-grpc-example" ) if err != nil { log.Fatalf( "Failed to create TLS credentials %v" , err) } //添加证书 dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)} //新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。 gwmux := runtime.NewServeMux() //将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点 err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts) if err != nil { log.Fatalf( "Register Endpoint err: %v" , err) } //新建mux,它是http的请求复用器 mux := http.NewServeMux() //注册gwmux mux.Handle( "/" , gwmux) log. Println (endpoint + " HTTP.Listing whth TLS and token..." ) return &http.Server{ Addr: endpoint, Handler: grpcHandlerFunc(grpcServer, mux), TLSConfig: getTLSConfig(), } } // grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理 func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { return h2c.NewHandler(http.HandlerFunc( func (w http.ResponseWriter, r *http.Request) { if r.ProtoMajor == 2 && strings.Contains(r.Header.Get( "Content-Type" ), "application/grpc" ) { grpcServer.ServeHTTP(w, r) } else { otherHandler.ServeHTTP(w, r) } }), &http2.Server{}) } // getTLSConfig获取TLS配置 func getTLSConfig() *tls.Config { cert, _ := ioutil.ReadFile( "../tls/server.pem" ) key, _ := ioutil.ReadFile( "../tls/server.key" ) var demoKeyPair *tls.Certificate pair, err := tls.X509KeyPair(cert, key) if err != nil { grpclog.Fatalf( "TLS KeyPair err: %v\n" , err) } demoKeyPair = &pair return &tls.Config{ Certificates: []tls.Certificate{*demoKeyPair}, NextProtos: [] string {http2.NextProtoTLS}, // HTTP2 TLS支持 } } |
它主要作用是把不用的请求重定向到指定的服务处理,从而实现把HTTP请求转到gRPC服务。
2.gRPC支持HTTP
1
2
3
4
5
|
//使用gateway把grpcServer转成httpServer httpServer := gateway.ProvideHTTP(Address, grpcServer) if err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != nil { log.Fatal( "ListenAndServe: " , err) } |
使用postman测试
在动图中可以看到,我们的gRPC服务已经同时支持RPC和HTTP请求了,而且API接口支持bearer token验证和数据验证。为了方便对接,我们把API接口生成swagger文档。
生成swagger文档
simple.swagger.json
1.安装protoc-gen-swagger
1
|
go get -u github.com /grpc-ecosystem/grpc-gateway/protoc-gen-swagger |
2.编译生成simple.swagger.json
到simple.proto文件目录下,编译:
1
|
protoc --swagger_out=logtostderr= true :./ . /simple .proto |
再次提一下,本人在VSCode中使用VSCode-proto3插件,第一篇有介绍,只要保存,就会自动编译,很方便,无需记忆指令。完整配置如下:
编译生成后把需要的文件留下,不需要的删掉。
把swagger-ui转成Go代码,备用
1.下载swagger-ui
下载地址,把dist目录下的所有文件拷贝我们项目的server/swagger/swagger-ui/目录下。
2.把Swagger UI转换为Go代码
安装go-bindata:
1
|
go get -u github.com /jteeuwen/go-bindata/ ... |
回到server/所在目录,运行指令把Swagger UI转成Go代码。
1
|
go-bindata --nocompress -pkg swagger -o swagger /datafile .go swagger /swagger-ui/ ... |
这步有坑,必须要回到main函数所在的目录运行指令,因为生成的Go代码中的_bindata 映射了swagger-ui的路径,程序是根据这些路径来找页面的。如果没有在main函数所在的目录运行指令,则生成的路径不对,会报404,无法找到页面。本项目server/端的main函数在server.go中,所以在server/所在目录下运行指令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var _bindata = map [ string ] func () (*asset, error ){ "swagger/swagger-ui/favicon-16x16.png" : swaggerSwaggerUiFavicon16x16Png, "swagger/swagger-ui/favicon-32x32.png" : swaggerSwaggerUiFavicon32x32Png, "swagger/swagger-ui/index.html" : swaggerSwaggerUiIndexHtml, "swagger/swagger-ui/oauth2-redirect.html" : swaggerSwaggerUiOauth2RedirectHtml, "swagger/swagger-ui/swagger-ui-bundle.js" : swaggerSwaggerUiSwaggerUiBundleJs, "swagger/swagger-ui/swagger-ui-bundle.js.map" : swaggerSwaggerUiSwaggerUiBundleJsMap, "swagger/swagger-ui/swagger-ui-standalone-preset.js" : swaggerSwaggerUiSwaggerUiStandalonePresetJs, "swagger/swagger-ui/swagger-ui-standalone-preset.js.map" : swaggerSwaggerUiSwaggerUiStandalonePresetJsMap, "swagger/swagger-ui/swagger-ui.css" : swaggerSwaggerUiSwaggerUiCss, "swagger/swagger-ui/swagger-ui.css.map" : swaggerSwaggerUiSwaggerUiCssMap, "swagger/swagger-ui/swagger-ui.js" : swaggerSwaggerUiSwaggerUiJs, "swagger/swagger-ui/swagger-ui.js.map" : swaggerSwaggerUiSwaggerUiJsMap, } |
对外提供swagger-ui
1.在swagger/目录下新建swagger.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
26
27
28
29
30
31
|
package swagger import ( "log" "net/http" "path" "strings" assetfs "github.com/elazarl/go-bindata-assetfs" ) //ServeSwaggerFile 把proto文件夹中的swagger.json文件暴露出去 func ServeSwaggerFile(w http.ResponseWriter, r *http.Request) { if !strings.HasSuffix(r.URL.Path, "swagger.json" ) { log.Printf( "Not Found: %s" , r.URL.Path) http.NotFound(w, r) return } p := strings.TrimPrefix(r.URL.Path, "/swagger/" ) // "../proto/"为.swagger.json所在目录 p = path.Join( "../proto/" , p) log.Printf( "Serving swagger-file: %s" , p) http.ServeFile(w, r, p) } //ServeSwaggerUI 对外提供swagger-ui func ServeSwaggerUI(mux *http.ServeMux) { fileServer := http.FileServer(&assetfs.AssetFS{ Asset: Asset, AssetDir: AssetDir, Prefix: "swagger/swagger-ui" , //swagger-ui文件夹所在目录 }) prefix := "/swagger-ui/" mux.Handle(prefix, http.StripPrefix(prefix, fileServer)) } |
2.注册swagger
在gateway.go中添加如下代码
1
2
3
|
//注册swagger mux.HandleFunc( "/swagger/" , swagger.ServeSwaggerFile) swagger.ServeSwaggerUI(mux) |
到这里我们已经完成了swagger文档的添加工作了,由于谷歌浏览器不能使用自己制作的TLS证书,所以我们用火狐浏览器进行测试。
用火狐浏览器打开:https://127.0.0.1:8000/swagger-ui/
在最上面地址栏输入:https://127.0.0.1:8000/swagger/simple.swagger.json
然后就可以看到swagger生成的API文档了。
还有个问题,我们使用了bearer token进行接口验证的,怎么把bearer token也添加到swagger中呢?
最后我在grpc-gatewayGitHub上的这个Issues找到解决办法。
在swagger中配置bearer token
1.修改simple.proto文件
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
syntax = "proto3" ; package proto; import "github.com/mwitkow/go-proto-validators/validator.proto" ; import "go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto" ; import "go-grpc-example/10-grpc-gateway/proto/google/options/annotations.proto" ; message InnerMessage { // some_integer can only be in range ( 1 , 100 ). int 32 some_integer = 1 [(validator.field) = {int_gt: 0 , int_lt: 100 }]; // some_float can only be in range ( 0 ; 1 ). double some_float = 2 [(validator.field) = {float_gte: 0 , float_lte: 1 }]; } message OuterMessage { // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE 2 syntax). string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$" }]; // proto 3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage. InnerMessage inner = 2 [(validator.field) = {msg_exists : true }]; } option (grpc.gateway.protoc_gen_swagger.options.openapiv 2 _swagger) = { security_definitions: { security: { key: "bearer" value: { type: TYPE_API_KEY in: IN_HEADER name: "Authorization" description: "Authentication token, prefixed by Bearer: Bearer <token>" } } } security: { security_requirement: { key: "bearer" } } info: { title: "grpc gateway sample" ; version: "1.0" ; license: { name: "MIT" ; }; } schemes: HTTPS }; service Simple{ rpc Route (InnerMessage) returns (OuterMessage){ option (google.api.http) ={ post: "/v1/example/route" body: "*" }; // //禁用bearer token // option (grpc.gateway.protoc_gen_swagger.options.openapiv 2 _operation) = { // security: { } // Disable security key // }; } } |
2.重新编译生成simple.swagger.json
大功告成!
验证测试
1.添加bearer token
2.调用接口,正确返回数据
3.传递不合规则的数据,返回违反数据验证逻辑错误
总结
本篇介绍了如何使用grpc-gateway让gRPC同时支持HTTP,最终转成的Restful Api支持bearer token验证、数据验证。同时生成swagger文档,方便API接口对接。
教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example
参考文档
http://www.tuohang.net/article/248619.html
https://www.tuohang.net/article/248620.html
以上就是Go gRPC进阶教程gRPC转换HTTP的详细内容,更多关于Go gRPC转换HTTP的资料请关注服务器之家其它相关文章!