trpc.group/trpc-go/trpc-go@v1.0.3/client/config.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package client
    15  
    16  import (
    17  	"fmt"
    18  	"sync"
    19  	"time"
    20  
    21  	"trpc.group/trpc-go/trpc-go/codec"
    22  	"trpc.group/trpc-go/trpc-go/config"
    23  	"trpc.group/trpc-go/trpc-go/filter"
    24  	icodec "trpc.group/trpc-go/trpc-go/internal/codec"
    25  	"trpc.group/trpc-go/trpc-go/naming/circuitbreaker"
    26  	"trpc.group/trpc-go/trpc-go/naming/discovery"
    27  	"trpc.group/trpc-go/trpc-go/naming/loadbalance"
    28  	"trpc.group/trpc-go/trpc-go/naming/selector"
    29  	"trpc.group/trpc-go/trpc-go/naming/servicerouter"
    30  	"trpc.group/trpc-go/trpc-go/transport"
    31  )
    32  
    33  // BackendConfig defines the configuration needed to call the backend service.
    34  // It's empty by default and can be replaced.
    35  type BackendConfig struct {
    36  	// Callee is the name of the backend service.
    37  	// The config file uses it as the key to set the parameters.
    38  	// Usually, it is the proto name of the callee service defined in proto stub file,
    39  	// and it is the same as ServiceName below.
    40  	Callee      string `yaml:"callee"`   // Name of the backend service.
    41  	ServiceName string `yaml:"name"`     // Backend service name.
    42  	EnvName     string `yaml:"env_name"` // Env name of the callee.
    43  	SetName     string `yaml:"set_name"` // "Set" name of the callee.
    44  
    45  	// DisableServiceRouter, despite its inherent inappropriate and vague nomenclature,
    46  	// is an option for naming service that denotes the de-facto meaning of disabling
    47  	// out-rule routing for the source service.
    48  	DisableServiceRouter bool              `yaml:"disable_servicerouter"`
    49  	Namespace            string            `yaml:"namespace"`       // Namespace of the callee: Production/Development.
    50  	CalleeMetadata       map[string]string `yaml:"callee_metadata"` // Set callee metadata.
    51  
    52  	Target   string `yaml:"target"`   // Polaris by default, generally no need to configure this.
    53  	Password string `yaml:"password"` // Password for authentication.
    54  
    55  	// Naming service four swordsmen.
    56  	// Discovery.List => ServiceRouter.Filter => Loadbalancer.Select => Circuitbreaker.Report
    57  	Discovery      string `yaml:"discovery"`      // Discovery for the backend service.
    58  	ServiceRouter  string `yaml:"servicerouter"`  // Service router for the backend service.
    59  	Loadbalance    string `yaml:"loadbalance"`    // Load balancing algorithm.
    60  	Circuitbreaker string `yaml:"circuitbreaker"` // Circuit breaker configuration.
    61  
    62  	Network   string `yaml:"network"`   // Transport protocol type: tcp or udp.
    63  	Timeout   int    `yaml:"timeout"`   // Client timeout in milliseconds.
    64  	Protocol  string `yaml:"protocol"`  // Business protocol type: trpc, http, http_no_protocol, etc.
    65  	Transport string `yaml:"transport"` // Transport type.
    66  
    67  	// Serialization type. Use a pointer to check if it has been set (0 means pb).
    68  	Serialization *int `yaml:"serialization"`
    69  	Compression   int  `yaml:"compression"` // Compression type.
    70  
    71  	TLSKey  string `yaml:"tls_key"`  // Client TLS key.
    72  	TLSCert string `yaml:"tls_cert"` // Client TLS certificate.
    73  	// CA certificate used to validate the server cert when calling a TLS service (e.g., an HTTPS server).
    74  	CACert string `yaml:"ca_cert"`
    75  	// Server name used to validate the server (default: hostname) when calling an HTTPS server.
    76  	TLSServerName string `yaml:"tls_server_name"`
    77  
    78  	Filter       []string `yaml:"filter"`        // Filters for the backend service.
    79  	StreamFilter []string `yaml:"stream_filter"` // Stream filters for the backend service.
    80  
    81  	// Report any error to the selector if this value is true.
    82  	ReportAnyErrToSelector bool `yaml:"report_any_err_to_selector"`
    83  }
    84  
    85  // genOptions generates options for each RPC from BackendConfig.
    86  func (cfg *BackendConfig) genOptions() (*Options, error) {
    87  	opts := NewOptions()
    88  	if err := cfg.setNamingOptions(opts); err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	if cfg.Timeout > 0 {
    93  		opts.Timeout = time.Duration(cfg.Timeout) * time.Millisecond
    94  	}
    95  	if cfg.Serialization != nil {
    96  		opts.SerializationType = *cfg.Serialization
    97  	}
    98  	if icodec.IsValidCompressType(cfg.Compression) && cfg.Compression != codec.CompressTypeNoop {
    99  		opts.CompressType = cfg.Compression
   100  	}
   101  
   102  	// Reset the transport to check if the user has specified any transport.
   103  	opts.Transport = nil
   104  	WithTransport(transport.GetClientTransport(cfg.Transport))(opts)
   105  	WithStreamTransport(transport.GetClientStreamTransport(cfg.Transport))(opts)
   106  	WithProtocol(cfg.Protocol)(opts)
   107  	WithNetwork(cfg.Network)(opts)
   108  	opts.Transport = attemptSwitchingTransport(opts)
   109  	WithPassword(cfg.Password)(opts)
   110  	WithTLS(cfg.TLSCert, cfg.TLSKey, cfg.CACert, cfg.TLSServerName)(opts)
   111  	if cfg.Protocol != "" && opts.Codec == nil {
   112  		return nil, fmt.Errorf("codec %s not exists", cfg.Protocol)
   113  	}
   114  	for _, name := range cfg.Filter {
   115  		f := filter.GetClient(name)
   116  		if f == nil {
   117  			if name == DefaultSelectorFilterName {
   118  				// selector filter is configured
   119  				// need to set selector filter pos
   120  				opts.selectorFilterPosFixed = true
   121  				opts.Filters = append(opts.Filters, selectorFilter)
   122  				opts.FilterNames = append(opts.FilterNames, name)
   123  				continue
   124  			}
   125  			return nil, fmt.Errorf("client config: filter %s no registered, do not configure", name)
   126  		}
   127  		opts.Filters = append(opts.Filters, f)
   128  		opts.FilterNames = append(opts.FilterNames, name)
   129  	}
   130  	for _, name := range cfg.StreamFilter {
   131  		f := GetStreamFilter(name)
   132  		if f == nil {
   133  			return nil, fmt.Errorf("client config: stream filter %s no registered, do not configure", name)
   134  		}
   135  		opts.StreamFilters = append(opts.StreamFilters, f)
   136  	}
   137  	opts.rebuildSliceCapacity()
   138  	return opts, nil
   139  }
   140  
   141  // setNamingOptions sets naming related options.
   142  func (cfg *BackendConfig) setNamingOptions(opts *Options) error {
   143  	if cfg.ServiceName != "" {
   144  		opts.ServiceName = cfg.ServiceName
   145  	}
   146  	if cfg.Namespace != "" {
   147  		opts.SelectOptions = append(opts.SelectOptions, selector.WithNamespace(cfg.Namespace))
   148  	}
   149  	if cfg.EnvName != "" {
   150  		opts.SelectOptions = append(opts.SelectOptions, selector.WithDestinationEnvName(cfg.EnvName))
   151  	}
   152  	if cfg.SetName != "" {
   153  		opts.SelectOptions = append(opts.SelectOptions, selector.WithDestinationSetName(cfg.SetName))
   154  	}
   155  	if cfg.DisableServiceRouter {
   156  		opts.SelectOptions = append(opts.SelectOptions, selector.WithDisableServiceRouter())
   157  		opts.DisableServiceRouter = true
   158  	}
   159  	if cfg.ReportAnyErrToSelector {
   160  		opts.shouldErrReportToSelector = func(err error) bool { return true }
   161  	}
   162  	for key, val := range cfg.CalleeMetadata {
   163  		opts.SelectOptions = append(opts.SelectOptions, selector.WithDestinationMetadata(key, val))
   164  	}
   165  	if cfg.Target != "" {
   166  		opts.Target = cfg.Target
   167  		return opts.parseTarget()
   168  	}
   169  	if cfg.Discovery != "" {
   170  		d := discovery.Get(cfg.Discovery)
   171  		if d == nil {
   172  			return fmt.Errorf("client config: discovery %s no registered", cfg.Discovery)
   173  		}
   174  		opts.SelectOptions = append(opts.SelectOptions, selector.WithDiscovery(d))
   175  	}
   176  	if cfg.ServiceRouter != "" {
   177  		r := servicerouter.Get(cfg.ServiceRouter)
   178  		if r == nil {
   179  			return fmt.Errorf("client config: servicerouter %s no registered", cfg.ServiceRouter)
   180  		}
   181  		opts.SelectOptions = append(opts.SelectOptions, selector.WithServiceRouter(r))
   182  	}
   183  	if cfg.Loadbalance != "" {
   184  		balancer := loadbalance.Get(cfg.Loadbalance)
   185  		if balancer == nil {
   186  			return fmt.Errorf("client config: balancer %s no registered", cfg.Loadbalance)
   187  		}
   188  		opts.SelectOptions = append(opts.SelectOptions, selector.WithLoadBalancer(balancer))
   189  	}
   190  	if cfg.Circuitbreaker != "" {
   191  		cb := circuitbreaker.Get(cfg.Circuitbreaker)
   192  		if cb == nil {
   193  			return fmt.Errorf("client config: circuitbreaker %s no registered", cfg.Circuitbreaker)
   194  		}
   195  		opts.SelectOptions = append(opts.SelectOptions, selector.WithCircuitBreaker(cb))
   196  	}
   197  	return nil
   198  }
   199  
   200  var (
   201  	// DefaultSelectorFilterName is the default name of selector filter.
   202  	// It can be modified if conflict exists.
   203  	DefaultSelectorFilterName = "selector"
   204  
   205  	defaultBackendConf = &BackendConfig{
   206  		Network:  "tcp",
   207  		Protocol: "trpc",
   208  	}
   209  	defaultBackendOptions *Options
   210  
   211  	mutex   sync.RWMutex
   212  	configs = make(map[string]*configsWithFallback) // Key: callee.
   213  	options = make(map[string]*optionsWithFallback) // Key: callee.
   214  )
   215  
   216  type configsWithFallback struct {
   217  	fallback     *BackendConfig
   218  	serviceNames map[string]*BackendConfig // Key: service name.
   219  }
   220  
   221  type optionsWithFallback struct {
   222  	fallback     *Options
   223  	serviceNames map[string]*Options // Key: service name.
   224  }
   225  
   226  // getDefaultOptions returns default options.
   227  func getDefaultOptions() *Options {
   228  	mutex.RLock()
   229  	opts := defaultBackendOptions
   230  	mutex.RUnlock()
   231  	if opts != nil {
   232  		return opts
   233  	}
   234  	mutex.Lock()
   235  	if defaultBackendOptions != nil {
   236  		mutex.Unlock()
   237  		return defaultBackendOptions
   238  	}
   239  	opts, err := defaultBackendConf.genOptions()
   240  	if err != nil {
   241  		defaultBackendOptions = NewOptions()
   242  	} else {
   243  		defaultBackendOptions = opts
   244  	}
   245  	mutex.Unlock()
   246  	return defaultBackendOptions
   247  }
   248  
   249  // DefaultClientConfig returns the default client config.
   250  //
   251  // Note: if multiple client configs with same callee and different service name
   252  // exist in trpc_go.yaml, this function will only return the last config for
   253  // the same callee key.
   254  func DefaultClientConfig() map[string]*BackendConfig {
   255  	mutex.RLock()
   256  	c := make(map[string]*BackendConfig)
   257  	for k, v := range configs {
   258  		c[k] = v.fallback
   259  	}
   260  	mutex.RUnlock()
   261  	return c
   262  }
   263  
   264  // LoadClientConfig loads client config by path.
   265  func LoadClientConfig(path string, opts ...config.LoadOption) error {
   266  	conf, err := config.DefaultConfigLoader.Load(path, opts...)
   267  	if err != nil {
   268  		return err
   269  	}
   270  	tmp := make(map[string]*BackendConfig)
   271  	if err := conf.Unmarshal(tmp); err != nil {
   272  		return err
   273  	}
   274  	RegisterConfig(tmp)
   275  	return nil
   276  }
   277  
   278  // Config returns BackendConfig by callee service name.
   279  func Config(callee string) *BackendConfig {
   280  	mutex.RLock()
   281  	if len(configs) == 0 {
   282  		mutex.RUnlock()
   283  		return defaultBackendConf
   284  	}
   285  	conf, ok := configs[callee]
   286  	if !ok {
   287  		conf, ok = configs["*"]
   288  		if !ok {
   289  			mutex.RUnlock()
   290  			return defaultBackendConf
   291  		}
   292  	}
   293  	mutex.RUnlock()
   294  	return conf.fallback
   295  }
   296  
   297  func getOptionsByCalleeAndUserOptions(callee string, opt ...Option) *Options {
   298  	// Each RPC call uses new options to ensure thread safety.
   299  	inputOpts := &Options{}
   300  	for _, o := range opt {
   301  		o(inputOpts)
   302  	}
   303  	if inputOpts.ServiceName != "" {
   304  		// If user passes in a service name option, use callee and service name
   305  		// as a combined key to retrieve client config.
   306  		return getOptionsByCalleeAndServiceName(callee, inputOpts.ServiceName)
   307  	}
   308  	// Otherwise use callee only.
   309  	return getOptions(callee)
   310  }
   311  
   312  // getOptions returns Options by callee service name.
   313  func getOptions(callee string) *Options {
   314  	mutex.RLock()
   315  	if len(options) == 0 {
   316  		mutex.RUnlock()
   317  		return getDefaultOptions()
   318  	}
   319  	opts, ok := options[callee]
   320  	if !ok {
   321  		opts, ok = options["*"]
   322  		if !ok {
   323  			mutex.RUnlock()
   324  			return getDefaultOptions()
   325  		}
   326  	}
   327  	mutex.RUnlock()
   328  	return opts.fallback
   329  }
   330  
   331  func getOptionsByCalleeAndServiceName(callee, serviceName string) *Options {
   332  	mutex.RLock()
   333  	serviceOptions, ok := options[callee]
   334  	if !ok {
   335  		mutex.RUnlock()
   336  		return getOptions(callee) // Fallback to use callee as the single key.
   337  	}
   338  	opts, ok := serviceOptions.serviceNames[serviceName]
   339  	if !ok {
   340  		mutex.RUnlock()
   341  		return getOptions(callee) // Fallback to use callee as the single key.
   342  	}
   343  	mutex.RUnlock()
   344  	return opts
   345  }
   346  
   347  // RegisterConfig is called to replace the global backend config,
   348  // allowing updating backend config regularly.
   349  func RegisterConfig(conf map[string]*BackendConfig) error {
   350  	opts := make(map[string]*optionsWithFallback)
   351  	confs := make(map[string]*configsWithFallback)
   352  	for key, cfg := range conf {
   353  		o, err := cfg.genOptions()
   354  		if err != nil {
   355  			return err
   356  		}
   357  		opts[key] = &optionsWithFallback{
   358  			fallback:     o,
   359  			serviceNames: make(map[string]*Options),
   360  		}
   361  		opts[key].serviceNames[cfg.ServiceName] = o
   362  		confs[key] = &configsWithFallback{
   363  			fallback:     cfg,
   364  			serviceNames: make(map[string]*BackendConfig),
   365  		}
   366  		confs[key].serviceNames[cfg.ServiceName] = cfg
   367  	}
   368  	mutex.Lock()
   369  	options = opts
   370  	configs = confs
   371  	mutex.Unlock()
   372  	return nil
   373  }
   374  
   375  // RegisterClientConfig is called to replace backend config of single callee service by name.
   376  func RegisterClientConfig(callee string, conf *BackendConfig) error {
   377  	if callee == "*" {
   378  		// Reset the callee and service name to enable wildcard matching.
   379  		conf.Callee = ""
   380  		conf.ServiceName = ""
   381  	}
   382  	opts, err := conf.genOptions()
   383  	if err != nil {
   384  		return err
   385  	}
   386  	mutex.Lock()
   387  	if opt, ok := options[callee]; !ok || opt == nil {
   388  		options[callee] = &optionsWithFallback{
   389  			serviceNames: make(map[string]*Options),
   390  		}
   391  		configs[callee] = &configsWithFallback{
   392  			serviceNames: make(map[string]*BackendConfig),
   393  		}
   394  	}
   395  	options[callee].fallback = opts
   396  	configs[callee].fallback = conf
   397  	options[callee].serviceNames[conf.ServiceName] = opts
   398  	configs[callee].serviceNames[conf.ServiceName] = conf
   399  	mutex.Unlock()
   400  	return nil
   401  }