前言

我们通常把RPC用作内部通信,而使用Restful Api进行外部通信。为了避免写两套应用,我们使用grpc-gatewaygRPC转成HTTP。服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON形式返回数据。本篇代码以上篇为基础,最终转成的Restful Api支持bearer token验证、数据验证,并添加swagger文档。

gRPC转成HTTP

编写和编译proto

1.编写simple.proto

  1. syntax = "proto3";
  2. package proto;
  3. import "github.com/mwitkow/go-proto-validators/validator.proto";
  4. import "go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto";
  5. message InnerMessage {
  6. // some_integer can only be in range (1, 100).
  7. int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
  8. // some_float can only be in range (0;1).
  9. double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
  10. }
  11. message OuterMessage {
  12. // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
  13. string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
  14. // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
  15. InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
  16. }
  17. service Simple{
  18. rpc Route (InnerMessage) returns (OuterMessage){
  19. option (google.api.http) ={
  20. post:"/v1/example/route"
  21. body:"*"
  22. };
  23. }
  24. }

可以看到,proto变化不大,只是添加了API的路由路径

  1. option (google.api.http) ={
  2. post:"/v1/example/route"
  3. body:"*"
  4. };

2.编译simple.proto

simple.proto文件引用了google/api/annotations.proto来源),先要把它编译了。我这里是把google/文件夹直接复制到项目中的proto/目录中进行编译。发现annotations.proto引用了google/api/http.proto,那把它也编译了。

进入annotations.proto所在目录,编译:

  1. protoc --go_out=plugins=grpc:./ ./http.proto
  2. protoc --go_out=plugins=grpc:./ ./annotations.proto

进入simple.proto所在目录,编译:

  1. #生成simple.validator.pb.go和simple.pb.go
  2. protoc --govalidators_out=. --go_out=plugins=grpc:./ ./simple.proto
  3. #生成simple.pb.gw.go
  4. protoc --grpc-gateway_out=logtostderr=true:./ ./simple.proto

以上完成proto编译,接着修改服务端代码。

服务端代码修改

1.server/文件夹下新建gateway/目录,然后在里面新建gateway.go文件

  1. package gateway
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "strings"
  9. pb "go-grpc-example/10-grpc-gateway/proto"
  10. "go-grpc-example/10-grpc-gateway/server/swagger"
  11. "github.com/grpc-ecosystem/grpc-gateway/runtime"
  12. "golang.org/x/net/http2"
  13. "golang.org/x/net/http2/h2c"
  14. "google.golang.org/grpc"
  15. "google.golang.org/grpc/credentials"
  16. "google.golang.org/grpc/grpclog"
  17. )
  18. // ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP
  19. func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server {
  20. ctx := context.Background()
  21. //获取证书
  22. creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
  23. if err != nil {
  24. log.Fatalf("Failed to create TLS credentials %v", err)
  25. }
  26. //添加证书
  27. dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
  28. //新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。
  29. gwmux := runtime.NewServeMux()
  30. //将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点
  31. err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
  32. if err != nil {
  33. log.Fatalf("Register Endpoint err: %v", err)
  34. }
  35. //新建mux,它是http的请求复用器
  36. mux := http.NewServeMux()
  37. //注册gwmux
  38. mux.Handle("/", gwmux)
  39. log.Println(endpoint + " HTTP.Listing whth TLS and token...")
  40. return &http.Server{
  41. Addr: endpoint,
  42. Handler: grpcHandlerFunc(grpcServer, mux),
  43. TLSConfig: getTLSConfig(),
  44. }
  45. }
  46. // grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理
  47. func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
  48. return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  49. if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
  50. grpcServer.ServeHTTP(w, r)
  51. } else {
  52. otherHandler.ServeHTTP(w, r)
  53. }
  54. }), &http2.Server{})
  55. }
  56. // getTLSConfig获取TLS配置
  57. func getTLSConfig() *tls.Config {
  58. cert, _ := ioutil.ReadFile("../tls/server.pem")
  59. key, _ := ioutil.ReadFile("../tls/server.key")
  60. var demoKeyPair *tls.Certificate
  61. pair, err := tls.X509KeyPair(cert, key)
  62. if err != nil {
  63. grpclog.Fatalf("TLS KeyPair err: %v\n", err)
  64. }
  65. demoKeyPair = &pair
  66. return &tls.Config{
  67. Certificates: []tls.Certificate{*demoKeyPair},
  68. NextProtos: []string{http2.NextProtoTLS}, // HTTP2 TLS支持
  69. }
  70. }

