介绍
grpc提供了拦截器,可以使用拦截器开发grpc中间件,实现各种中间功能。 比如: 日志采集,认证 等
pb文件
$ protoc -I ./ pb/*.proto --plugin=protoc-gen-go=protoc-gen-go --go_out=plugins=grpc:pb
syntax = "proto3";
option go_package = "./echo";
package echo;
// Request
message EchoRequest {
string message = 1;
}
// Response
message EchoResponse {
string message = 1;
}
// Echo is the echo service.
service Echo {
// UnaryEcho is unary echo.
rpc UnaryEcho(EchoRequest) returns (EchoResponse) {}
// ServerStreamingEcho is server side streaming.
rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {}
// ClientStreamingEcho is client side streaming.
rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {}
// BidirectionalStreamingEcho is bidi streaming.
rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {}
}
Server端
核心方法: grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor), grpc.StreamInterceptor(streamInterceptor))
unaryInterceptor
func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Println("[Server] 请求参数 = ", req)
log.Println("[Server] 请求方法 = ", info.FullMethod)
md, _ := metadata.FromIncomingContext(ctx)
log.Println("[Server] context 上下文参数 = ", md)
m, err := handler(ctx, req)
log.Println("[Server] handler = ", m, err)
if err != nil {
log.Println("RPC failed with error %v", err)
}
return m, err
}
streamInterceptor
func streamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
log.Println("[Server] 发起了请求, streamInterceptor. ")
md, _ := metadata.FromIncomingContext(ss.Context())
log.Println("[Server] md = ", md)
err := handler(srv, newWrappedStream(ss))
if err != nil {
log.Println("RPC failed with error %v", err)
}
return err
}
// wrappedStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and
// SendMsg method call.
type wrappedStream struct {
grpc.ServerStream
}
func newWrappedStream(s grpc.ServerStream) grpc.ServerStream {
return &wrappedStream{s}
}
Client端
核心方法: conn, err := grpc.Dial("127.0.0.1:18881", grpc.WithInsecure(), grpc.WithUnaryInterceptor(unaryInterceptor), grpc.WithStreamInterceptor(streamInterceptor))
setCtx 设置上下文参数
// setCtx set context
func setCtx(kv map[string]string, grpcConn *grpc.ClientConn) context.Context {
if grpcConn == nil {
return nil
}
value := make([]string,0)
for k, v := range kv {
value = append(value, k)
value = append(value, v)
}
return metadata.NewOutgoingContext(context.Background(), metadata.Pairs(value...))
}
unaryInterceptor
func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
log.Println("[Clinet] 请求方法 = ", method)
log.Println("[Clinet] 请求参数 = ", req)
log.Println("[Clinet] reply = ", reply)
log.Println("[Clinet] 请求状态 = ", cc.GetState())
log.Println("[Clinet] 服务地址 = ", cc, cc.Target())
// 给 ctx 设置参数
ctx = setCtx(map[string]string{"a":"a", "b":"b", "c":"c"}, cc)
// 请求时间记录 start, end
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
end := time.Now()
// 打印日志
log.Printf("RPC: %s, start time: %s, end time: %s, err: %v", method, start.Format("Basic"), end.Format(time.RFC3339), err)
return err
}
cc.GetState()
为了向 gRPC API(即应用程序代码)的用户隐藏所有这些活动的详细信息,同时公开有关通道状态的有意义的信息,我们使用具有五个状态的状态机,定义如下:
CONNECTING:通道正在尝试建立连接,并且正在等待名称解析、TCP 连接建立或 TLS 握手中涉及的步骤之一取得进展。这可以用作创建通道时的初始状态。
READY:通道已通过 TLS 握手(或等效)和协议级(HTTP/2 等)握手成功建立了连接,并且所有后续通信尝试都已成功(或在没有任何已知故障的情况下处于挂起状态)。
TRANSIENT_FAILURE:出现了一些暂时性故障(例如 TCP 3 次握手超时或套接字错误)。该状态的通道最终会切换到 CONNECTING 状态并尝试再次建立连接。由于重试是通过指数退避完成的,因此无法连接的通道一开始将在此状态下花费很少的时间,但随着尝试反复失败,通道将在此状态下花费越来越多的时间。对于许多非致命故障(例如,由于服务器尚不可用,TCP 连接尝试超时),通道可能会在此状态下花费越来越多的时间。
IDLE:这是通道甚至没有尝试创建连接的状态,因为缺少新的或挂起的 RPC。在这种状态下可以创建新的 RPC。任何在通道上启动 RPC 的尝试都会将通道推出此状态以进行连接。如果在指定的 IDLE_TIMEOUT 内通道上没有 RPC 活动,即在此期间没有新的或未决(活动)的 RPC,则准备好或正在连接的通道将切换到 IDLE。此外,当没有活动或挂起的 RPC 时接收 GOAWAY 的通道也应该切换到 IDLE 以避免在尝试脱落连接的服务器上的连接过载。我们将使用 300 秒(5 分钟)的默认 IDLE_TIMEOUT。
SHUTDOWN:此频道已开始关闭。任何新的 RPC 都应该立即失败。挂起的 RPC 可能会继续运行,直到应用程序取消它们。通道可能会进入此状态,因为应用程序明确请求关闭,或者在尝试连接通信期间发生不可恢复的错误。(截至 2015 年 6 月 12 日,不存在归类为不可恢复的已知错误(连接或通信时)。)进入此状态的通道永远不会离开此状态。
以下方法可以获取客户端地址
// 获取客户端地址
func getClietIP(ctx context.Context) (string, error) {
pr, ok := peer.FromContext(ctx)
if !ok {
return "", fmt.Errorf("invoke FromContext() failed")
}
if pr.Addr == net.Addr(nil) {
return "", fmt.Errorf("peer.Addr is nil")
}
return pr.Addr.String(), nil
}
完整实例代码地址:
https://github.com/mangenotwork/man/tree/master/core/grpc_1