github.com/cloudwego/kitex@v0.9.0/client/option.go (about)

     1  /*
     2   * Copyright 2021 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package client
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"reflect"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/cloudwego/kitex/internal/client"
    28  	"github.com/cloudwego/kitex/pkg/circuitbreak"
    29  	"github.com/cloudwego/kitex/pkg/connpool"
    30  	"github.com/cloudwego/kitex/pkg/discovery"
    31  	"github.com/cloudwego/kitex/pkg/endpoint"
    32  	"github.com/cloudwego/kitex/pkg/fallback"
    33  	"github.com/cloudwego/kitex/pkg/http"
    34  	"github.com/cloudwego/kitex/pkg/klog"
    35  	"github.com/cloudwego/kitex/pkg/loadbalance"
    36  	"github.com/cloudwego/kitex/pkg/loadbalance/lbcache"
    37  	"github.com/cloudwego/kitex/pkg/remote"
    38  	"github.com/cloudwego/kitex/pkg/remote/trans/netpollmux"
    39  	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc"
    40  	"github.com/cloudwego/kitex/pkg/retry"
    41  	"github.com/cloudwego/kitex/pkg/rpcinfo"
    42  	"github.com/cloudwego/kitex/pkg/stats"
    43  	"github.com/cloudwego/kitex/pkg/utils"
    44  	"github.com/cloudwego/kitex/pkg/warmup"
    45  	"github.com/cloudwego/kitex/pkg/xds"
    46  	"github.com/cloudwego/kitex/transport"
    47  )
    48  
    49  // Option is the only way to config client.
    50  type Option = client.Option
    51  
    52  // Options is used to initialize a client.
    53  type Options = client.Options
    54  
    55  // A Suite is a collection of Options. It is useful to assemble multiple associated
    56  // Options as a single one to keep the order or presence in a desired manner.
    57  type Suite interface {
    58  	Options() []Option
    59  }
    60  
    61  // WithTransportProtocol sets the transport protocol for client.
    62  func WithTransportProtocol(tp transport.Protocol) Option {
    63  	return Option{F: func(o *client.Options, di *utils.Slice) {
    64  		tpName := tp.String()
    65  		if tpName == transport.Unknown {
    66  			panic(fmt.Errorf("WithTransportProtocol: invalid '%v'", tp))
    67  		}
    68  		di.Push(fmt.Sprintf("WithTransportProtocol(%s)", tpName))
    69  		rpcinfo.AsMutableRPCConfig(o.Configs).SetTransportProtocol(tp)
    70  	}}
    71  }
    72  
    73  // WithSuite adds an option suite for client.
    74  func WithSuite(suite Suite) Option {
    75  	return Option{F: func(o *client.Options, di *utils.Slice) {
    76  		var nested struct {
    77  			Suite   string
    78  			Options utils.Slice
    79  		}
    80  		nested.Suite = fmt.Sprintf("%T(%+v)", suite, suite)
    81  
    82  		for _, op := range suite.Options() {
    83  			op.F(o, &nested.Options)
    84  		}
    85  		di.Push(nested)
    86  	}}
    87  }
    88  
    89  // WithMiddleware adds middleware for client to handle request.
    90  func WithMiddleware(mw endpoint.Middleware) Option {
    91  	mwb := func(ctx context.Context) endpoint.Middleware {
    92  		return mw
    93  	}
    94  	return Option{F: func(o *client.Options, di *utils.Slice) {
    95  		di.Push(fmt.Sprintf("WithMiddleware(%+v)", utils.GetFuncName(mw)))
    96  		o.MWBs = append(o.MWBs, mwb)
    97  	}}
    98  }
    99  
   100  // WithMiddlewareBuilder adds middleware that depend on context for client to handle request
   101  func WithMiddlewareBuilder(mwb endpoint.MiddlewareBuilder) Option {
   102  	return Option{F: func(o *client.Options, di *utils.Slice) {
   103  		di.Push(fmt.Sprintf("WithMiddlewareBuilder(%+v)", utils.GetFuncName(mwb)))
   104  		o.MWBs = append(o.MWBs, mwb)
   105  	}}
   106  }
   107  
   108  // WithInstanceMW adds middleware for client to handle request after service discovery and loadbalance process.
   109  func WithInstanceMW(mw endpoint.Middleware) Option {
   110  	imwb := func(ctx context.Context) endpoint.Middleware {
   111  		return mw
   112  	}
   113  	return Option{F: func(o *client.Options, di *utils.Slice) {
   114  		di.Push(fmt.Sprintf("WithInstanceMW(%+v)", utils.GetFuncName(mw)))
   115  		o.IMWBs = append(o.IMWBs, imwb)
   116  	}}
   117  }
   118  
   119  // WithDestService specifies the name of target service.
   120  func WithDestService(svr string) Option {
   121  	return Option{F: func(o *client.Options, di *utils.Slice) {
   122  		o.Once.OnceOrPanic()
   123  		di.Push(fmt.Sprintf("WithDestService(%s)", svr))
   124  		o.Svr.ServiceName = svr
   125  	}}
   126  }
   127  
   128  // WithHostPorts specifies the target instance addresses when doing service discovery.
   129  // It overwrites the results from the Resolver.
   130  func WithHostPorts(hostports ...string) Option {
   131  	return Option{F: func(o *client.Options, di *utils.Slice) {
   132  		di.Push(fmt.Sprintf("WithHostPorts(%v)", hostports))
   133  
   134  		var ins []discovery.Instance
   135  		for _, hp := range hostports {
   136  			if _, err := net.ResolveTCPAddr("tcp", hp); err == nil {
   137  				ins = append(ins, discovery.NewInstance("tcp", hp, discovery.DefaultWeight, nil))
   138  				continue
   139  			}
   140  			if _, err := net.ResolveUnixAddr("unix", hp); err == nil {
   141  				ins = append(ins, discovery.NewInstance("unix", hp, discovery.DefaultWeight, nil))
   142  				continue
   143  			}
   144  			panic(fmt.Errorf("WithHostPorts: invalid '%s'", hp))
   145  		}
   146  
   147  		if len(ins) == 0 {
   148  			panic("WithHostPorts() requires at least one argument")
   149  		}
   150  
   151  		o.Targets = strings.Join(hostports, ",")
   152  		o.Resolver = &discovery.SynthesizedResolver{
   153  			ResolveFunc: func(ctx context.Context, key string) (discovery.Result, error) {
   154  				return discovery.Result{
   155  					Cacheable: true,
   156  					CacheKey:  "fixed",
   157  					Instances: ins,
   158  				}, nil
   159  			},
   160  			NameFunc: func() string { return o.Targets },
   161  			TargetFunc: func(ctx context.Context, target rpcinfo.EndpointInfo) string {
   162  				return o.Targets
   163  			},
   164  		}
   165  	}}
   166  }
   167  
   168  // WithResolver provides the Resolver for kitex client.
   169  func WithResolver(r discovery.Resolver) Option {
   170  	return Option{F: func(o *client.Options, di *utils.Slice) {
   171  		di.Push(fmt.Sprintf("WithResolver(%T)", r))
   172  
   173  		o.Resolver = r
   174  	}}
   175  }
   176  
   177  // WithHTTPResolver specifies resolver for url (which specified by WithURL).
   178  func WithHTTPResolver(r http.Resolver) Option {
   179  	return Option{F: func(o *client.Options, di *utils.Slice) {
   180  		di.Push(fmt.Sprintf("WithHTTPResolver(%T)", r))
   181  
   182  		o.HTTPResolver = r
   183  	}}
   184  }
   185  
   186  // WithShortConnection forces kitex to close connection after each call is finished.
   187  func WithShortConnection() Option {
   188  	return Option{F: func(o *client.Options, di *utils.Slice) {
   189  		di.Push("WithShortConnection")
   190  
   191  		o.PoolCfg = new(connpool.IdleConfig)
   192  	}}
   193  }
   194  
   195  // WithLongConnection enables long connection with kitex's built-in pooling implementation.
   196  func WithLongConnection(cfg connpool.IdleConfig) Option {
   197  	return Option{F: func(o *client.Options, di *utils.Slice) {
   198  		di.Push(fmt.Sprintf("WithLongConnection(%+v)", cfg))
   199  
   200  		o.PoolCfg = connpool.CheckPoolConfig(cfg)
   201  	}}
   202  }
   203  
   204  // WithMuxConnection specifies the transport type to be mux.
   205  func WithMuxConnection(connNum int) Option {
   206  	return Option{F: func(o *client.Options, di *utils.Slice) {
   207  		di.Push("WithMuxConnection")
   208  
   209  		o.RemoteOpt.ConnPool = netpollmux.NewMuxConnPool(connNum)
   210  		WithTransHandlerFactory(netpollmux.NewCliTransHandlerFactory()).F(o, di)
   211  		rpcinfo.AsMutableRPCConfig(o.Configs).SetTransportProtocol(transport.TTHeader)
   212  	}}
   213  }
   214  
   215  // WithLogger sets the Logger for kitex client.
   216  // Deprecated: client uses the global klog.DefaultLogger.
   217  func WithLogger(logger klog.FormatLogger) Option {
   218  	panic("client.WithLogger is deprecated")
   219  }
   220  
   221  // WithLoadBalancer sets the loadbalancer for client.
   222  func WithLoadBalancer(lb loadbalance.Loadbalancer, opts ...*lbcache.Options) Option {
   223  	return Option{F: func(o *client.Options, di *utils.Slice) {
   224  		di.Push(fmt.Sprintf("WithLoadBalancer(%+v, %+v)", lb, opts))
   225  		o.Balancer = lb
   226  		if len(opts) > 0 {
   227  			o.BalancerCacheOpt = opts[0]
   228  		}
   229  	}}
   230  }
   231  
   232  // WithRPCTimeout specifies the RPC timeout.
   233  func WithRPCTimeout(d time.Duration) Option {
   234  	return Option{F: func(o *client.Options, di *utils.Slice) {
   235  		di.Push(fmt.Sprintf("WithRPCTimeout(%dms)", d.Milliseconds()))
   236  
   237  		rpcinfo.AsMutableRPCConfig(o.Configs).SetRPCTimeout(d)
   238  		o.Locks.Bits |= rpcinfo.BitRPCTimeout
   239  	}}
   240  }
   241  
   242  // WithConnectTimeout specifies the connection timeout.
   243  func WithConnectTimeout(d time.Duration) Option {
   244  	return Option{F: func(o *client.Options, di *utils.Slice) {
   245  		di.Push(fmt.Sprintf("WithConnectTimeout(%dms)", d.Milliseconds()))
   246  
   247  		rpcinfo.AsMutableRPCConfig(o.Configs).SetConnectTimeout(d)
   248  		o.Locks.Bits |= rpcinfo.BitConnectTimeout
   249  	}}
   250  }
   251  
   252  // WithTimeoutProvider adds a TimeoutProvider to the client.
   253  // Note that the timeout settings provided by the TimeoutProvider
   254  // will be applied before the other timeout options in this package
   255  // and those in the callopt package. Thus it can not modify the
   256  // timeouts set by WithRPCTimeout or WithConnectTimeout.
   257  func WithTimeoutProvider(p rpcinfo.TimeoutProvider) Option {
   258  	return Option{F: func(o *client.Options, di *utils.Slice) {
   259  		di.Push(fmt.Sprintf("WithTimeoutProvider(%T(%+v))", p, p))
   260  		o.Timeouts = p
   261  	}}
   262  }
   263  
   264  // WithTag sets the customize tag for service discovery, eg: idc, cluster.
   265  func WithTag(key, val string) Option {
   266  	return Option{F: func(o *client.Options, di *utils.Slice) {
   267  		di.Push(fmt.Sprintf("WithTag(%s=%s)", key, val))
   268  
   269  		o.Svr.Tags[key] = val
   270  		o.Locks.Tags[key] = struct{}{}
   271  	}}
   272  }
   273  
   274  // WithTracer adds a tracer to client.
   275  func WithTracer(c stats.Tracer) Option {
   276  	return Option{F: func(o *client.Options, di *utils.Slice) {
   277  		di.Push(fmt.Sprintf("WithTracer(%T{%+v})", c, c))
   278  
   279  		if o.TracerCtl == nil {
   280  			o.TracerCtl = &rpcinfo.TraceController{}
   281  		}
   282  		o.TracerCtl.Append(c)
   283  	}}
   284  }
   285  
   286  // WithStatsLevel sets the stats level for client.
   287  func WithStatsLevel(level stats.Level) Option {
   288  	return Option{F: func(o *client.Options, di *utils.Slice) {
   289  		di.Push(fmt.Sprintf("WithStatsLevel(%+v)", level))
   290  		l := level
   291  		o.StatsLevel = &l
   292  	}}
   293  }
   294  
   295  // WithCodec to set a codec that handle other protocols which not support by kitex
   296  func WithCodec(c remote.Codec) Option {
   297  	return Option{F: func(o *client.Options, di *utils.Slice) {
   298  		di.Push(fmt.Sprintf("WithCodec(%+v)", c))
   299  
   300  		o.RemoteOpt.Codec = c
   301  	}}
   302  }
   303  
   304  // WithPayloadCodec to set a payloadCodec that handle other payload which not support by kitex
   305  func WithPayloadCodec(c remote.PayloadCodec) Option {
   306  	return Option{F: func(o *client.Options, di *utils.Slice) {
   307  		di.Push(fmt.Sprintf("WithPayloadCodec(%+v)", c))
   308  
   309  		o.RemoteOpt.PayloadCodec = c
   310  	}}
   311  }
   312  
   313  // WithConnReporterEnabled to enable reporting connection pool stats.
   314  func WithConnReporterEnabled() Option {
   315  	return Option{F: func(o *client.Options, di *utils.Slice) {
   316  		di.Push("WithConnReporterEnabled()")
   317  
   318  		o.RemoteOpt.EnableConnPoolReporter = true
   319  	}}
   320  }
   321  
   322  // WithFailureRetry sets the failure retry policy for client, it will take effect for all methods.
   323  func WithFailureRetry(p *retry.FailurePolicy) Option {
   324  	return Option{F: func(o *client.Options, di *utils.Slice) {
   325  		if p == nil {
   326  			return
   327  		}
   328  		di.Push(fmt.Sprintf("WithFailureRetry(%+v)", *p))
   329  		if o.RetryMethodPolicies == nil {
   330  			o.RetryMethodPolicies = make(map[string]retry.Policy)
   331  		}
   332  		if o.RetryMethodPolicies[retry.Wildcard].BackupPolicy != nil {
   333  			panic("BackupPolicy has been setup, cannot support Failure Retry at same time")
   334  		}
   335  		o.RetryMethodPolicies[retry.Wildcard] = retry.BuildFailurePolicy(p)
   336  	}}
   337  }
   338  
   339  // WithBackupRequest sets the backup request policy for client, it will take effect for all methods.
   340  func WithBackupRequest(p *retry.BackupPolicy) Option {
   341  	return Option{F: func(o *client.Options, di *utils.Slice) {
   342  		if p == nil {
   343  			return
   344  		}
   345  		di.Push(fmt.Sprintf("WithBackupRequest(%+v)", *p))
   346  		if o.RetryMethodPolicies == nil {
   347  			o.RetryMethodPolicies = make(map[string]retry.Policy)
   348  		}
   349  		if o.RetryMethodPolicies[retry.Wildcard].FailurePolicy != nil {
   350  			panic("BackupPolicy has been setup, cannot support Failure Retry at same time")
   351  		}
   352  		o.RetryMethodPolicies[retry.Wildcard] = retry.BuildBackupRequest(p)
   353  	}}
   354  }
   355  
   356  // WithRetryMethodPolicies sets the retry policy for method.
   357  // The priority is higher than WithFailureRetry and WithBackupRequest. Only the methods which are not included by
   358  // this config will use the policy that is configured by WithFailureRetry or WithBackupRequest .
   359  // FailureRetry and BackupRequest can be set for different method at same time.
   360  // Notice: method name is case-sensitive, it should be same with the definition in IDL.
   361  func WithRetryMethodPolicies(mp map[string]retry.Policy) Option {
   362  	return Option{F: func(o *client.Options, di *utils.Slice) {
   363  		if mp == nil {
   364  			return
   365  		}
   366  		di.Push(fmt.Sprintf("WithRetryMethodPolicies(%+v)", mp))
   367  		if o.RetryMethodPolicies == nil {
   368  			o.RetryMethodPolicies = make(map[string]retry.Policy)
   369  		}
   370  		wildcardCfg := o.RetryMethodPolicies[retry.Wildcard]
   371  		o.RetryMethodPolicies = mp
   372  		if wildcardCfg.Enable && !mp[retry.Wildcard].Enable {
   373  			// if there is enabled wildcard config before, keep it
   374  			o.RetryMethodPolicies[retry.Wildcard] = wildcardCfg
   375  		}
   376  	}}
   377  }
   378  
   379  // WithSpecifiedResultRetry is used with FailureRetry.
   380  // When you enable FailureRetry and want to retry with the specified error or response, you can configure this Option.
   381  // ShouldResultRetry is defined inside retry.FailurePolicy, so WithFailureRetry also can set ShouldResultRetry.
   382  // But if your retry policy is enabled by remote config, WithSpecifiedResultRetry is useful.
   383  func WithSpecifiedResultRetry(rr *retry.ShouldResultRetry) Option {
   384  	return Option{F: func(o *client.Options, di *utils.Slice) {
   385  		if rr == nil || (rr.RespRetry == nil && rr.ErrorRetry == nil) {
   386  			panic(fmt.Errorf("WithSpecifiedResultRetry: invalid '%+v'", rr))
   387  		}
   388  		di.Push(fmt.Sprintf("WithSpecifiedResultRetry(%+v)", rr))
   389  		o.RetryWithResult = rr
   390  	}}
   391  }
   392  
   393  // WithFallback is used to set the fallback policy for the client.
   394  // Demos are provided below:
   395  //
   396  //	demo1. fallback for error and resp
   397  //		`client.WithFallback(fallback.NewFallbackPolicy(yourFBFunc))`
   398  //	demo2. fallback for error and enable reportAsFallback, which sets reportAsFallback to be true and will do report(metric) as Fallback result
   399  //		`client.WithFallback(fallback.ErrorFallback(yourErrFBFunc).EnableReportAsFallback())`
   400  //	demo2. fallback for rpctime and circuit breaker
   401  //		`client.WithFallback(fallback.TimeoutAndCBFallback(yourErrFBFunc))`
   402  func WithFallback(fb *fallback.Policy) Option {
   403  	return Option{F: func(o *client.Options, di *utils.Slice) {
   404  		if !fallback.IsPolicyValid(fb) {
   405  			panic(fmt.Errorf("WithFallback: invalid '%+v'", fb))
   406  		}
   407  		di.Push(fmt.Sprintf("WithFallback(%+v)", fb))
   408  		o.Fallback = fb
   409  	}}
   410  }
   411  
   412  // WithCircuitBreaker adds a circuitbreaker suite for the client.
   413  func WithCircuitBreaker(s *circuitbreak.CBSuite) Option {
   414  	return Option{F: func(o *client.Options, di *utils.Slice) {
   415  		di.Push("WithCircuitBreaker()")
   416  		o.CBSuite = s
   417  	}}
   418  }
   419  
   420  // WithGRPCConnPoolSize sets the value for the client connection pool size.
   421  // In general, you should not adjust the size of the connection pool, otherwise it may cause performance degradation.
   422  // You should adjust the size according to the actual situation.
   423  func WithGRPCConnPoolSize(s uint32) Option {
   424  	return Option{F: func(o *client.Options, di *utils.Slice) {
   425  		di.Push(fmt.Sprintf("WithGRPCConnPoolSize(%d)", s))
   426  		o.GRPCConnPoolSize = s
   427  	}}
   428  }
   429  
   430  // WithGRPCWriteBufferSize determines how much data can be batched before doing a
   431  // write on the wire. The corresponding memory allocation for this buffer will
   432  // be twice the size to keep syscalls low. The default value for this buffer is
   433  // 32KB.
   434  //
   435  // Zero will disable the write buffer such that each write will be on underlying
   436  // connection. Note: A Send call may not directly translate to a write.
   437  // It corresponds to the WithWriteBufferSize DialOption of gRPC.
   438  func WithGRPCWriteBufferSize(s uint32) Option {
   439  	return Option{F: func(o *client.Options, di *utils.Slice) {
   440  		di.Push(fmt.Sprintf("WithGRPCWriteBufferSize(%d)", s))
   441  		o.GRPCConnectOpts.WriteBufferSize = s
   442  	}}
   443  }
   444  
   445  // WithGRPCReadBufferSize lets you set the size of read buffer, this determines how
   446  // much data can be read at most for each read syscall.
   447  //
   448  // The default value for this buffer is 32KB. Zero will disable read buffer for
   449  // a connection so data framer can access the underlying conn directly.
   450  // It corresponds to the WithReadBufferSize DialOption of gRPC.
   451  func WithGRPCReadBufferSize(s uint32) Option {
   452  	return Option{F: func(o *client.Options, di *utils.Slice) {
   453  		di.Push(fmt.Sprintf("WithGRPCReadBufferSize(%d)", s))
   454  		o.GRPCConnectOpts.ReadBufferSize = s
   455  	}}
   456  }
   457  
   458  // WithGRPCInitialWindowSize sets the value for initial window size on a grpc stream.
   459  // The lower bound for window size is 64K and any value smaller than that will be ignored.
   460  // It corresponds to the WithInitialWindowSize DialOption of gRPC.
   461  func WithGRPCInitialWindowSize(s uint32) Option {
   462  	return Option{F: func(o *client.Options, di *utils.Slice) {
   463  		di.Push(fmt.Sprintf("WithGRPCInitialWindowSize(%d)", s))
   464  		o.GRPCConnectOpts.InitialWindowSize = s
   465  	}}
   466  }
   467  
   468  // WithGRPCInitialConnWindowSize sets the value for initial window size on a connection of grpc.
   469  // The lower bound for window size is 64K and any value smaller than that will be ignored.
   470  // It corresponds to the WithInitialConnWindowSize DialOption of gRPC.
   471  func WithGRPCInitialConnWindowSize(s uint32) Option {
   472  	return Option{F: func(o *client.Options, di *utils.Slice) {
   473  		di.Push(fmt.Sprintf("WithGRPCInitialConnWindowSize(%d)", s))
   474  		o.GRPCConnectOpts.InitialConnWindowSize = s
   475  	}}
   476  }
   477  
   478  // WithGRPCMaxHeaderListSize returns a DialOption that specifies the maximum
   479  // (uncompressed) size of header list that the client is prepared to accept.
   480  // It corresponds to the WithMaxHeaderListSize DialOption of gRPC.
   481  func WithGRPCMaxHeaderListSize(s uint32) Option {
   482  	return Option{F: func(o *client.Options, di *utils.Slice) {
   483  		di.Push(fmt.Sprintf("WithGRPCMaxHeaderListSize(%d)", s))
   484  		o.GRPCConnectOpts.MaxHeaderListSize = &s
   485  	}}
   486  }
   487  
   488  // WithGRPCKeepaliveParams returns a DialOption that specifies keepalive parameters for the client transport.
   489  // It corresponds to the WithKeepaliveParams DialOption of gRPC.
   490  func WithGRPCKeepaliveParams(kp grpc.ClientKeepalive) Option {
   491  	if kp.Time < grpc.KeepaliveMinPingTime {
   492  		kp.Time = grpc.KeepaliveMinPingTime
   493  	}
   494  	return Option{F: func(o *client.Options, di *utils.Slice) {
   495  		di.Push(fmt.Sprintf("WithGRPCKeepaliveParams(%+v)", kp))
   496  		o.GRPCConnectOpts.KeepaliveParams = kp
   497  	}}
   498  }
   499  
   500  // WithWarmingUp forces the client to do some warm-ups at the end of the initialization.
   501  func WithWarmingUp(wuo *warmup.ClientOption) Option {
   502  	return Option{F: func(o *client.Options, di *utils.Slice) {
   503  		di.Push(fmt.Sprintf("WithWarmingUp(%+v)", wuo))
   504  		o.WarmUpOption = wuo
   505  	}}
   506  }
   507  
   508  // WithXDSSuite is used to set the xds suite for the client.
   509  func WithXDSSuite(suite xds.ClientSuite) Option {
   510  	return Option{F: func(o *client.Options, di *utils.Slice) {
   511  		if xds.CheckClientSuite(suite) {
   512  			di.Push("WithXDSSuite")
   513  			o.XDSEnabled = true
   514  			o.XDSRouterMiddleware = suite.RouterMiddleware
   515  			o.Resolver = suite.Resolver
   516  		}
   517  	}}
   518  }
   519  
   520  // WithContextBackup enables local-session to retrieve context backuped by server,
   521  // in case of user don't correctly pass context into next RPC call.
   522  //   - backupHandler pass a handler to check and handler user-defined key-values according to current context, returning backup==false means no need further operations.
   523  func WithContextBackup(backupHandler func(prev, cur context.Context) (ctx context.Context, backup bool)) Option {
   524  	return Option{F: func(o *client.Options, di *utils.Slice) {
   525  		di.Push(fmt.Sprintf("WithContextBackup({%v})", reflect.TypeOf(backupHandler).String()))
   526  		o.CtxBackupHandler = backupHandler
   527  	}}
   528  }