内容简介:我们通常把1.编写simple.proto可以看到,
前言
我们通常把 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测试
在动图中可以看到,我们的 gRPC 服务已经同时支持 RPC 和 HTTP 请求了,而且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 插件, 第一篇 有介绍,只要保存,就会自动编译,很方便,无需记忆指令。完整配置如下:
// vscode-proto3插件配置
"protoc": {
// protoc.exe所在目录
"path": "C:\\Go\\bin\\protoc.exe",
// 保存时自动编译
"compile_on_save": true,
"options": [
"--go_out=plugins=grpc:.",//在当前目录编译输出.pb.go文件
"--govalidators_out=.",//在当前目录编译输出.validator.pb文件
"--grpc-gateway_out=logtostderr=true:.",//在当前目录编译输出.pb.gw.go文件
"--swagger_out=logtostderr=true:."//在当前目录编译输出.swagger.json文件
]
}
编译生成后把需要的文件留下,不需要的删掉。
把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文档了。
还有个问题,我们使用了bearer token进行接口验证的,怎么把 bearer token 也添加到swagger中呢? 最后我在 grpc-gateway GitHub上的这个 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
2.调用接口,正确返回数据
3.传递不合规则的数据,返回违反数据验证逻辑错误
总结
本篇介绍了如何使用 grpc-gateway 让 gRPC 同时支持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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JavaScript进阶系列-类型转换、隐式类型转换
- Android 多国语言转换 Excel 和 Excel 转换为 string
- [SSL证书转换(一)]关于JKS 转换成 CRT 和 KEY
- c++中几种常见的类型转换。int与string的转换,float与string的转换以及string和long类型之间的相互...
- Protocol Buffer使用转换工具将proto文件转换成Java文件流程及使用
- 开源 | Alita:一套把 React Native 代码转换成微信小程序代码的转换引擎工具
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python for Data Analysis
Wes McKinney / O'Reilly Media / 2012-11-1 / USD 39.99
Finding great data analysts is difficult. Despite the explosive growth of data in industries ranging from manufacturing and retail to high technology, finance, and healthcare, learning and accessing d......一起来看看 《Python for Data Analysis》 这本书的介绍吧!