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 }