有關GPRC 的創建 大家 請參考 go學習筆記 Windows Go 1.15 以上版本的 GRPC 通信【自簽CA和雙向認證】,本文同樣會用上文創建的證書。【注意我的環境是win7+go1.15.6】
1:將REST注釋添加到API定義,我們必須安裝grpc-gateway和swagger文檔生成器插件
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger go get -u github.com/golang/protobuf/protoc-gen-go go get -u -v github.com/golang/protobuf/protoc-gen-g
先看看我文件夾的結構吧:
2創建 /api/proto/v1/todo.protor然后生成 ,你可以在這里獲得proto語言規范:https://developers.google.com/protocol-buffers/docs/proto3,這里增加了swagger的配置,這個配置的作用是讓swagger把遠程調用配置成http,如果沒有這些配置,swagger默認的遠程調用就是https的,本文的gRPC-Gateway提供的是http服務,所以要加上這些配置
syntax = "proto3"; package protos; // 1 導入 gateway 相關的proto 以及 swagger 相關的 proto import "google/api/annotations.proto"; import "protoc-gen-swagger/options/annotations.proto"; // 2 定義 swagger 相關的內容 option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = { info: { title: "grpc gateway sample"; version: "1.0"; license: { name: "MIT"; }; }; schemes: HTTP; consumes: "application/json"; produces: "application/json"; }; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) { // 3 標識接口路由 option (google.api.http) = { post: "/hello_world" body: "*" }; } } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
生成命令如下,
1.如果編譯遇到Interpreting non ascii codepoint 194. 類似錯誤【說白了就是有肉眼看不見的一些東西】,我用的是VSCode,所以安裝插件 vscode-proto3 及其依賴的 Clang-Format 【如果存在報錯 clang-format not installed
,需要在系統里安裝 clang-format】安裝后一眼就可以看出來了
2.如果編譯遇到 google/api/annotations.proto: File not found.之類問題,需要指定protoc的include 文件夾路徑【我把protoc-3.14.0-win64.zip 解壓到D:\Go ,所以我的是D:\Go\include】 ,如果里面沒有 可以搜索電腦 , 然后拷貝過去【比如我的是在 D:\GoProject\pkg\mod\cache\download\github.com\grpc-ecosystem\grpc-gateway\@v\v1.16.0\github.com\grpc-ecosystem\grpc-gateway@v1.16.0\third_party\googleapis\google\api 和 D:\GoProject\pkg\mod\cache\download\github.com\grpc-ecosystem\grpc-gateway\@v\v1.16.0\github.com\grpc-ecosystem\grpc-gateway@v1.16.0\protoc-gen-swagger\options 這個 路徑太長, 我拷貝到D:\Go\include 】
編譯命令:[google/api/annotations.proto 在D:\Go\include里面]
//我是在hello 目錄下執行的 protoc -ID:\Go\include -I. --go_out=plugins=grpc:. ./protos/hello.proto protoc -ID:\Go\include -I. --grpc-gateway_out=logtostderr=true:. ./protos/hello.proto protoc -ID:\Go\include -I. --swagger_out=logtostderr=true:. ./protos/hello.proto
如果你的VScode 安裝了 swagger viewer版本,使用快捷鍵Shift + Alt + P可以啟動預覽模式,一邊編寫配置文件,一邊查看效果,還可以進行語法檢查
3..創建hello/server/services/services.go
package services import ( "context" "fmt" pb "hello/protos" ) type server struct{} func NewServer() *server { return &server{} } func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { fmt.Println("request: ", in.Name) return &pb.HelloReply{Message: "hello, " + in.Name}, nil }
4.先下載swagger ui的靜態文件放到 hello\third_party下面。 https://github.com/swagger-api/swagger-ui 把這些文件打包成go文件。
go get -u github.com/jteeuwen/go-bindata/... //安裝 go-bindata --nocompress -pkg swagger -o gateway/swagger/datafile.go third_party/swagger-ui/...
go get github.com/elazarl/go-bindata-assetfs/...
5.准備網關/hello/gateway/gateway.go
package gateway import ( "fmt" "net/http" "path" "strings" assetfs "github.com/elazarl/go-bindata-assetfs" swagger "hello/gateway/swagger" gw "hello/protos" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" ) /// func HttpRun(gprcPort, httpPort string) error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() gwmux, err := newGateway(ctx, gprcPort) if err != nil { panic(err) } mux := http.NewServeMux() mux.Handle("/", gwmux) mux.HandleFunc("/swagger/", serveSwaggerFile) serveSwaggerUI(mux) fmt.Println("grpc-gateway listen on localhost" + httpPort) return http.ListenAndServe(httpPort, mux) } func newGateway(ctx context.Context, gprcPort string) (http.Handler, error) { opts := []grpc.DialOption{grpc.WithInsecure()} gwmux := runtime.NewServeMux() if err := gw.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, gprcPort, opts); err != nil { return nil, err } return gwmux, nil } func serveSwaggerFile(w http.ResponseWriter, r *http.Request) { if !strings.HasSuffix(r.URL.Path, "swagger.json") { fmt.Printf("Not Found: %s\r\n", r.URL.Path) http.NotFound(w, r) return } p := strings.TrimPrefix(r.URL.Path, "/swagger/") p = path.Join("../protos", p) fmt.Printf("Serving swagger-file: %s\r\n", p) http.ServeFile(w, r, p) } func serveSwaggerUI(mux *http.ServeMux) { fileServer := http.FileServer(&assetfs.AssetFS{ Asset: swagger.Asset, AssetDir: swagger.AssetDir, Prefix: "third_party/swagger-ui", }) prefix := "/swagger-ui/" mux.Handle(prefix, http.StripPrefix(prefix, fileServer)) }
6.准備f服務 /hello/server/main.go
package main import ( "fmt" pb "hello/protos" "hello/server/services" "log" "net" "hello/gateway" "google.golang.org/grpc" ) func main() { grpcPort := ":9090" httpPort := ":8080" lis, err := net.Listen("tcp", grpcPort) if err != nil { log.Fatalf("failed to listen: %v", err) } go gateway.HttpRun(grpcPort, httpPort) s := grpc.NewServer() pb.RegisterGreeterServer(s, services.NewServer()) fmt.Println("rpc services started, listen on localhost" + grpcPort) s.Serve(lis) }
運行結果如下:
D:\GoProject\src\hello>cd server D:\GoProject\src\hello\server>go run main.go rpc services started, listen on localhost:9090 grpc-gateway listen on localhost:8080
訪問 http://localhost:8080/swagger/hello.swagger.json 正常
7.創建客戶端的main.go【分為 gprc 和http 部分】hello/client/main.go
package main import ( "context" "encoding/json" "fmt" pb "hello/protos" sv "hello/server/services" "io/ioutil" "net/http" "strings" "google.golang.org/grpc" ) func main() { req := pb.HelloRequest{Name: "gavin"} // GRPC conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure()) if err != nil { fmt.Println(err) return } defer conn.Close() c := sv.NewServer() r, err := c.SayHello(context.Background(), &req) if err != nil { fmt.Println(err) } // 打印返回值 fmt.Println(r) fmt.Println("http Start......................") //http requestByte, _ := json.Marshal(req) request, _ := http.NewRequest("POST", "http://localhost:8080/hello_world", strings.NewReader(string(requestByte))) request.Header.Set("Content-Type", "application/json") response, _ := http.DefaultClient.Do(request) bodyBytes, err := ioutil.ReadAll(response.Body) defer response.Body.Close() fmt.Println(string(bodyBytes)) }
運行效果:
D:\GoProject\src\hello>cd client D:\GoProject\src\hello\client>go run main.go request: gavin message:"hello, gavin" http Start...................... {"message":"hello, gavin"}
--------------------------------------------------------------------------------------------------------------------
8.開啟grpc的雙向認證
受到 asp.ner core 5.0 Grpc雙向認證 和 restful api包裝 外加swagger啟用【VSCode創建】,決定多開一個 端口 用於grpc tsl的雙向認證, 修改后的server/main.go代碼如下:
package main import ( "crypto/tls" "crypto/x509" "fmt" "hello/gateway" pb "hello/protos" "hello/server/services" "io/ioutil" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" ) func main() { //grpc grpctslPort := ":9090" grpcPort := ":8081" httpPort := ":8080" ///grpc tsl 用於雙向認證 go GrpcTslServer(grpctslPort) ///普通的主要是便於gateway使用 lis, err := net.Listen("tcp", grpcPort) if err != nil { log.Fatalf("failed to listen: %v", err) } //gateway go gateway.HttpRun(grpcPort, httpPort) s := grpc.NewServer() pb.RegisterGreeterServer(s, services.NewServer()) fmt.Println("rpc services started, listen on localhost" + grpcPort) s.Serve(lis) } func GrpcTslServer(grpctslPort string) error { //證書 cert, _ := tls.LoadX509KeyPair("../cert/server.pem", "../cert/server.key") certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("../cert/ca.pem") certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: certPool, }) sl := grpc.NewServer(grpc.Creds(creds)) // 創建GRPC pb.RegisterGreeterServer(sl, services.NewServer()) reflection.Register(sl) // 在GRPC服務器注冊服務器反射服務 listener, err := net.Listen("tcp", grpctslPort) if err != nil { fmt.Println(err) return err } fmt.Println("rpc tsl services started, listen on localhost" + grpctslPort) return sl.Serve(listener) }
client/main.go如下:
package main import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" pb "hello/protos" sv "hello/server/services" "io/ioutil" "net/http" "strings" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func main() { ///grpc client req := pb.HelloRequest{Name: "gavin"} cert, err := tls.LoadX509KeyPair("../certs/client.pem", "../certs/client.key") certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("ca.pem") certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ServerName: "localhost", RootCAs: certPool, }) // GRPC conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(creds)) if err != nil { fmt.Println(err) return } defer conn.Close() c := sv.NewServer() r, err := c.SayHello(context.Background(), &req) if err != nil { fmt.Println(err) } // 打印返回值 fmt.Println(r) fmt.Println("http Start......................") //http requestByte, _ := json.Marshal(req) client := http.Client{Timeout: 15 * time.Second} resp, err := client.Post("http://localhost:8080/hello_world", "application/json", strings.NewReader(string(requestByte))) bodyBytes, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() fmt.Println(string(bodyBytes)) }
運行效果:
D:\GoProject\src\gogrpccertAndgateway\server>go run main.go rpc tsl services started, listen on localhost:9090 rpc services started, listen on localhost:8081 grpc-gateway listen on localhost:8080 request: gavin
參考:
https://www.cnblogs.com/catcher1994/archive/2004/01/13/11869532.html
http://weekly.dockerone.com/article/10028
https://segmentfault.com/a/1190000013513469