文章介紹
在閱讀本文之前建議您先閱讀RPC核心概念理解和GRPC之protobuf理解,在本文中我們不再過多介紹GRPC的基礎部分,主要介紹:grpc的metadata機制、GRPC攔截器、透過攔截器和metadata實現GRPC的auth認證、grpc狀態碼、grpc的超時機制和protoc檔案生成的go原始碼解析
GRPC的metadata機制
metadata的建立
使用metadata.New()
這裡需要注意:返回的metadata.New()返回的是型別:
type MD map[string][]string
,而我們的metadata.New()方法是入參是:map[string]string
,下面我們來看看metadata.New()方法的實現:type MD map[string][]string func New(m map[string]string) MD { md := MD{} for k, val := range m { key := strings.ToLower(k) md[key] = append(md[key], val) } return md }
示例:
md := metadata.New(map[string]string{ "name": "ice_moss", "password": "ice_12345", })
使用metadata.Pairs()
同樣我們可以來檢視一下原始碼:
type MD map[string][]string func Pairs(kv ...string) MD { if len(kv)%2 == 1 { panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv))) } md := MD{} for i := 0; i < len(kv); i += 2 { key := strings.ToLower(kv[i]) md[key] = append(md[key], kv[i+1]) } return md }
仔細看metadata.Pairs()的原始碼可以看出我們的入參個數必須是偶數,並且每兩兩為鍵值對key-value
示例:
md := metadata.Pairs( "name", "ice_moss", "password", "ice_moss12345" )
即:
name: ice_moss password: ice_moss12345
metadata資料的傳送和接收
在此之前我們先來定義一個proto檔案,來滿足我們下面的需要的metadata資料使用場景
Proto:
syntax = "proto3"; option go_package="/.;proto"; //引入protobuf的內建型別 import "google/protobuf/timestamp.proto"; //定義介面 service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } //列舉型別 enum Gender{ MALE = 0; FE_MALE = 1; } message HelloRequest { string name = 1; string url = 2; Gender gender = 3; map<string, string> m = 4; //proto map型別 google.protobuf.Timestamp addTime = 5; //protobuf的內建型別 } message HelloReply { string id = 1; HelloRequest request = 2; }
以上程式碼有不理解的可以閱讀:GRPC之protobuf理解
這裡我們來模擬客服端client和服務端server進行互動,來進行metadata資料的傳送和接收,這裡我們需要知道的是metadata是透過go語言中的context的上下文來傳輸的
server:
package main import ( "context" "log" "net" "rpcstudy/metadata_test/proto" "google.golang.org/grpc" ) type HelloSerivce struct{} //SayHello實現對使用者資料和id繫結 func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) { //透過metadata.FromIncomingContext()方法獲取context的上下文 md, ok := metadata.FromIncomingContext(c) if !ok { log.Fatal("FromIncomingContext error") } for key, value := range md { fmt.Printf("key:%v, value:%v\n", key, value) } return &proto.HelloReply{ Id: "123456789", Request: req, }, nil } func main() { //例項化server lit, err := net.Listen("tcp", ":9090") if err != nil { log.Panicln("監聽失敗", err) } //建立grpc伺服器 s := grpc.NewServer() //註冊處理邏輯 proto.RegisterGreeterServer(s, &HelloSerivce{}) //開啟服務,這裡理解為將連線連入grpc伺服器中,就可以完成通訊 log.Println(s.Serve(lit)) }
寫完server並執行server,等待請求進入
client:
package main import ( "context" "fmt" "log" "rpcstudy/metadata_test/proto" "time" "google.golang.org/grpc/metadata" "google.golang.org/grpc" timepb "google.golang.org/protobuf/types/known/timestamppb" ) func main() { //使用grpc.Dial()進行撥號, grpc.WithInsecure()使用不安全的方式連線 clientConn, err := grpc.Dial("localhost:9090", grpc.WithInsecure()) if err != nil { log.Panicln("連線失敗", err) } defer clientConn.Close() md := metadata.New(map[string]string{ "name": "ice_moss", "password": "ice_12345", }) //傳送metadata //NewOutgoingContext建立一個新的上下文,並附加了md放入context中,並返回context //即:新建一個有metada的context ctx := metadata.NewOutgoingContext(context.Background(), md) //連線grpc伺服器 client := proto.NewGreeterClient(clientConn) //單向傳送至server,將帶有md的上下文ctx入參 res, err := client.SayHello(ctx, &proto.HelloRequest{ Name: "kuangyang", Url: "https://learnku.com/blog/yangkuang", Gender: proto.Gender_MALE, M: map[string]string{ "來自": "北京", "現居": "上海", }, AddTime: timepb.New(time.Now()), }) if err != nil { panic(err) } fmt.Printf("返回結果: %v", res) }
執行client,最後client返回:
返回結果: id:"123456789" request:{name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds929482000}}
server輸出:
key:user-agent, value:[grpc-go/1.46.2] key:name, value:[ice_moss] key:password, value:[ice_12345] key::authority, value:[localhost:9090] key:content-type, value:[application/grpc]
輸出的內容中我們就可以看到客服端透過metadata請求發過來的資料,當然裡面還有一些其他的資料,其實就是我們的handler,和http的handler類似
總結一下:
建立metadata:
metadata.New() metadata.Pairs()
將metadata資料放入context中:
//NewOutgoingContext(ctx, md) //函式宣告 func NewOutgoingContext(ctx context.Context, md MD) context.Context { return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md}) }
將metadata資料從context中拿出:
metadata.FromIncomingContext(c) //函式宣告 func FromIncomingContext(ctx context.Context) (MD, bool) { md, ok := ctx.Value(mdIncomingKey{}).(MD) if !ok { return nil, false } out := MD{} for k, v := range md { // We need to manually convert all keys to lower case, because MD is a // map, and there's no guarantee that the MD attached to the context is // created using our helper functions. key := strings.ToLower(k) s := make([]string, len(v)) copy(s, v) out[key] = s } return out, true }
GRPC的攔截器
我們在開發的過程中驗證使用者身份、驗證token、驗證有效時間等,我們又不想將我們在驗證機制加入到我們的業務邏輯的程式碼中,並且我們的業務邏輯不可能每一個業務都進行驗證,這時就需要攔截器來解決這類問題。而grpc為我們提供了攔截器的實現,可以方便開發者更好的開發,在執行業務邏輯之前,服務端會先執行我們實現的攔截器,對客服端資料的提取和驗證。
在metadata機制中,我們獲取metadata資料是透過我們的業務程式碼SayHell()方法來實現的,這顯然是影響到了我們的業務邏輯,業務程式碼中我們只想做業務的開發,並且不可能每一個業務都寫入驗證機制,下面我們直接來實現一個簡單的攔截器:
我們依然使用metadata機制中的proto程式碼,下面我們來寫server和client的程式碼
server:
package main
import (
"context"
"fmt"
"log"
"net"
"rpcstudy/grpc_interpretor/proto"
"google.golang.org/grpc"
)
type HelloSerivce struct{}
func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
//此時我們不需要在 SayHello中寫攔截器邏輯了
//接收client傳送的handle訊息
//md, ok := metadata.FromIncomingContext(c)
//if !ok {
// fmt.Println("get metadata err")
//}
//
//for key, value := range md {
// fmt.Printf("%v: %v\n", key, value)
//}
return &proto.HelloReply{
Id: "123456789",
Request: req,
}, nil
}
func main() {
//grpc在呼叫業務邏輯之前會先執行inerpertor,進行資料的攔截
//例項化server
lit, err := net.Listen("tcp", ":9090")
if err != nil {
log.Panicln("監聽失敗", err)
}
//實現攔截器的處理邏輯
interpretor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ma, ok := metadata.FromIncomingContext(ctx)
if !ok {
log.Fatal("FromIncomingContext error")
}
fmt.Println("metadata資料: \n", ma)
fmt.Println("攔截到一條新訊息:\n", req)
//我們想繼續做時間統計,不讓直接返回
res, err := handler(ctx, req)
fmt.Println("該請求已經完成")
return res, err
}
//grpc攔截器的使用,返回一個ServerOption
sopt := grpc.UnaryInterceptor(interpretor)
//註冊處理邏輯
//建立grpc服務, NewServer的引數為ServerOption型別的可選引數
//即建立伺服器時使用可放入攔截器
s := grpc.NewServer(sopt)
proto.RegisterGreeterServer(s, &HelloSerivce{})
//開啟服務
log.Println(s.Serve(lit))
}
執行server,等待請求進入
Client:
package main
import (
"context"
"fmt"
"log"
"rpcstudy/grpc_interpretor/proto"
"time"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc"
timepb "google.golang.org/protobuf/types/known/timestamppb"
)
func main() {
//client攔截器,client的攔截器和server攔截器邏輯一致,程式碼有點出入,具體不過多介紹,具體看這部分註釋:
//interpretor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// start := time.Now()
// md := metadata.New(map[string]string{
// "appid": "ice_moss",
// "appkey": "ice_12345",
// })
// //NewOutgoingContext建立一個新的上下文context,並附加了md放入context中,並返回這個context
// ctx = metadata.NewOutgoingContext(context.Background(), md)
//
// //呼叫者invoker
// err := invoker(ctx, method, req, reply, cc)
// fmt.Printf("消耗時間:%s\n", time.Since(start))
// return err
//}
//
//opt := grpc.WithUnaryInterceptor(interpretor)
clientConn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
if err != nil {
log.Panicln("連線失敗", err)
}
defer clientConn.Close()
//連線grpc伺服器
client := proto.NewGreeterClient(clientConn)
md := metadata.New(map[string]string{
"appid": "ice_moss",
"appkey": "ice_12345",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
res, err := client.SayHello(ctx, &proto.HelloRequest{
Name: "kuangyang",
Url: "https://learnku.com/blog/yangkuang",
Gender: proto.Gender_MALE,
M: map[string]string{
"來自": "北京",
"現居": "上海",
},
AddTime: timepb.New(time.Now()),
})
if err != nil {
panic(err)
}
fmt.Printf("返回結果: %v", res)
}
這裡我們只介紹server攔截器,client返回結果:
返回結果: id:"123456789" request:{name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds:165s:686279000}}
server輸出:
metadata資料:
map[:authority:[localhost:9090] appid:[ice_moss] appkey:[ice_12345] content-type:[application/grpc] user-agent:[grpc-go/1.46.2]]
攔截到一條新訊息:
name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds:1652953608 nanos:686279000}
該請求已經完成
仔細看我們的server不僅拿到了metadata中的資料,也拿到了client上傳的資訊資料,接著我們就可以將這些資料進行有效驗證了
攔截器和metadata實現GRPC的auth認證
學習了上面的內容,下面我們來實現對使用者的身份和密碼的驗證這裡我們叫auth認證,同樣使用上面內容中的proto檔案,直接上程式碼:
server:
package main
import (
"context"
"fmt"
"log"
"net"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc/metadata"
"rpcstudy/grpc_interpretor/proto"
"google.golang.org/grpc"
)
//實現介面的實現者
type HelloSerivce struct{}
//對使用者資料和id進行繫結
func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
return &proto.HelloReply{
Id: "123456789",
Request: req,
}, nil
}
func main() {
//grpc在呼叫業務邏輯之前會先執行inerpertor,進行資料的攔截
//例項化server
lit, err := net.Listen("tcp", ":9090")
if err != nil {
log.Panicln("監聽失敗", err)
}
//攔截器處理邏輯
interpretor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
//接收client傳送的handle訊息
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Println("get metadata err")
return nil, status.Error(codes.Unauthenticated, "沒有token認證")
}
var appid string
var appkey string
//將appid的value取出
if value, ok := md["appid"]; ok {
appid = value[0]
}
//將appkey的value取出
if value, ok := md["appkey"]; ok {
appkey = value[0]
}
if appid != "ice_moss" || appkey != "ice_12345" {
fmt.Println("使用者token未認證")
return nil, status.Error(codes.Unauthenticated, "沒有token認證")
}
fmt.Println("攔截到一條新訊息:\n", req)
//我們想繼續做時間統計,不讓直接返回
res, err := handler(ctx, req)
fmt.Println("該請求已經完成, 認證成功")
return res, err
}
//grpc攔截器的使用,返回一個ServerOption
sopt := grpc.UnaryInterceptor(interpretor)
//註冊處理邏輯
//建立grpc服務, NewServer的引數為ServerOption型別的可選引數
//即建立伺服器時將攔截器放入伺服器中
s := grpc.NewServer(sopt)
proto.RegisterGreeterServer(s, &HelloSerivce{})
//開啟服務
log.Println(s.Serve(lit))
}
執行server, 等待請求進入
client:
package main
import (
"context"
"fmt"
"log"
"rpcstudy/grpc_interpretor/proto"
"time"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc"
timepb "google.golang.org/protobuf/types/known/timestamppb"
)
func main() {
clientConn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
if err != nil {
log.Panicln("連線失敗", err)
}
defer clientConn.Close()
//連線grpc伺服器
client := proto.NewGreeterClient(clientConn)
md := metadata.New(map[string]string{
"appid": "ice_moss",
"appkey": "ice_12345",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
res, err := client.SayHello(ctx, &proto.HelloRequest{
Name: "kuangyang",
Url: "https://learnku.com/blog/yangkuang",
Gender: proto.Gender_MALE,
M: map[string]string{
"來自": "北京",
"現居": "上海",
},
AddTime: timepb.New(time.Now()),
})
if err != nil {
panic(err)
}
fmt.Printf("返回結果: %v", res)
}
client返回:
返回結果: id:"123456789" request:{name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds:165nanos:883406000}}
server輸出:
攔截到一條新訊息:
name:"kuangyang" url:"https://learnku.com/blog/yangkuang" m:{key:"來自" value:"北京"} m:{key:"現居" value:"上海"} addTime:{seconds:1652954947 nanos:883406000}
該請求已經完成, 認證成功
接下來我們將metadata中資料修改一下:
md := metadata.New(map[string]string{
"appid": "ice_moss",
"appkey": "ice",
})
client返回:
panic: rpc error: code = Unauthenticated desc = 沒有token認證
Server輸出:
使用者token未認證
這就是一個簡單的auth認證過程,當然這裡也歡迎閱讀我的微信小程式使用者登入token認證: 微信小程式登入服務後端實戰
grpc狀態碼
其實在上一部分內容中我們就已經接觸到了grpc狀態碼
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Println("get metadata err")
return nil, status.Error(codes.Unauthenticated, "沒有token認證")
}
status.Error(codes.Unauthenticated, "沒有token認證")
這既是一個狀態碼
又或者是:
if req.Name == "" {
return nil, status.Errorf(codes.NotFound, "未找到name:%s", req)
}
不做過多介紹,關於更多狀態碼
grpc的超時機制
我們在使用grpc呼叫過程中,如果某一個請求沒跟上,或者出現一些情況,導致我們的請求一直沒有返回,這肯定是不行的,例如:我們請求A服務,然後A會去呼叫其他服務,如:A -> B -> C -> D,但是這個過程D一直沒有返回,這肯定會導致我們的C回不來,就處於一直等待的狀態了,這不僅佔用資源,這也使得呼叫者體驗不好,所以需要使用grpc的超時機制來防止此類問題的傳送,而grpc為我們提供了這樣方法,我們只需要只需要在客服端使用:func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
我們以auth認證程式碼為例,只需要在客服端呼叫func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
client:
//建立一個有時間限定context的上下文
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
//使用metadata機制和攔截器進行使用者身份驗證
md := metadata.New(map[string]string{
"appid": "ice_moss",
"appkey": "12345",
})
//將md附加到context上下文中
ctx = metadata.NewOutgoingContext(ctx, md)
在server中我們我們加入:
func (h *HolleService) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
//驗證超時機制
time.Sleep(5 * time.Second)
……
……
……
}
我們給這個請求2秒的時間,但是我們執行 SayHello服務需要執行5秒時間,顯然這個請求已經超時了,加入超時機制後,他不會一直等待,2秒後他就返回:
2022/05/19 18:38:41 cannot get id for messagerpc error: code = DeadlineExceeded desc = context deadline exceeded
protoc檔案生成的go原始碼解析
我們以metadata機制內容中的proto檔案為例:
syntax = "proto3";
option go_package="/.;proto";
//引入protobuf的內建型別
import "google/protobuf/timestamp.proto";
//定義介面
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
//列舉型別
enum Gender{
MALE = 0;
FE_MALE = 1;
}
message HelloRequest {
string name = 1;
string url = 2;
Gender gender = 3;
map<string, string> m = 4; //proto map型別
google.protobuf.Timestamp addTime = 5; //protobuf的內建型別
}
message HelloReply {
string id = 1;
HelloRequest request = 2;
}
執行protoc -I . holle.proto --go_out=plugins=grpc:.
後生成:hello.pb.go檔案
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.13.0
// source: md.proto
package proto
import (
context "context"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
//列舉型別
type Gender int32
const (
Gender_MALE Gender = 0
Gender_FE_MALE Gender = 1
)
// Enum value maps for Gender.
var (
Gender_name = map[int32]string{
0: "MALE",
1: "FE_MALE",
}
Gender_value = map[string]int32{
"MALE": 0,
"FE_MALE": 1,
}
)
func (x Gender) Enum() *Gender {
p := new(Gender)
*p = x
return p
}
func (x Gender) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Gender) Descriptor() protoreflect.EnumDescriptor {
return file_md_proto_enumTypes[0].Descriptor()
}
func (Gender) Type() protoreflect.EnumType {
return &file_md_proto_enumTypes[0]
}
func (x Gender) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Gender.Descriptor instead.
func (Gender) EnumDescriptor() ([]byte, []int) {
return file_md_proto_rawDescGZIP(), []int{0}
}
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
Gender Gender `protobuf:"varint,3,opt,name=gender,proto3,enum=Gender" json:"gender,omitempty"`
M map[string]string `protobuf:"bytes,4,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` //proto map型別
AddTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=addTime,proto3" json:"addTime,omitempty"` //protobuf的內建型別
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_md_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_md_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_md_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *HelloRequest) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *HelloRequest) GetGender() Gender {
if x != nil {
return x.Gender
}
return Gender_MALE
}
func (x *HelloRequest) GetM() map[string]string {
if x != nil {
return x.M
}
return nil
}
func (x *HelloRequest) GetAddTime() *timestamp.Timestamp {
if x != nil {
return x.AddTime
}
return nil
}
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Request *HelloRequest `protobuf:"bytes,2,opt,name=request,proto3" json:"request,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
if protoimpl.UnsafeEnabled {
mi := &file_md_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_md_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_md_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *HelloReply) GetRequest() *HelloRequest {
if x != nil {
return x.Request
}
return nil
}
var File_md_proto protoreflect.FileDescriptor
var file_md_proto_rawDesc = []byte{
0x0a, 0x08, 0x6d, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x01, 0x0a, 0x0c,
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x07, 0x2e, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x06, 0x67, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x01, 0x6d, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x01, 0x6d, 0x12, 0x34, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x54, 0x69,
0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x61, 0x64, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x34, 0x0a,
0x06, 0x4d, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0x45, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x27, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2a, 0x1f, 0x0a, 0x06, 0x47, 0x65,
0x6e, 0x64, 0x65, 0x72, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x41, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b,
0x0a, 0x07, 0x46, 0x45, 0x5f, 0x4d, 0x41, 0x4c, 0x45, 0x10, 0x01, 0x32, 0x31, 0x0a, 0x07, 0x47,
0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c,
0x6c, 0x6f, 0x12, 0x0d, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x0b, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x0a,
0x5a, 0x08, 0x2f, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_md_proto_rawDescOnce sync.Once
file_md_proto_rawDescData = file_md_proto_rawDesc
)
func file_md_proto_rawDescGZIP() []byte {
file_md_proto_rawDescOnce.Do(func() {
file_md_proto_rawDescData = protoimpl.X.CompressGZIP(file_md_proto_rawDescData)
})
return file_md_proto_rawDescData
}
var file_md_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_md_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_md_proto_goTypes = []interface{}{
(Gender)(0), // 0: Gender
(*HelloRequest)(nil), // 1: HelloRequest
(*HelloReply)(nil), // 2: HelloReply
nil, // 3: HelloRequest.MEntry
(*timestamp.Timestamp)(nil), // 4: google.protobuf.Timestamp
}
var file_md_proto_depIdxs = []int32{
0, // 0: HelloRequest.gender:type_name -> Gender
3, // 1: HelloRequest.m:type_name -> HelloRequest.MEntry
4, // 2: HelloRequest.addTime:type_name -> google.protobuf.Timestamp
1, // 3: HelloReply.request:type_name -> HelloRequest
1, // 4: Greeter.SayHello:input_type -> HelloRequest
2, // 5: Greeter.SayHello:output_type -> HelloReply
5, // [5:6] is the sub-list for method output_type
4, // [4:5] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_md_proto_init() }
func file_md_proto_init() {
if File_md_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_md_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_md_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_md_proto_rawDesc,
NumEnums: 1,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_md_proto_goTypes,
DependencyIndexes: file_md_proto_depIdxs,
EnumInfos: file_md_proto_enumTypes,
MessageInfos: file_md_proto_msgTypes,
}.Build()
File_md_proto = out.File
file_md_proto_rawDesc = nil
file_md_proto_goTypes = nil
file_md_proto_depIdxs = nil
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
type greeterClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
// UnimplementedGreeterServer can be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "md.proto",
}
message –> : 生成了對應的結構體
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
Gender Gender `protobuf:"varint,3,opt,name=gender,proto3,enum=Gender" json:"gender,omitempty"`
M map[string]string `protobuf:"bytes,4,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` //proto map型別
AddTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=addTime,proto3" json:"addTime,omitempty"` //protobuf的內建型別
}
我們可以看到這部分程式碼: 這部分為客服端呼叫
//介面
type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
//介面的實現者
type greeterClient struct {
cc grpc.ClientConnInterface
}
//構造一個greeterClient物件
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
//實現介面的方法
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
我們在client中要呼叫grpc我們定義的業務程式碼時呼叫了:
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
入參是: 這個介面中的兩個方法主要就是為我們做序列化和反序列化操作:
type ClientConnInterface interface {
// Invoke performs a unary RPC and returns after the response is received
// into reply.
Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...CallOption) error
// NewStream begins a streaming RPC.
NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error)
}
然後他返回了一個greeterClient{cc}
的物件
在程式碼中可以看到err := c.cc.Invoke(ctx, "/Greeter/SayHello", in, out, opts...)d
其中:”/Greeter/SayHello”就是SayHello方法的對映ID
接下來看這部分程式碼:
// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
// UnimplementedGreeterServer can be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
裡面有一個介面:
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
這個介面就需要我們來實現,他就是我們用來做業務邏輯處理的,我們在server中是這樣實現它的:
//介面的實現者
type HelloSerivce struct{}
//實現介面中的方法
func (h *HelloSerivce) SayHello(c context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
return &proto.HelloReply{
Id: "123456789",
Request: req,
}, nil
}
然後再來看這個方法:
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
我們在server中是這樣呼叫的:
//建立一個grpc伺服器
s := grpc.NewServer()
proto.RegisterGreeterServer(s, &HelloSerivce{})
我們看這個RegisterGreeterServer(s *grpc.Server, srv GreeterServer)
方法入參是s *grpc.Server, srv
和GreeterServer
型別,而GreeterServer
是一個介面:
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
為什麼我們可以在呼叫的時候直接將&HelloSerivce{}
傳入? 答案是:HelloSerivce
結構體實現了GreeterServer
介面,這個&HelloSerivce{}
物件可以直接傳入。 如果你對介面的知識不理解也可以閱讀「Golang成長之路」面向介面
其實我們在hello.pb.go的原始碼中就需要理解這麼多就行了。
這裡我們的GRPC的內容就介紹結束,感謝閱讀!
本作品採用《CC 協議》,轉載必須註明作者和本文連結