它主要作用是把不用的请求重定向到指定的服务处理,从而实现把HTTP请求转到gRPC服务。

2.gRPC支持HTTP

  1. //使用gateway把grpcServer转成httpServer
  2. httpServer := gateway.ProvideHTTP(Address, grpcServer)
  3. if err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != nil {
  4. log.Fatal("ListenAndServe: ", err)
  5. }

使用postman测试

在动图中可以看到,我们的gRPC服务已经同时支持RPCHTTP请求了,而且API接口支持bearer token验证和数据验证。为了方便对接,我们把API接口生成swagger文档。

生成swagger文档

生成swagger文档-simple.swagger.json

1.安装protoc-gen-swagger

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

2.编译生成simple.swagger.json

到simple.proto文件目录下,编译:

protoc --swagger_out=logtostderr=true:./ ./simple.proto

再次提一下,本人在VSCode中使用VSCode-proto3插件,第一篇有介绍,只要保存,就会自动编译,很方便,无需记忆指令。完整配置如下:

  1. // vscode-proto3插件配置
  2. "protoc": {
  3. // protoc.exe所在目录
  4. "path": "C:\\Go\\bin\\protoc.exe",
  5. // 保存时自动编译
  6. "compile_on_save": true,
  7. "options": [
  8. "--go_out=plugins=grpc:.",//在当前目录编译输出.pb.go文件
  9. "--govalidators_out=.",//在当前目录编译输出.validator.pb文件
  10. "--grpc-gateway_out=logtostderr=true:.",//在当前目录编译输出.pb.gw.go文件
  11. "--swagger_out=logtostderr=true:."//在当前目录编译输出.swagger.json文件
  12. ]
  13. }

编译生成后把需要的文件留下,不需要的删掉。

把swagger-ui转成Go代码,备用

1.下载swagger-ui

下载地址,把dist目录下的所有文件拷贝我们项目的server/swagger/swagger-ui/目录下。

2.把Swagger UI转换为Go代码

安装go-bindata

go get -u github.com/jteeuwen/go-bindata/...

回到server/所在目录,运行指令把Swagger UI转成Go代码。

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. var _bindata = map[string]func() (*asset, error){
  2. "swagger/swagger-ui/favicon-16x16.png": swaggerSwaggerUiFavicon16x16Png,
  3. "swagger/swagger-ui/favicon-32x32.png": swaggerSwaggerUiFavicon32x32Png,
  4. "swagger/swagger-ui/index.html": swaggerSwaggerUiIndexHtml,
  5. "swagger/swagger-ui/oauth2-redirect.html": swaggerSwaggerUiOauth2RedirectHtml,
  6. "swagger/swagger-ui/swagger-ui-bundle.js": swaggerSwaggerUiSwaggerUiBundleJs,
  7. "swagger/swagger-ui/swagger-ui-bundle.js.map": swaggerSwaggerUiSwaggerUiBundleJsMap,
  8. "swagger/swagger-ui/swagger-ui-standalone-preset.js": swaggerSwaggerUiSwaggerUiStandalonePresetJs,
  9. "swagger/swagger-ui/swagger-ui-standalone-preset.js.map": swaggerSwaggerUiSwaggerUiStandalonePresetJsMap,
  10. "swagger/swagger-ui/swagger-ui.css": swaggerSwaggerUiSwaggerUiCss,
  11. "swagger/swagger-ui/swagger-ui.css.map": swaggerSwaggerUiSwaggerUiCssMap,
  12. "swagger/swagger-ui/swagger-ui.js": swaggerSwaggerUiSwaggerUiJs,
  13. "swagger/swagger-ui/swagger-ui.js.map": swaggerSwaggerUiSwaggerUiJsMap,
  14. }

对外提供swagger-ui

