dialoptions.go (23100B)
1 /* 2 * 3 * Copyright 2018 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package grpc 20 21 import ( 22 "context" 23 "net" 24 "time" 25 26 "google.golang.org/grpc/backoff" 27 "google.golang.org/grpc/channelz" 28 "google.golang.org/grpc/credentials" 29 "google.golang.org/grpc/credentials/insecure" 30 "google.golang.org/grpc/internal" 31 internalbackoff "google.golang.org/grpc/internal/backoff" 32 "google.golang.org/grpc/internal/binarylog" 33 "google.golang.org/grpc/internal/transport" 34 "google.golang.org/grpc/keepalive" 35 "google.golang.org/grpc/resolver" 36 "google.golang.org/grpc/stats" 37 ) 38 39 func init() { 40 internal.AddGlobalDialOptions = func(opt ...DialOption) { 41 globalDialOptions = append(globalDialOptions, opt...) 42 } 43 internal.ClearGlobalDialOptions = func() { 44 globalDialOptions = nil 45 } 46 internal.WithBinaryLogger = withBinaryLogger 47 internal.JoinDialOptions = newJoinDialOption 48 internal.DisableGlobalDialOptions = newDisableGlobalDialOptions 49 } 50 51 // dialOptions configure a Dial call. dialOptions are set by the DialOption 52 // values passed to Dial. 53 type dialOptions struct { 54 unaryInt UnaryClientInterceptor 55 streamInt StreamClientInterceptor 56 57 chainUnaryInts []UnaryClientInterceptor 58 chainStreamInts []StreamClientInterceptor 59 60 cp Compressor 61 dc Decompressor 62 bs internalbackoff.Strategy 63 block bool 64 returnLastError bool 65 timeout time.Duration 66 scChan <-chan ServiceConfig 67 authority string 68 binaryLogger binarylog.Logger 69 copts transport.ConnectOptions 70 callOptions []CallOption 71 channelzParentID *channelz.Identifier 72 disableServiceConfig bool 73 disableRetry bool 74 disableHealthCheck bool 75 healthCheckFunc internal.HealthChecker 76 minConnectTimeout func() time.Duration 77 defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON. 78 defaultServiceConfigRawJSON *string 79 resolvers []resolver.Builder 80 } 81 82 // DialOption configures how we set up the connection. 83 type DialOption interface { 84 apply(*dialOptions) 85 } 86 87 var globalDialOptions []DialOption 88 89 // EmptyDialOption does not alter the dial configuration. It can be embedded in 90 // another structure to build custom dial options. 91 // 92 // # Experimental 93 // 94 // Notice: This type is EXPERIMENTAL and may be changed or removed in a 95 // later release. 96 type EmptyDialOption struct{} 97 98 func (EmptyDialOption) apply(*dialOptions) {} 99 100 type disableGlobalDialOptions struct{} 101 102 func (disableGlobalDialOptions) apply(*dialOptions) {} 103 104 // newDisableGlobalDialOptions returns a DialOption that prevents the ClientConn 105 // from applying the global DialOptions (set via AddGlobalDialOptions). 106 func newDisableGlobalDialOptions() DialOption { 107 return &disableGlobalDialOptions{} 108 } 109 110 // funcDialOption wraps a function that modifies dialOptions into an 111 // implementation of the DialOption interface. 112 type funcDialOption struct { 113 f func(*dialOptions) 114 } 115 116 func (fdo *funcDialOption) apply(do *dialOptions) { 117 fdo.f(do) 118 } 119 120 func newFuncDialOption(f func(*dialOptions)) *funcDialOption { 121 return &funcDialOption{ 122 f: f, 123 } 124 } 125 126 type joinDialOption struct { 127 opts []DialOption 128 } 129 130 func (jdo *joinDialOption) apply(do *dialOptions) { 131 for _, opt := range jdo.opts { 132 opt.apply(do) 133 } 134 } 135 136 func newJoinDialOption(opts ...DialOption) DialOption { 137 return &joinDialOption{opts: opts} 138 } 139 140 // WithWriteBufferSize determines how much data can be batched before doing a 141 // write on the wire. The corresponding memory allocation for this buffer will 142 // be twice the size to keep syscalls low. The default value for this buffer is 143 // 32KB. 144 // 145 // Zero or negative values will disable the write buffer such that each write 146 // will be on underlying connection. Note: A Send call may not directly 147 // translate to a write. 148 func WithWriteBufferSize(s int) DialOption { 149 return newFuncDialOption(func(o *dialOptions) { 150 o.copts.WriteBufferSize = s 151 }) 152 } 153 154 // WithReadBufferSize lets you set the size of read buffer, this determines how 155 // much data can be read at most for each read syscall. 156 // 157 // The default value for this buffer is 32KB. Zero or negative values will 158 // disable read buffer for a connection so data framer can access the 159 // underlying conn directly. 160 func WithReadBufferSize(s int) DialOption { 161 return newFuncDialOption(func(o *dialOptions) { 162 o.copts.ReadBufferSize = s 163 }) 164 } 165 166 // WithInitialWindowSize returns a DialOption which sets the value for initial 167 // window size on a stream. The lower bound for window size is 64K and any value 168 // smaller than that will be ignored. 169 func WithInitialWindowSize(s int32) DialOption { 170 return newFuncDialOption(func(o *dialOptions) { 171 o.copts.InitialWindowSize = s 172 }) 173 } 174 175 // WithInitialConnWindowSize returns a DialOption which sets the value for 176 // initial window size on a connection. The lower bound for window size is 64K 177 // and any value smaller than that will be ignored. 178 func WithInitialConnWindowSize(s int32) DialOption { 179 return newFuncDialOption(func(o *dialOptions) { 180 o.copts.InitialConnWindowSize = s 181 }) 182 } 183 184 // WithMaxMsgSize returns a DialOption which sets the maximum message size the 185 // client can receive. 186 // 187 // Deprecated: use WithDefaultCallOptions(MaxCallRecvMsgSize(s)) instead. Will 188 // be supported throughout 1.x. 189 func WithMaxMsgSize(s int) DialOption { 190 return WithDefaultCallOptions(MaxCallRecvMsgSize(s)) 191 } 192 193 // WithDefaultCallOptions returns a DialOption which sets the default 194 // CallOptions for calls over the connection. 195 func WithDefaultCallOptions(cos ...CallOption) DialOption { 196 return newFuncDialOption(func(o *dialOptions) { 197 o.callOptions = append(o.callOptions, cos...) 198 }) 199 } 200 201 // WithCodec returns a DialOption which sets a codec for message marshaling and 202 // unmarshaling. 203 // 204 // Deprecated: use WithDefaultCallOptions(ForceCodec(_)) instead. Will be 205 // supported throughout 1.x. 206 func WithCodec(c Codec) DialOption { 207 return WithDefaultCallOptions(CallCustomCodec(c)) 208 } 209 210 // WithCompressor returns a DialOption which sets a Compressor to use for 211 // message compression. It has lower priority than the compressor set by the 212 // UseCompressor CallOption. 213 // 214 // Deprecated: use UseCompressor instead. Will be supported throughout 1.x. 215 func WithCompressor(cp Compressor) DialOption { 216 return newFuncDialOption(func(o *dialOptions) { 217 o.cp = cp 218 }) 219 } 220 221 // WithDecompressor returns a DialOption which sets a Decompressor to use for 222 // incoming message decompression. If incoming response messages are encoded 223 // using the decompressor's Type(), it will be used. Otherwise, the message 224 // encoding will be used to look up the compressor registered via 225 // encoding.RegisterCompressor, which will then be used to decompress the 226 // message. If no compressor is registered for the encoding, an Unimplemented 227 // status error will be returned. 228 // 229 // Deprecated: use encoding.RegisterCompressor instead. Will be supported 230 // throughout 1.x. 231 func WithDecompressor(dc Decompressor) DialOption { 232 return newFuncDialOption(func(o *dialOptions) { 233 o.dc = dc 234 }) 235 } 236 237 // WithServiceConfig returns a DialOption which has a channel to read the 238 // service configuration. 239 // 240 // Deprecated: service config should be received through name resolver or via 241 // WithDefaultServiceConfig, as specified at 242 // https://github.com/grpc/grpc/blob/master/doc/service_config.md. Will be 243 // removed in a future 1.x release. 244 func WithServiceConfig(c <-chan ServiceConfig) DialOption { 245 return newFuncDialOption(func(o *dialOptions) { 246 o.scChan = c 247 }) 248 } 249 250 // WithConnectParams configures the ClientConn to use the provided ConnectParams 251 // for creating and maintaining connections to servers. 252 // 253 // The backoff configuration specified as part of the ConnectParams overrides 254 // all defaults specified in 255 // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. Consider 256 // using the backoff.DefaultConfig as a base, in cases where you want to 257 // override only a subset of the backoff configuration. 258 func WithConnectParams(p ConnectParams) DialOption { 259 return newFuncDialOption(func(o *dialOptions) { 260 o.bs = internalbackoff.Exponential{Config: p.Backoff} 261 o.minConnectTimeout = func() time.Duration { 262 return p.MinConnectTimeout 263 } 264 }) 265 } 266 267 // WithBackoffMaxDelay configures the dialer to use the provided maximum delay 268 // when backing off after failed connection attempts. 269 // 270 // Deprecated: use WithConnectParams instead. Will be supported throughout 1.x. 271 func WithBackoffMaxDelay(md time.Duration) DialOption { 272 return WithBackoffConfig(BackoffConfig{MaxDelay: md}) 273 } 274 275 // WithBackoffConfig configures the dialer to use the provided backoff 276 // parameters after connection failures. 277 // 278 // Deprecated: use WithConnectParams instead. Will be supported throughout 1.x. 279 func WithBackoffConfig(b BackoffConfig) DialOption { 280 bc := backoff.DefaultConfig 281 bc.MaxDelay = b.MaxDelay 282 return withBackoff(internalbackoff.Exponential{Config: bc}) 283 } 284 285 // withBackoff sets the backoff strategy used for connectRetryNum after a failed 286 // connection attempt. 287 // 288 // This can be exported if arbitrary backoff strategies are allowed by gRPC. 289 func withBackoff(bs internalbackoff.Strategy) DialOption { 290 return newFuncDialOption(func(o *dialOptions) { 291 o.bs = bs 292 }) 293 } 294 295 // WithBlock returns a DialOption which makes callers of Dial block until the 296 // underlying connection is up. Without this, Dial returns immediately and 297 // connecting the server happens in background. 298 // 299 // Use of this feature is not recommended. For more information, please see: 300 // https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md 301 func WithBlock() DialOption { 302 return newFuncDialOption(func(o *dialOptions) { 303 o.block = true 304 }) 305 } 306 307 // WithReturnConnectionError returns a DialOption which makes the client connection 308 // return a string containing both the last connection error that occurred and 309 // the context.DeadlineExceeded error. 310 // Implies WithBlock() 311 // 312 // Use of this feature is not recommended. For more information, please see: 313 // https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md 314 // 315 // # Experimental 316 // 317 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 318 // later release. 319 func WithReturnConnectionError() DialOption { 320 return newFuncDialOption(func(o *dialOptions) { 321 o.block = true 322 o.returnLastError = true 323 }) 324 } 325 326 // WithInsecure returns a DialOption which disables transport security for this 327 // ClientConn. Under the hood, it uses insecure.NewCredentials(). 328 // 329 // Note that using this DialOption with per-RPC credentials (through 330 // WithCredentialsBundle or WithPerRPCCredentials) which require transport 331 // security is incompatible and will cause grpc.Dial() to fail. 332 // 333 // Deprecated: use WithTransportCredentials and insecure.NewCredentials() 334 // instead. Will be supported throughout 1.x. 335 func WithInsecure() DialOption { 336 return newFuncDialOption(func(o *dialOptions) { 337 o.copts.TransportCredentials = insecure.NewCredentials() 338 }) 339 } 340 341 // WithNoProxy returns a DialOption which disables the use of proxies for this 342 // ClientConn. This is ignored if WithDialer or WithContextDialer are used. 343 // 344 // # Experimental 345 // 346 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 347 // later release. 348 func WithNoProxy() DialOption { 349 return newFuncDialOption(func(o *dialOptions) { 350 o.copts.UseProxy = false 351 }) 352 } 353 354 // WithTransportCredentials returns a DialOption which configures a connection 355 // level security credentials (e.g., TLS/SSL). This should not be used together 356 // with WithCredentialsBundle. 357 func WithTransportCredentials(creds credentials.TransportCredentials) DialOption { 358 return newFuncDialOption(func(o *dialOptions) { 359 o.copts.TransportCredentials = creds 360 }) 361 } 362 363 // WithPerRPCCredentials returns a DialOption which sets credentials and places 364 // auth state on each outbound RPC. 365 func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption { 366 return newFuncDialOption(func(o *dialOptions) { 367 o.copts.PerRPCCredentials = append(o.copts.PerRPCCredentials, creds) 368 }) 369 } 370 371 // WithCredentialsBundle returns a DialOption to set a credentials bundle for 372 // the ClientConn.WithCreds. This should not be used together with 373 // WithTransportCredentials. 374 // 375 // # Experimental 376 // 377 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 378 // later release. 379 func WithCredentialsBundle(b credentials.Bundle) DialOption { 380 return newFuncDialOption(func(o *dialOptions) { 381 o.copts.CredsBundle = b 382 }) 383 } 384 385 // WithTimeout returns a DialOption that configures a timeout for dialing a 386 // ClientConn initially. This is valid if and only if WithBlock() is present. 387 // 388 // Deprecated: use DialContext instead of Dial and context.WithTimeout 389 // instead. Will be supported throughout 1.x. 390 func WithTimeout(d time.Duration) DialOption { 391 return newFuncDialOption(func(o *dialOptions) { 392 o.timeout = d 393 }) 394 } 395 396 // WithContextDialer returns a DialOption that sets a dialer to create 397 // connections. If FailOnNonTempDialError() is set to true, and an error is 398 // returned by f, gRPC checks the error's Temporary() method to decide if it 399 // should try to reconnect to the network address. 400 func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption { 401 return newFuncDialOption(func(o *dialOptions) { 402 o.copts.Dialer = f 403 }) 404 } 405 406 func init() { 407 internal.WithHealthCheckFunc = withHealthCheckFunc 408 } 409 410 // WithDialer returns a DialOption that specifies a function to use for dialing 411 // network addresses. If FailOnNonTempDialError() is set to true, and an error 412 // is returned by f, gRPC checks the error's Temporary() method to decide if it 413 // should try to reconnect to the network address. 414 // 415 // Deprecated: use WithContextDialer instead. Will be supported throughout 416 // 1.x. 417 func WithDialer(f func(string, time.Duration) (net.Conn, error)) DialOption { 418 return WithContextDialer( 419 func(ctx context.Context, addr string) (net.Conn, error) { 420 if deadline, ok := ctx.Deadline(); ok { 421 return f(addr, time.Until(deadline)) 422 } 423 return f(addr, 0) 424 }) 425 } 426 427 // WithStatsHandler returns a DialOption that specifies the stats handler for 428 // all the RPCs and underlying network connections in this ClientConn. 429 func WithStatsHandler(h stats.Handler) DialOption { 430 return newFuncDialOption(func(o *dialOptions) { 431 if h == nil { 432 logger.Error("ignoring nil parameter in grpc.WithStatsHandler ClientOption") 433 // Do not allow a nil stats handler, which would otherwise cause 434 // panics. 435 return 436 } 437 o.copts.StatsHandlers = append(o.copts.StatsHandlers, h) 438 }) 439 } 440 441 // withBinaryLogger returns a DialOption that specifies the binary logger for 442 // this ClientConn. 443 func withBinaryLogger(bl binarylog.Logger) DialOption { 444 return newFuncDialOption(func(o *dialOptions) { 445 o.binaryLogger = bl 446 }) 447 } 448 449 // FailOnNonTempDialError returns a DialOption that specifies if gRPC fails on 450 // non-temporary dial errors. If f is true, and dialer returns a non-temporary 451 // error, gRPC will fail the connection to the network address and won't try to 452 // reconnect. The default value of FailOnNonTempDialError is false. 453 // 454 // FailOnNonTempDialError only affects the initial dial, and does not do 455 // anything useful unless you are also using WithBlock(). 456 // 457 // Use of this feature is not recommended. For more information, please see: 458 // https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md 459 // 460 // # Experimental 461 // 462 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 463 // later release. 464 func FailOnNonTempDialError(f bool) DialOption { 465 return newFuncDialOption(func(o *dialOptions) { 466 o.copts.FailOnNonTempDialError = f 467 }) 468 } 469 470 // WithUserAgent returns a DialOption that specifies a user agent string for all 471 // the RPCs. 472 func WithUserAgent(s string) DialOption { 473 return newFuncDialOption(func(o *dialOptions) { 474 o.copts.UserAgent = s 475 }) 476 } 477 478 // WithKeepaliveParams returns a DialOption that specifies keepalive parameters 479 // for the client transport. 480 func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption { 481 if kp.Time < internal.KeepaliveMinPingTime { 482 logger.Warningf("Adjusting keepalive ping interval to minimum period of %v", internal.KeepaliveMinPingTime) 483 kp.Time = internal.KeepaliveMinPingTime 484 } 485 return newFuncDialOption(func(o *dialOptions) { 486 o.copts.KeepaliveParams = kp 487 }) 488 } 489 490 // WithUnaryInterceptor returns a DialOption that specifies the interceptor for 491 // unary RPCs. 492 func WithUnaryInterceptor(f UnaryClientInterceptor) DialOption { 493 return newFuncDialOption(func(o *dialOptions) { 494 o.unaryInt = f 495 }) 496 } 497 498 // WithChainUnaryInterceptor returns a DialOption that specifies the chained 499 // interceptor for unary RPCs. The first interceptor will be the outer most, 500 // while the last interceptor will be the inner most wrapper around the real call. 501 // All interceptors added by this method will be chained, and the interceptor 502 // defined by WithUnaryInterceptor will always be prepended to the chain. 503 func WithChainUnaryInterceptor(interceptors ...UnaryClientInterceptor) DialOption { 504 return newFuncDialOption(func(o *dialOptions) { 505 o.chainUnaryInts = append(o.chainUnaryInts, interceptors...) 506 }) 507 } 508 509 // WithStreamInterceptor returns a DialOption that specifies the interceptor for 510 // streaming RPCs. 511 func WithStreamInterceptor(f StreamClientInterceptor) DialOption { 512 return newFuncDialOption(func(o *dialOptions) { 513 o.streamInt = f 514 }) 515 } 516 517 // WithChainStreamInterceptor returns a DialOption that specifies the chained 518 // interceptor for streaming RPCs. The first interceptor will be the outer most, 519 // while the last interceptor will be the inner most wrapper around the real call. 520 // All interceptors added by this method will be chained, and the interceptor 521 // defined by WithStreamInterceptor will always be prepended to the chain. 522 func WithChainStreamInterceptor(interceptors ...StreamClientInterceptor) DialOption { 523 return newFuncDialOption(func(o *dialOptions) { 524 o.chainStreamInts = append(o.chainStreamInts, interceptors...) 525 }) 526 } 527 528 // WithAuthority returns a DialOption that specifies the value to be used as the 529 // :authority pseudo-header and as the server name in authentication handshake. 530 func WithAuthority(a string) DialOption { 531 return newFuncDialOption(func(o *dialOptions) { 532 o.authority = a 533 }) 534 } 535 536 // WithChannelzParentID returns a DialOption that specifies the channelz ID of 537 // current ClientConn's parent. This function is used in nested channel creation 538 // (e.g. grpclb dial). 539 // 540 // # Experimental 541 // 542 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 543 // later release. 544 func WithChannelzParentID(id *channelz.Identifier) DialOption { 545 return newFuncDialOption(func(o *dialOptions) { 546 o.channelzParentID = id 547 }) 548 } 549 550 // WithDisableServiceConfig returns a DialOption that causes gRPC to ignore any 551 // service config provided by the resolver and provides a hint to the resolver 552 // to not fetch service configs. 553 // 554 // Note that this dial option only disables service config from resolver. If 555 // default service config is provided, gRPC will use the default service config. 556 func WithDisableServiceConfig() DialOption { 557 return newFuncDialOption(func(o *dialOptions) { 558 o.disableServiceConfig = true 559 }) 560 } 561 562 // WithDefaultServiceConfig returns a DialOption that configures the default 563 // service config, which will be used in cases where: 564 // 565 // 1. WithDisableServiceConfig is also used, or 566 // 567 // 2. The name resolver does not provide a service config or provides an 568 // invalid service config. 569 // 570 // The parameter s is the JSON representation of the default service config. 571 // For more information about service configs, see: 572 // https://github.com/grpc/grpc/blob/master/doc/service_config.md 573 // For a simple example of usage, see: 574 // examples/features/load_balancing/client/main.go 575 func WithDefaultServiceConfig(s string) DialOption { 576 return newFuncDialOption(func(o *dialOptions) { 577 o.defaultServiceConfigRawJSON = &s 578 }) 579 } 580 581 // WithDisableRetry returns a DialOption that disables retries, even if the 582 // service config enables them. This does not impact transparent retries, which 583 // will happen automatically if no data is written to the wire or if the RPC is 584 // unprocessed by the remote server. 585 func WithDisableRetry() DialOption { 586 return newFuncDialOption(func(o *dialOptions) { 587 o.disableRetry = true 588 }) 589 } 590 591 // WithMaxHeaderListSize returns a DialOption that specifies the maximum 592 // (uncompressed) size of header list that the client is prepared to accept. 593 func WithMaxHeaderListSize(s uint32) DialOption { 594 return newFuncDialOption(func(o *dialOptions) { 595 o.copts.MaxHeaderListSize = &s 596 }) 597 } 598 599 // WithDisableHealthCheck disables the LB channel health checking for all 600 // SubConns of this ClientConn. 601 // 602 // # Experimental 603 // 604 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 605 // later release. 606 func WithDisableHealthCheck() DialOption { 607 return newFuncDialOption(func(o *dialOptions) { 608 o.disableHealthCheck = true 609 }) 610 } 611 612 // withHealthCheckFunc replaces the default health check function with the 613 // provided one. It makes tests easier to change the health check function. 614 // 615 // For testing purpose only. 616 func withHealthCheckFunc(f internal.HealthChecker) DialOption { 617 return newFuncDialOption(func(o *dialOptions) { 618 o.healthCheckFunc = f 619 }) 620 } 621 622 func defaultDialOptions() dialOptions { 623 return dialOptions{ 624 healthCheckFunc: internal.HealthCheckFunc, 625 copts: transport.ConnectOptions{ 626 WriteBufferSize: defaultWriteBufSize, 627 ReadBufferSize: defaultReadBufSize, 628 UseProxy: true, 629 }, 630 } 631 } 632 633 // withGetMinConnectDeadline specifies the function that clientconn uses to 634 // get minConnectDeadline. This can be used to make connection attempts happen 635 // faster/slower. 636 // 637 // For testing purpose only. 638 func withMinConnectDeadline(f func() time.Duration) DialOption { 639 return newFuncDialOption(func(o *dialOptions) { 640 o.minConnectTimeout = f 641 }) 642 } 643 644 // WithResolvers allows a list of resolver implementations to be registered 645 // locally with the ClientConn without needing to be globally registered via 646 // resolver.Register. They will be matched against the scheme used for the 647 // current Dial only, and will take precedence over the global registry. 648 // 649 // # Experimental 650 // 651 // Notice: This API is EXPERIMENTAL and may be changed or removed in a 652 // later release. 653 func WithResolvers(rs ...resolver.Builder) DialOption { 654 return newFuncDialOption(func(o *dialOptions) { 655 o.resolvers = append(o.resolvers, rs...) 656 }) 657 }