go語言中protobuf高階使用
如果你僅僅是想玩玩
go-web
單機開發可以參考gin專案框架
一、微服務之間傳遞資料
-
1、在微服務中不僅僅是可以通過入參和返回引數來進行資料互動,另外還可以通過
metadata
的方式傳遞引數 -
2、定義一個簡單的
proto
檔案
```protobuf syntax = "proto3";
option go_package = ".;proto";
service HelloWorld { rpc SayHello(HelloRequest) returns(HelloResponse); }
message HelloRequest { string name = 1; }
message HelloResponse { string message = 1; } ```
- 3、伺服器端定義介面客戶端傳遞過來的
metadata
的資料
```go type server struct { }
func (s server) SayHello(ctx context.Context, in proto.HelloRequest) (*proto.HelloResponse, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { log.Fatalf("獲取metadata資料失敗") } if nameSlice, ok := md["name"]; ok { fmt.Println("獲取到的資料", nameSlice[0]) } return &proto.HelloResponse{ Message: "hello" + in.Name, }, nil } func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("監聽埠錯誤:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } } ```
- 4、客戶端定義
metadata
傳遞引數給伺服器端
```go func main() { conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) if err != nil { fmt.Println(err.Error()) return } defer conn.Close()
c := proto.NewHelloWorldClient(conn)
// 自定義傳遞的metadata引數
md := metadata.New(map[string]string{
"name": "admin",
"password": "123456",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
response, err := c.SayHello(ctx, &proto.HelloRequest{
Name: "admin",
})
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("伺服器端返回的資料" + response.Message)
} ```
二、攔截器的使用interceptor
-
1、所謂的攔截器是在網路請求的過程中,攔截請求和響應的,以下先配置一個簡單的
protobuf
的網路請求,然後在這個基礎上新增攔截器,來攔截客戶端傳送的資料到伺服器端,也可以用來統計介面訪問時長 -
1、
proto
檔案
```protobuf syntax = "proto3";
option go_package = ".;proto";
service HelloWorld { rpc SayHello(HelloRequest) returns(HelloResponse); }
message HelloRequest { string name = 1; }
message HelloResponse { string message = 1; } ```
- 2、使用命令生成
go
檔案
sh
protoc -I=. --go_out=plugins=grpc,paths=source_relative:. helloWorld.proto
-
3、伺服器端的程式碼
```go type server struct { }
func (s server) SayHello(ctx context.Context, in proto.HelloRequest) (*proto.HelloResponse, error) { return &proto.HelloResponse{ Message: "hello" + in.Name, }, nil }
func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("監聽埠錯誤:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } } ```
-
4、客戶端
```go func main() { conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) if err != nil { fmt.Println(err.Error()) return } defer conn.Close()
c := proto.NewHelloWorldClient(conn) response, err := c.SayHello(context.Background(), &proto.HelloRequest{ Name: "admin", }) if err != nil { fmt.Println(err.Error()) return } fmt.Println("伺服器端返回的資料" + response.Message)
}
-
2、在伺服器端定義攔截器來攔截請求,客戶端的不變,啟動客戶端和伺服器端,觀察伺服器端
```go func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("監聽埠錯誤:" + err.Error()) } // 定義攔截器 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("接收到一個新的請求") return handler(ctx, req) } // 使用攔截器 opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt)
proto.RegisterHelloWorldServer(service, &server{})
err = service.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
} ```
- 3、攔截在請求前和請求後都列印資訊出來
go
...
// 定義攔截器
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("請求前列印")
res, err := handler(ctx, req)
fmt.Println("請求完成列印")
return res, err
}
// 使用攔截器
opt := grpc.UnaryInterceptor(interceptor)
service := grpc.NewServer(opt)
...
- 4、客戶端攔截器的使用
```go func main() { // 定義攔截器 interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { start := time.Now() err := invoker(ctx, method, req, reply, cc, opts...) fmt.Printf("耗時:%s\n", time.Since(start)) return err } // 使用攔截器 opt := grpc.WithUnaryInterceptor(interceptor) conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), opt) if err != nil { fmt.Println(err.Error()) return } defer conn.Close()
c := proto.NewHelloWorldClient(conn)
response, err := c.SayHello(context.Background(), &proto.HelloRequest{
Name: "admin",
})
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("伺服器端返回的資料" + response.Message)
} ```
三、使用攔截器和metadata
來做微服務之間的授權
-
1、這裡授權我們可以簡單的做一個類似使用者登入後才能訪問的功能,把客戶端想象成瀏覽器,把伺服器端想象成之前我們熟悉的
gin-web
開發 -
2、簡單的
demo
和上面的一樣的,只是在基礎上新增攔截器和metadata
的操作 -
3、在客戶端的攔截器中傳遞使用者名稱和密碼到伺服器端
```go func main() { // 定義攔截器 interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { start := time.Now() fmt.Printf("耗時:%s\n", time.Since(start)) // 使用metadata傳遞資料 md := metadata.New(map[string]string{ "username": "admin", "password": "123456", }) ctx = metadata.NewOutgoingContext(context.Background(), md) err := invoker(ctx, method, req, reply, cc, opts...) return err } // 使用攔截器 opt := grpc.WithUnaryInterceptor(interceptor) conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), opt) if err != nil { fmt.Println(err.Error()) return } defer conn.Close()
c := proto.NewHelloWorldClient(conn)
response, err := c.SayHello(context.Background(), &proto.HelloRequest{
Name: "admin",
})
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("伺服器端返回的資料" + response.Message)
} ```
- 4、伺服器端在攔截中獲取到
metadata
中的資料並且判斷使用者名稱和密碼
```go func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("監聽埠錯誤:" + err.Error()) } // 定義攔截器 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("請求前列印") // 解析metadata中的資料驗證 md, ok := metadata.FromIncomingContext(ctx) if !ok { return resp, status.Error(codes.Unauthenticated, "無效的引數") } var ( username string password string ) if val, ok := md["username"]; ok { username = val[0] } if val, ok := md["password"]; ok { password = val[0] } fmt.Println(username, password, "接收到的引數") if username != "admin" || password != "123456" { return resp, status.Error(codes.Unauthenticated, "使用者名稱和密碼錯誤") } res, err := handler(ctx, req) fmt.Println("請求完成列印") return res, err } // 使用攔截器 opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt)
proto.RegisterHelloWorldServer(service, &server{})
err = service.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
} ```
四、引數校驗
- 1、github參考文件
- 2、這個文件還不太穩定,有需要的可以自行研究
五、返回狀態嗎
-
1、我們在
http
請求的時候有狀態嗎,自然在grpc
的時候也有狀態碼,來標註當前請求成功與失敗 -
3、伺服器端使用狀態碼返回給客戶端端
```go import ( ... "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "log" "net" )
type server struct { }
func (s server) SayHello(ctx context.Context, in proto.HelloRequest) (*proto.HelloResponse, error) { //return &proto.HelloResponse{ // Message: "hello" + in.Name, //}, nil // 返回錯誤 return nil, status.Error(codes.NotFound, "記錄沒有找到") }
func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("監聽埠錯誤:" + err.Error()) }
service := grpc.NewServer()
proto.RegisterHelloWorldServer(service, &server{})
err = service.Serve(listen)
if err != nil {
fmt.Println(err.Error())
}
} ```
- 4、客戶端接收錯誤,根據錯誤中的狀態碼提示
```go func main() {
conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
c := proto.NewHelloWorldClient(conn)
response, err := c.SayHello(context.Background(), &proto.HelloRequest{
Name: "admin",
})
if err != nil {
str, ok := status.FromError(err)
if !ok {
panic("解析錯誤資訊失敗")
}
fmt.Println(str.Message(), "獲取到的錯誤資訊")
fmt.Println(str.Code(), "獲取到的錯誤code")
fmt.Println(err.Error())
return
}
fmt.Println("伺服器端返回的資料" + response.Message)
} ```
六、超時處理
- 1、在客戶端設定一個最大時間限制
go
// 設定時間
ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
response, err := c.SayHello(ctx, &proto.HelloRequest{
Name: "admin",
})
- 2、伺服器端休眠下返回資料