1.在swagger/目录下新建swagger.go文件

  1. package swagger
  2. import (
  3. "log"
  4. "net/http"
  5. "path"
  6. "strings"
  7. assetfs "github.com/elazarl/go-bindata-assetfs"
  8. )
  9. //ServeSwaggerFile 把proto文件夹中的swagger.json文件暴露出去
  10. func ServeSwaggerFile(w http.ResponseWriter, r *http.Request) {
  11. if !strings.HasSuffix(r.URL.Path, "swagger.json") {
  12. log.Printf("Not Found: %s", r.URL.Path)
  13. http.NotFound(w, r)
  14. return
  15. }
  16. p := strings.TrimPrefix(r.URL.Path, "/swagger/")
  17. // "../proto/"为.swagger.json所在目录
  18. p = path.Join("../proto/", p)
  19. log.Printf("Serving swagger-file: %s", p)
  20. http.ServeFile(w, r, p)
  21. }
  22. //ServeSwaggerUI 对外提供swagger-ui
  23. func ServeSwaggerUI(mux *http.ServeMux) {
  24. fileServer := http.FileServer(&assetfs.AssetFS{
  25. Asset: Asset,
  26. AssetDir: AssetDir,
  27. Prefix: "swagger/swagger-ui", //swagger-ui文件夹所在目录
  28. })
  29. prefix := "/swagger-ui/"
  30. mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
  31. }

2.注册swagger

gateway.go中添加如下代码

  1. //注册swagger
  2. mux.HandleFunc("/swagger/", swagger.ServeSwaggerFile)
  3. 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. syntax = "proto3";
  2. package proto;
  3. import "github.com/mwitkow/go-proto-validators/validator.proto";
  4. import "go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto";
  5. import "go-grpc-example/10-grpc-gateway/proto/google/options/annotations.proto";
  6. message InnerMessage {
  7. // some_integer can only be in range (1, 100).
  8. int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
  9. // some_float can only be in range (0;1).
  10. double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
  11. }
  12. message OuterMessage {
  13. // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
  14. string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
  15. // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
  16. InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
  17. }
  18. option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
  19. security_definitions: {
  20. security: {
  21. key: "bearer"
  22. value: {
  23. type: TYPE_API_KEY
  24. in: IN_HEADER
  25. name: "Authorization"
  26. description: "Authentication token, prefixed by Bearer: Bearer <token>"
  27. }
  28. }
  29. }
  30. security: {
  31. security_requirement: {
  32. key: "bearer"
  33. }
  34. }
  35. info: {
  36. title: "grpc gateway sample";
  37. version: "1.0";
  38. license: {
  39. name: "MIT";
  40. };
  41. }
  42. schemes: HTTPS
  43. };
  44. service Simple{
  45. rpc Route (InnerMessage) returns (OuterMessage){
  46. option (google.api.http) ={
  47. post:"/v1/example/route"
  48. body:"*"
  49. };
  50. // //禁用bearer token
  51. // option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
  52. // security: { } // Disable security key
  53. // };
  54. }
  55. }

2.重新编译生成simple.swagger.json

大功告成!

验证测试

1.添加bearer token

2.调用接口,正确返回数据

3.传递不合规则的数据,返回违反数据验证逻辑错误

总结

本篇介绍了如何使用grpc-gatewaygRPC同时支持HTTP,最终转成的Restful Api支持bearer token验证、数据验证。同时生成swagger文档,方便API接口对接。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

参考文档:

https://eddycjy.com/tags/grpc-gateway/

https://segmentfault.com/a/1190000008106582

