Go?gRPC進階教程gRPC轉換HTTP
前言
我們通常把RPC用作內部通信,而使用Restful Api進行外部通信。為了避免寫兩套應用,我們使用grpc-gateway把gRPC轉成HTTP。服務接收到HTTP請求后,grpc-gateway把它轉成gRPC進行處理,然后以JSON形式返回數(shù)據(jù)。本篇代碼以上篇為基礎,最終轉成的Restful Api支持bearer token驗證、數(shù)據(jù)驗證,并添加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/目錄中進行編譯。發(fā)現(xiàn)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轉發(fā)請求到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 根據(jù)不同的請求重定向到指定的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支持
}
}
它主要作用是把不用的請求重定向到指定的服務處理,從而實現(xiàn)把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服務已經(jīng)同時支持RPC和HTTP請求了,而且API接口支持bearer token驗證和數(shù)據(jù)驗證。為了方便對接,我們把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函數(shù)所在的目錄運行指令,因為生成的Go代碼中的_bindata 映射了swagger-ui的路徑,程序是根據(jù)這些路徑來找頁面的。如果沒有在main函數(shù)所在的目錄運行指令,則生成的路徑不對,會報404,無法找到頁面。本項目server/端的main函數(shù)在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)到這里我們已經(jīng)完成了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文件
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.調用接口,正確返回數(shù)據(jù)

3.傳遞不合規(guī)則的數(shù)據(jù),返回違反數(shù)據(jù)驗證邏輯錯誤

總結
本篇介紹了如何使用grpc-gateway讓gRPC同時支持HTTP,最終轉成的Restful Api支持bearer token驗證、數(shù)據(jù)驗證。同時生成swagger文檔,方便API接口對接。
教程源碼地址:https://github.com/Bingjian-Zhu/go-grpc-example
參考文檔
http://www.dbjr.com.cn/article/251828.htm
http://www.dbjr.com.cn/article/251837.htm
以上就是Go gRPC進階教程gRPC轉換HTTP的詳細內容,更多關于Go gRPC轉換HTTP的資料請關注腳本之家其它相關文章!
相關文章
go?zero微服務實戰(zhàn)處理每秒上萬次的下單請求
這篇文章主要為大家介紹了go?zero微服務實戰(zhàn)處理每秒上萬次的下單請求示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07

