Go gRPC进阶教程gRPC转换HTTP

作者:烟花易冷人憔悴 时间:2024-05-21 10:25:57 

前言

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

gRPC转成HTTP

编写和编译proto

1.编写simple.proto

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的路由路径

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所在目录,编译:

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

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

#生成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文件

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

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

使用postman测试

Go gRPC进阶教程gRPC转换HTTP

在动图中可以看到,我们的gRPC服务已经同时支持RPC和HTTP请求了,而且API接口支持bearer token验证和数据验证。为了方便对接,我们把API接口生成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插件,第一篇有介绍,只要保存,就会自动编译,很方便,无需记忆指令。完整配置如下:

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

把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/所在目录下运行指令。

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文件

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中添加如下代码

//注册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文档了。

Go gRPC进阶教程gRPC转换HTTP

还有个问题,我们使用了bearer token进行接口验证的,怎么把bearer token也添加到swagger中呢?
最后我在grpc-gatewayGitHub上的这个Issues找到解决办法。

在swagger中配置bearer token

1.修改simple.proto文件

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).
 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}];
}
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_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.openapiv2_operation) = {
     //   security: { } // Disable security key
     // };
 }
}

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

大功告成!

验证测试

1.添加bearer token

Go gRPC进阶教程gRPC转换HTTP

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

Go gRPC进阶教程gRPC转换HTTP

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

Go gRPC进阶教程gRPC转换HTTP

来源:https://www.cnblogs.com/FireworksEasyCool/p/12782137.html

标签:Go,gRPC,HTTP转换
0
投稿

猜你喜欢

  • Django项目使用ckeditor详解(不使用admin)

    2022-12-15 01:52:52
  • Python爬取网站图片并保存的实现示例

    2023-06-05 18:01:29
  • 不要忽略了颜色的可用性

    2009-03-05 18:19:00
  • 原生JS实现非常好看的计数器

    2024-04-19 09:59:20
  • python随机获取列表中某一元素的方法

    2023-08-23 18:25:13
  • Sql Server之数据类型详解

    2024-01-20 08:08:11
  • python实现音乐播放和下载小程序功能

    2023-07-03 17:59:03
  • pandas or sql计算前后两行数据间的增值方法

    2022-08-08 09:36:30
  • python上下文管理器协议的实现

    2023-08-28 18:32:18
  • Python游戏开发之魔塔小游戏的实现

    2022-08-26 16:14:35
  • JavaScript 判断日期格式是否正确的实现代码

    2011-07-05 12:37:44
  • 微信公众平台实现获取用户OpenID的方法

    2023-11-17 05:55:56
  • mysql查询的控制语句图文详解

    2024-01-27 00:02:38
  • python实现一个简单的ping工具方法

    2022-01-27 17:20:41
  • Python学习之私有函数,私有变量及封装详解

    2022-05-28 13:47:15
  • 网站渗透常用Python小脚本查询同ip网站

    2021-06-12 21:51:28
  • python 字典修改键(key)的几种方法

    2021-01-05 16:58:00
  • 公共Hooks封装报表导出useExportExcel实现详解

    2024-04-28 09:21:36
  • Python二维列表的创建、转换以及访问详解

    2022-08-09 16:03:15
  • python动态加载变量示例分享

    2022-10-23 18:57:50
  • asp之家 网络编程 m.aspxhome.com