github.com/lingyao2333/mo-zero@v1.4.1/zrpc/internal/client.go (about) 1 package internal 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/lingyao2333/mo-zero/zrpc/internal/balancer/p2c" 11 "github.com/lingyao2333/mo-zero/zrpc/internal/clientinterceptors" 12 "github.com/lingyao2333/mo-zero/zrpc/resolver" 13 "google.golang.org/grpc" 14 "google.golang.org/grpc/credentials" 15 "google.golang.org/grpc/credentials/insecure" 16 ) 17 18 const ( 19 dialTimeout = time.Second * 3 20 separator = '/' 21 ) 22 23 func init() { 24 resolver.Register() 25 } 26 27 type ( 28 // Client interface wraps the Conn method. 29 Client interface { 30 Conn() *grpc.ClientConn 31 } 32 33 // A ClientOptions is a client options. 34 ClientOptions struct { 35 NonBlock bool 36 Timeout time.Duration 37 Secure bool 38 DialOptions []grpc.DialOption 39 } 40 41 // ClientOption defines the method to customize a ClientOptions. 42 ClientOption func(options *ClientOptions) 43 44 client struct { 45 conn *grpc.ClientConn 46 } 47 ) 48 49 // NewClient returns a Client. 50 func NewClient(target string, opts ...ClientOption) (Client, error) { 51 var cli client 52 53 svcCfg := fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, p2c.Name) 54 balancerOpt := WithDialOption(grpc.WithDefaultServiceConfig(svcCfg)) 55 opts = append([]ClientOption{balancerOpt}, opts...) 56 if err := cli.dial(target, opts...); err != nil { 57 return nil, err 58 } 59 60 return &cli, nil 61 } 62 63 func (c *client) Conn() *grpc.ClientConn { 64 return c.conn 65 } 66 67 func (c *client) buildDialOptions(opts ...ClientOption) []grpc.DialOption { 68 var cliOpts ClientOptions 69 for _, opt := range opts { 70 opt(&cliOpts) 71 } 72 73 var options []grpc.DialOption 74 if !cliOpts.Secure { 75 options = append([]grpc.DialOption(nil), grpc.WithTransportCredentials(insecure.NewCredentials())) 76 } 77 78 if !cliOpts.NonBlock { 79 options = append(options, grpc.WithBlock()) 80 } 81 82 options = append(options, 83 WithUnaryClientInterceptors( 84 clientinterceptors.UnaryTracingInterceptor, 85 clientinterceptors.DurationInterceptor, 86 clientinterceptors.PrometheusInterceptor, 87 clientinterceptors.BreakerInterceptor, 88 clientinterceptors.TimeoutInterceptor(cliOpts.Timeout), 89 ), 90 WithStreamClientInterceptors( 91 clientinterceptors.StreamTracingInterceptor, 92 ), 93 ) 94 95 return append(options, cliOpts.DialOptions...) 96 } 97 98 func (c *client) dial(server string, opts ...ClientOption) error { 99 options := c.buildDialOptions(opts...) 100 timeCtx, cancel := context.WithTimeout(context.Background(), dialTimeout) 101 defer cancel() 102 conn, err := grpc.DialContext(timeCtx, server, options...) 103 if err != nil { 104 service := server 105 if errors.Is(err, context.DeadlineExceeded) { 106 pos := strings.LastIndexByte(server, separator) 107 // len(server) - 1 is the index of last char 108 if 0 < pos && pos < len(server)-1 { 109 service = server[pos+1:] 110 } 111 } 112 return fmt.Errorf("rpc dial: %s, error: %s, make sure rpc service %q is already started", 113 server, err.Error(), service) 114 } 115 116 c.conn = conn 117 return nil 118 } 119 120 // WithDialOption returns a func to customize a ClientOptions with given dial option. 121 func WithDialOption(opt grpc.DialOption) ClientOption { 122 return func(options *ClientOptions) { 123 options.DialOptions = append(options.DialOptions, opt) 124 } 125 } 126 127 // WithNonBlock sets the dialing to be nonblock. 128 func WithNonBlock() ClientOption { 129 return func(options *ClientOptions) { 130 options.NonBlock = true 131 } 132 } 133 134 // WithStreamClientInterceptor returns a func to customize a ClientOptions with given interceptor. 135 func WithStreamClientInterceptor(interceptor grpc.StreamClientInterceptor) ClientOption { 136 return func(options *ClientOptions) { 137 options.DialOptions = append(options.DialOptions, WithStreamClientInterceptors(interceptor)) 138 } 139 } 140 141 // WithTimeout returns a func to customize a ClientOptions with given timeout. 142 func WithTimeout(timeout time.Duration) ClientOption { 143 return func(options *ClientOptions) { 144 options.Timeout = timeout 145 } 146 } 147 148 // WithTransportCredentials return a func to make the gRPC calls secured with given credentials. 149 func WithTransportCredentials(creds credentials.TransportCredentials) ClientOption { 150 return func(options *ClientOptions) { 151 options.Secure = true 152 options.DialOptions = append(options.DialOptions, grpc.WithTransportCredentials(creds)) 153 } 154 } 155 156 // WithUnaryClientInterceptor returns a func to customize a ClientOptions with given interceptor. 157 func WithUnaryClientInterceptor(interceptor grpc.UnaryClientInterceptor) ClientOption { 158 return func(options *ClientOptions) { 159 options.DialOptions = append(options.DialOptions, WithUnaryClientInterceptors(interceptor)) 160 } 161 }