Go gRPC进阶-gRPC转换HTTP(十)的更多相关文章

  1. Go gRPC进阶-TLS认证+自定义方法认证(七)

    前言 前面篇章的gRPC都是明文传输的,容易被篡改数据.本章将介绍如何为gRPC添加安全机制,包括TLS证书认证和Token认证. TLS证书认证 什么是TLS TLS(Transport Layer ...

  2. Go gRPC进阶-go-grpc-middleware使用(八)

    前言 上篇介绍了gRPC中TLS认证和自定义方法认证,最后还简单介绍了gRPC拦截器的使用.gRPC自身只能设置一个拦截器,所有逻辑都写一起会比较乱.本篇简单介绍go-grpc-middleware的 ...

  3. JVM菜鸟进阶高手之路十四:分析篇

    转载请注明原创出处,谢谢! 题目回顾 JVM菜鸟进阶高手之路十三,问题现象就是相同的代码,jvm参数不一样,表现的现象不一样. private static final int _1MB = 1024 ...

  4. 带入gRPC:gRPC Streaming, Client and Server

    带入gRPC:gRPC Streaming, Client and Server 原文地址:带入gRPC:gRPC Streaming, Client and Server 前言 本章节将介绍 gRP ...

  5. 带入gRPC:gRPC Deadlines

    带入gRPC:gRPC Deadlines 原文地址:带入gRPC:gRPC Deadlines项目地址:https://github.com/EDDYCJY/go... 前言 在前面的章节中,已经介 ...

  6. gRPC Motivation and Design Principles | gRPC https://grpc.io/blog/principles/

    gRPC Motivation and Design Principles | gRPC https://grpc.io/blog/principles/

  7. Go gRPC进阶-超时设置(六)

    前言 gRPC默认的请求的超时时间是很长的,当你没有设置请求超时时间时,所有在运行的请求都占用大量资源且可能运行很长的时间,导致服务资源损耗过高,使得后来的请求响应过慢,甚至会引起整个进程崩溃. 为了 ...

  8. Go gRPC进阶-proto数据验证(九)

    前言 上篇介绍了go-grpc-middleware的grpc_zap.grpc_auth和grpc_recovery使用,本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行 ...

  9. 【小梅哥FPGA进阶教程】第十四章 TFT屏显示图片

    十四.TFT屏显示图片 本文由杭电网友曾凯峰贡献,特此感谢 学习了小梅哥的TFT显示屏驱动设计后,想着在此基础上通过TFT屏显示一张图片,有了这个想法就开始动工了.首先想到是利用FPGA内部ROM存储 ...

随机推荐

  1. spring bean的装载过程简略赏析

    spring一个bean的容器,它从这个最基本的功能进而扩展出AOP,transaction,cache,schedule,data等等,将业务与框架代码解耦,让我们可以将大部分精力投入到业务代码中, ...

  2. [codevs2370]小机房的树<LCA>

    题目链接:http://codevs.cn/problem/2370/ 这题我还是做了比较久了,因为有人告诉我这是用tarjan离线做 好吧算我是蒟蒻,真心不懂tarjan怎么做,最后还是用倍增做的 ...

  3. CentOS7部署指南

    1.rpm包安装---下载安装文件 wget https://pkg.jenkins.io/redhat/jenkins-2.156-1.1.noarch.rpm rpm -ivh jenkins-2 ...

  4. A - 你能数的清吗 51Nod - 1770(找规律)

    A - 你能数的清吗 51Nod - 1770(找规律) 演演是个厉害的数学家,他最近又迷上了数字谜.... 他很好奇 xxx...xxx(n个x)*y 的答案中 有多少个z,x,y,z均为位数只有一 ...

  5. 【Jenkins】使用 Jenkins REST API 配合清华大学镜像站更新 Jenkins 插件

    自从去年用上了 Jenkins 进行 CI/CD 之后,工作效率高了不少,摸鱼的时间更多了.不过 Jenkins 好是好,但在功夫网的影响下,插件就是经常更新不成功的,就像下面这样子: 查了不少资料, ...

  6. 【tensorflow2.0】自动微分机制

    神经网络通常依赖反向传播求梯度来更新网络参数,求梯度过程通常是一件非常复杂而容易出错的事情. 而深度学习框架可以帮助我们自动地完成这种求梯度运算. Tensorflow一般使用梯度磁带tf.Gradi ...

  7. DHCP完整过程详解及Wireshark抓包分析

    DHCP,Dynamic Host Configuration Protocol,动态主机配置协议,简单来说就是主机获取IP地址的过程,属于应用层协议. DHCP采用UDP的68(客户端)和67(服务 ...

  8. Nginx知多少系列之(二)安装

    目录 1.前言 2.安装 3.配置文件详解 4.Linux下托管.NET Core项目 5.Linux下.NET Core项目负载均衡 6.Linux下.NET Core项目Nginx+Keepali ...

  9. ACL,NAT的使用

     项目练习 练习一: 练习目的:通过配置路由器的dhcp功能使pc自动获取ip地址. Router>enable Router#configure terminal Router(config) ...

  10. ESLint如何配置

    1.简介 通过用 ESLint 来检查一些规则,我们可以: 统一代码风格规则,如:代码缩进用几个空格:是否用驼峰命名法来命名变量和函数名等. 减少错误, 如:相等比较必须用 === ,变量在使用前必须 ...