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  }