Go?gRPC進(jìn)階教程gRPC轉(zhuǎn)換HTTP
前言
我們通常把RPC用作內(nèi)部通信,而使用Restful Api進(jìn)行外部通信。為了避免寫(xiě)兩套應(yīng)用,我們使用grpc-gateway把gRPC轉(zhuǎn)成HTTP。服務(wù)接收到HTTP請(qǐng)求后,grpc-gateway把它轉(zhuǎn)成gRPC進(jìn)行處理,然后以JSON形式返回?cái)?shù)據(jù)。本篇代碼以上篇為基礎(chǔ),最終轉(zhuǎn)成的Restful Api支持bearer token驗(yàn)證、數(shù)據(jù)驗(yàn)證,并添加swagger文檔。
gRPC轉(zhuǎn)成HTTP
編寫(xiě)和編譯proto
1.編寫(xiě)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/文件夾直接復(fù)制到項(xiàng)目中的proto/目錄中進(jìn)行編譯。發(fā)現(xiàn)annotations.proto引用了google/api/http.proto,那把它也編譯了。
進(jìn)入annotations.proto所在目錄,編譯:
protoc --go_out=plugins=grpc:./ ./http.proto protoc --go_out=plugins=grpc:./ ./annotations.proto
進(jìn)入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編譯,接著修改服務(wù)端代碼。
服務(wù)端代碼修改
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服務(wù)轉(zhuǎn)成HTTP服務(wù),讓gRPC同時(shí)支持HTTP func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server { ctx := context.Background() //獲取證書(shū) creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example") if err != nil { log.Fatalf("Failed to create TLS credentials %v", err) } //添加證書(shū) dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)} //新建gwmux,它是grpc-gateway的請(qǐng)求復(fù)用器。它將http請(qǐng)求與模式匹配,并調(diào)用相應(yīng)的處理程序。 gwmux := runtime.NewServeMux() //將服務(wù)的http處理程序注冊(cè)到gwmux。處理程序通過(guò)endpoint轉(zhuǎn)發(fā)請(qǐng)求到grpc端點(diǎn) err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts) if err != nil { log.Fatalf("Register Endpoint err: %v", err) } //新建mux,它是http的請(qǐng)求復(fù)用器 mux := http.NewServeMux() //注冊(cè)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ù)不同的請(qǐng)求重定向到指定的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支持 } }
它主要作用是把不用的請(qǐng)求重定向到指定的服務(wù)處理,從而實(shí)現(xiàn)把HTTP請(qǐng)求轉(zhuǎn)到gRPC服務(wù)。
2.gRPC支持HTTP
//使用gateway把grpcServer轉(zhuǎn)成httpServer httpServer := gateway.ProvideHTTP(Address, grpcServer) if err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != nil { log.Fatal("ListenAndServe: ", err) }
使用postman測(cè)試
在動(dòng)圖中可以看到,我們的gRPC服務(wù)已經(jīng)同時(shí)支持RPC和HTTP請(qǐng)求了,而且API接口支持bearer token驗(yàn)證和數(shù)據(jù)驗(yàn)證。為了方便對(duì)接,我們把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插件,第一篇有介紹,只要保存,就會(huì)自動(dòng)編譯,很方便,無(wú)需記憶指令。完整配置如下:
編譯生成后把需要的文件留下,不需要的刪掉。
把swagger-ui轉(zhuǎn)成Go代碼,備用
1.下載swagger-ui
下載地址,把dist目錄下的所有文件拷貝我們項(xiàng)目的server/swagger/swagger-ui/目錄下。
2.把Swagger UI轉(zhuǎn)換為Go代碼
安裝go-bindata:
go get -u github.com/jteeuwen/go-bindata/...
回到server/所在目錄,運(yùn)行指令把Swagger UI轉(zhuǎn)成Go代碼。
go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...
這步有坑,必須要回到main函數(shù)所在的目錄運(yùn)行指令,因?yàn)樯傻腉o代碼中的_bindata 映射了swagger-ui的路徑,程序是根據(jù)這些路徑來(lái)找頁(yè)面的。如果沒(méi)有在main函數(shù)所在的目錄運(yùn)行指令,則生成的路徑不對(duì),會(huì)報(bào)404,無(wú)法找到頁(yè)面。本項(xiàng)目server/端的main函數(shù)在server.go中,所以在server/所在目錄下運(yùn)行指令。
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, }
對(duì)外提供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 對(duì)外提供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.注冊(cè)swagger
在gateway.go中添加如下代碼
//注冊(cè)swagger mux.HandleFunc("/swagger/", swagger.ServeSwaggerFile) swagger.ServeSwaggerUI(mux)
到這里我們已經(jīng)完成了swagger文檔的添加工作了,由于谷歌瀏覽器不能使用自己制作的TLS證書(shū),所以我們用火狐瀏覽器進(jìn)行測(cè)試。
用火狐瀏覽器打開(kāi):https://127.0.0.1:8000/swagger-ui/
在最上面地址欄輸入:https://127.0.0.1:8000/swagger/simple.swagger.json
然后就可以看到swagger生成的API文檔了。
還有個(gè)問(wèn)題,我們使用了bearer token進(jìn)行接口驗(yàn)證的,怎么把bearer token也添加到swagger中呢?
最后我在grpc-gatewayGitHub上的這個(gè)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
大功告成!
驗(yàn)證測(cè)試
1.添加bearer token
2.調(diào)用接口,正確返回?cái)?shù)據(jù)
3.傳遞不合規(guī)則的數(shù)據(jù),返回違反數(shù)據(jù)驗(yàn)證邏輯錯(cuò)誤
總結(jié)
本篇介紹了如何使用grpc-gateway讓gRPC同時(shí)支持HTTP,最終轉(zhuǎn)成的Restful Api支持bearer token驗(yàn)證、數(shù)據(jù)驗(yàn)證。同時(shí)生成swagger文檔,方便API接口對(duì)接。
教程源碼地址: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進(jìn)階教程gRPC轉(zhuǎn)換HTTP的詳細(xì)內(nèi)容,更多關(guān)于Go gRPC轉(zhuǎn)換HTTP的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go開(kāi)源項(xiàng)目用戶名密碼驗(yàn)證的邏輯鬼才寫(xiě)法
這篇文章主要為大家介紹了go開(kāi)源項(xiàng)目中發(fā)現(xiàn)的一個(gè)邏輯鬼才寫(xiě)法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07golang gorm 結(jié)構(gòu)體的表字段缺省值設(shè)置方式
這篇文章主要介紹了golang gorm 結(jié)構(gòu)體的表字段缺省值設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go語(yǔ)言中實(shí)現(xiàn)enum枚舉的方法詳解
枚舉,即?enum,可用于表示一組范圍固定的值,它能助我們寫(xiě)出清晰、安全的代碼,那么你是否了解過(guò)?Go?中的枚舉呢?下面就跟隨小編一起來(lái)學(xué)習(xí)一下Go語(yǔ)言中實(shí)現(xiàn)enum枚舉的常用方法吧2024-02-02Go語(yǔ)言中調(diào)用外部命令的方法總結(jié)
在工作中,我們時(shí)不時(shí)地會(huì)需要在Go中調(diào)用外部命令。本文為大家總結(jié)了Go語(yǔ)言中調(diào)用外部命令的幾種姿勢(shì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-11-11Go語(yǔ)言HTTP請(qǐng)求流式寫(xiě)入body的示例代碼
這篇文章主要介紹了Go語(yǔ)言HTTP請(qǐng)求流式寫(xiě)入body,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06在Go語(yǔ)言程序中使用gojson來(lái)解析JSON格式文件
這篇文章主要介紹了在Go語(yǔ)言程序中使用gojson來(lái)解析JSON格式文件的方法,Go是由Google開(kāi)發(fā)的高人氣新興編程語(yǔ)言,需要的朋友可以參考下2015-10-10go?zero微服務(wù)實(shí)戰(zhàn)處理每秒上萬(wàn)次的下單請(qǐng)求
這篇文章主要為大家介紹了go?zero微服務(wù)實(shí)戰(zhàn)處理每秒上萬(wàn)次的下單請(qǐng)求示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07