github.com/cloudwego/hertz@v0.9.3/pkg/app/client/client.go (about) 1 /* 2 * Copyright 2022 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 * The MIT License (MIT) 17 * 18 * Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors 19 * 20 * Permission is hereby granted, free of charge, to any person obtaining a copy 21 * of this software and associated documentation files (the "Software"), to deal 22 * in the Software without restriction, including without limitation the rights 23 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 * copies of the Software, and to permit persons to whom the Software is 25 * furnished to do so, subject to the following conditions: 26 * 27 * The above copyright notice and this permission notice shall be included in 28 * all copies or substantial portions of the Software. 29 * 30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 * THE SOFTWARE. 37 * 38 * This file may have been modified by CloudWeGo authors. All CloudWeGo 39 * Modifications are Copyright 2022 CloudWeGo Authors. 40 */ 41 42 package client 43 44 import ( 45 "bytes" 46 "context" 47 "fmt" 48 "io" 49 "reflect" 50 "strings" 51 "sync" 52 "time" 53 54 "github.com/cloudwego/hertz/internal/bytestr" 55 "github.com/cloudwego/hertz/internal/nocopy" 56 "github.com/cloudwego/hertz/pkg/common/config" 57 "github.com/cloudwego/hertz/pkg/common/errors" 58 "github.com/cloudwego/hertz/pkg/common/hlog" 59 "github.com/cloudwego/hertz/pkg/common/utils" 60 "github.com/cloudwego/hertz/pkg/network/dialer" 61 "github.com/cloudwego/hertz/pkg/protocol" 62 "github.com/cloudwego/hertz/pkg/protocol/client" 63 "github.com/cloudwego/hertz/pkg/protocol/consts" 64 "github.com/cloudwego/hertz/pkg/protocol/http1" 65 "github.com/cloudwego/hertz/pkg/protocol/http1/factory" 66 "github.com/cloudwego/hertz/pkg/protocol/suite" 67 ) 68 69 var ( 70 errorInvalidURI = errors.NewPublic("invalid uri") 71 errorLastMiddlewareExist = errors.NewPublic("last middleware already set") 72 ) 73 74 // Do performs the given http request and fills the given http response. 75 // 76 // Request must contain at least non-zero RequestURI with full url (including 77 // scheme and host) or non-zero Host header + RequestURI.© 78 // 79 // Client determines the server to be requested in the following order: 80 // 81 // - from RequestURI if it contains full url with scheme and host; 82 // - from Host header otherwise. 83 // 84 // The function doesn't follow redirects. Use Get* for following redirects. 85 // 86 // Response is ignored if resp is nil. 87 // 88 // ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections 89 // to the requested host are busy. 90 // 91 // It is recommended obtaining req and resp via AcquireRequest 92 // and AcquireResponse in performance-critical code. 93 func Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error { 94 return defaultClient.Do(ctx, req, resp) 95 } 96 97 // DoTimeout performs the given request and waits for response during 98 // the given timeout duration. 99 // 100 // Request must contain at least non-zero RequestURI with full url (including 101 // scheme and host) or non-zero Host header + RequestURI. 102 // 103 // Client determines the server to be requested in the following order: 104 // 105 // - from RequestURI if it contains full url with scheme and host; 106 // - from Host header otherwise. 107 // 108 // The function doesn't follow redirects. Use Get* for following redirects. 109 // 110 // Response is ignored if resp is nil. 111 // 112 // errTimeout is returned if the response wasn't returned during 113 // the given timeout. 114 // 115 // ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections 116 // to the requested host are busy. 117 // 118 // It is recommended obtaining req and resp via AcquireRequest 119 // and AcquireResponse in performance-critical code. 120 // 121 // Warning: DoTimeout does not terminate the request itself. The request will 122 // continue in the background and the response will be discarded. 123 // If requests take too long and the connection pool gets filled up please 124 // try using a customized Client instance with a ReadTimeout config or set the request level read timeout like: 125 // `req.SetOptions(config.WithReadTimeout(1 * time.Second))` 126 func DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error { 127 return defaultClient.DoTimeout(ctx, req, resp, timeout) 128 } 129 130 // DoDeadline performs the given request and waits for response until 131 // the given deadline. 132 // 133 // Request must contain at least non-zero RequestURI with full url (including 134 // scheme and host) or non-zero Host header + RequestURI. 135 // 136 // Client determines the server to be requested in the following order: 137 // 138 // - from RequestURI if it contains full url with scheme and host; 139 // - from Host header otherwise. 140 // 141 // The function doesn't follow redirects. Use Get* for following redirects. 142 // 143 // Response is ignored if resp is nil. 144 // 145 // errTimeout is returned if the response wasn't returned until 146 // the given deadline. 147 // 148 // ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections 149 // to the requested host are busy. 150 // 151 // It is recommended obtaining req and resp via AcquireRequest 152 // and AcquireResponse in performance-critical code. 153 // 154 // Warning: DoDeadline does not terminate the request itself. The request will 155 // continue in the background and the response will be discarded. 156 // If requests take too long and the connection pool gets filled up please 157 // try using a customized Client instance with a ReadTimeout config or set the request level read timeout like: 158 // `req.SetOptions(config.WithReadTimeout(1 * time.Second))` 159 func DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error { 160 return defaultClient.DoDeadline(ctx, req, resp, deadline) 161 } 162 163 // DoRedirects performs the given http request and fills the given http response, 164 // following up to maxRedirectsCount redirects. When the redirect count exceeds 165 // maxRedirectsCount, ErrTooManyRedirects is returned. 166 // 167 // Request must contain at least non-zero RequestURI with full url (including 168 // scheme and host) or non-zero Host header + RequestURI. 169 // 170 // Client determines the server to be requested in the following order: 171 // 172 // - from RequestURI if it contains full url with scheme and host; 173 // - from Host header otherwise. 174 // 175 // Response is ignored if resp is nil. 176 // 177 // ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections 178 // to the requested host are busy. 179 // 180 // It is recommended obtaining req and resp via AcquireRequest 181 // and AcquireResponse in performance-critical code. 182 func DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error { 183 _, _, err := client.DoRequestFollowRedirects(ctx, req, resp, req.URI().String(), maxRedirectsCount, defaultClient) 184 return err 185 } 186 187 // Get returns the status code and body of url. 188 // 189 // The contents of dst will be replaced by the body and returned, if the dst 190 // is too small a new slice will be allocated. 191 // 192 // The function follows redirects. Use Do* for manually handling redirects. 193 func Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 194 return defaultClient.Get(ctx, dst, url, requestOptions...) 195 } 196 197 // GetTimeout returns the status code and body of url. 198 // 199 // The contents of dst will be replaced by the body and returned, if the dst 200 // is too small a new slice will be allocated. 201 // 202 // The function follows redirects. Use Do* for manually handling redirects. 203 // 204 // errTimeout error is returned if url contents couldn't be fetched 205 // during the given timeout. 206 // 207 // Warning: GetTimeout does not terminate the request itself. The request will 208 // continue in the background and the response will be discarded. 209 // If requests take too long and the connection pool gets filled up please 210 // try using a customized Client instance with a ReadTimeout config or set the request level read timeout like: 211 // `GetTimeout(ctx, dst, url, timeout, config.WithReadTimeout(1 * time.Second))` 212 func GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 213 return defaultClient.GetTimeout(ctx, dst, url, timeout, requestOptions...) 214 } 215 216 // GetDeadline returns the status code and body of url. 217 // 218 // The contents of dst will be replaced by the body and returned, if the dst 219 // is too small a new slice will be allocated. 220 // 221 // The function follows redirects. Use Do* for manually handling redirects. 222 // 223 // errTimeout error is returned if url contents couldn't be fetched 224 // until the given deadline. 225 // 226 // Warning: GetDeadline does not terminate the request itself. The request will 227 // continue in the background and the response will be discarded. 228 // If requests take too long and the connection pool gets filled up please 229 // try using a customized Client instance with a ReadTimeout config or set the request level read timeout like: 230 // `GetDeadline(ctx, dst, url, timeout, config.WithReadTimeout(1 * time.Second))` 231 func GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 232 return defaultClient.GetDeadline(ctx, dst, url, deadline, requestOptions...) 233 } 234 235 // Post sends POST request to the given url with the given POST arguments. 236 // 237 // The contents of dst will be replaced by the body and returned, if the dst 238 // is too small a new slice will be allocated. 239 // 240 // The function follows redirects. Use Do* for manually handling redirects. 241 // 242 // Empty POST body is sent if postArgs is nil. 243 func Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 244 return defaultClient.Post(ctx, dst, url, postArgs, requestOptions...) 245 } 246 247 var defaultClient, _ = NewClient(WithDialTimeout(consts.DefaultDialTimeout)) 248 249 // Client implements http client. 250 // 251 // Copying Client by value is prohibited. Create new instance instead. 252 // 253 // It is safe calling Client methods from concurrently running goroutines. 254 type Client struct { 255 noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used 256 257 options *config.ClientOptions 258 259 // Proxy specifies a function to return a proxy for a given 260 // Request. If the function returns a non-nil error, the 261 // request is aborted with the provided error. 262 // 263 // The proxy type is determined by the URL scheme. 264 // "http" and "https" are supported. If the scheme is empty, 265 // "http" is assumed. 266 // 267 // If Proxy is nil or returns a nil *URL, no proxy is used. 268 Proxy protocol.Proxy 269 270 // RetryIfFunc sets the retry decision function. If nil, the client.DefaultRetryIf will be applied. 271 RetryIfFunc client.RetryIfFunc 272 273 clientFactory suite.ClientFactory 274 275 mLock sync.Mutex 276 m map[string]client.HostClient 277 ms map[string]client.HostClient 278 mws Middleware 279 lastMiddleware Middleware 280 } 281 282 func (c *Client) GetOptions() *config.ClientOptions { 283 return c.options 284 } 285 286 func (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc) { 287 c.RetryIfFunc = retryIf 288 } 289 290 // Deprecated: use SetRetryIfFunc instead of SetRetryIf 291 func (c *Client) SetRetryIf(fn func(request *protocol.Request) bool) { 292 f := func(req *protocol.Request, resp *protocol.Response, err error) bool { 293 return fn(req) 294 } 295 c.SetRetryIfFunc(f) 296 } 297 298 // SetProxy is used to set client proxy. 299 // 300 // Don't SetProxy twice for a client. 301 // If you want to use another proxy, please create another client and set proxy to it. 302 func (c *Client) SetProxy(p protocol.Proxy) { 303 c.Proxy = p 304 } 305 306 // Get returns the status code and body of url. 307 // 308 // The contents of dst will be replaced by the body and returned, if the dst 309 // is too small a new slice will be allocated. 310 // 311 // The function follows redirects. Use Do* for manually handling redirects. 312 func (c *Client) Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 313 return client.GetURL(ctx, dst, url, c, requestOptions...) 314 } 315 316 // GetTimeout returns the status code and body of url. 317 // 318 // The contents of dst will be replaced by the body and returned, if the dst 319 // is too small a new slice will be allocated. 320 // 321 // The function follows redirects. Use Do* for manually handling redirects. 322 // 323 // errTimeout error is returned if url contents couldn't be fetched 324 // during the given timeout. 325 func (c *Client) GetTimeout(ctx context.Context, dst []byte, url string, timeout time.Duration, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 326 return client.GetURLTimeout(ctx, dst, url, timeout, c, requestOptions...) 327 } 328 329 // GetDeadline returns the status code and body of url. 330 // 331 // The contents of dst will be replaced by the body and returned, if the dst 332 // is too small a new slice will be allocated. 333 // 334 // The function follows redirects. Use Do* for manually handling redirects. 335 // 336 // errTimeout error is returned if url contents couldn't be fetched 337 // until the given deadline. 338 func (c *Client) GetDeadline(ctx context.Context, dst []byte, url string, deadline time.Time, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 339 return client.GetURLDeadline(ctx, dst, url, deadline, c, requestOptions...) 340 } 341 342 // Post sends POST request to the given url with the given POST arguments. 343 // 344 // The contents of dst will be replaced by the body and returned, if the dst 345 // is too small a new slice will be allocated. 346 // 347 // The function follows redirects. Use Do* for manually handling redirects. 348 // 349 // Empty POST body is sent if postArgs is nil. 350 func (c *Client) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) { 351 return client.PostURL(ctx, dst, url, postArgs, c, requestOptions...) 352 } 353 354 // DoTimeout performs the given request and waits for response during 355 // the given timeout duration. 356 // 357 // Request must contain at least non-zero RequestURI with full url (including 358 // scheme and host) or non-zero Host header + RequestURI. 359 // 360 // Client determines the server to be requested in the following order: 361 // 362 // - from RequestURI if it contains full url with scheme and host; 363 // - from Host header otherwise. 364 // 365 // The function doesn't follow redirects. Use Get* for following redirects. 366 // 367 // Response is ignored if resp is nil. 368 // 369 // errTimeout is returned if the response wasn't returned during 370 // the given timeout. 371 // 372 // ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections 373 // to the requested host are busy. 374 // 375 // It is recommended obtaining req and resp via AcquireRequest 376 // and AcquireResponse in performance-critical code. 377 // 378 // Warning: DoTimeout does not terminate the request itself. The request will 379 // continue in the background and the response will be discarded. 380 // If requests take too long and the connection pool gets filled up please 381 // try using a customized Client instance with a ReadTimeout config or set the request level read timeout like: 382 // `req.SetOptions(config.WithReadTimeout(1 * time.Second))` 383 func (c *Client) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error { 384 return client.DoTimeout(ctx, req, resp, timeout, c) 385 } 386 387 // DoDeadline performs the given request and waits for response until 388 // the given deadline. 389 // 390 // Request must contain at least non-zero RequestURI with full url (including 391 // scheme and host) or non-zero Host header + RequestURI. 392 // 393 // Client determines the server to be requested in the following order: 394 // 395 // - from RequestURI if it contains full url with scheme and host; 396 // - from Host header otherwise. 397 // 398 // The function doesn't follow redirects. Use Get* for following redirects. 399 // 400 // Response is ignored if resp is nil. 401 // 402 // errTimeout is returned if the response wasn't returned until 403 // the given deadline. 404 // 405 // ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections 406 // to the requested host are busy. 407 // 408 // It is recommended obtaining req and resp via AcquireRequest 409 // and AcquireResponse in performance-critical code. 410 func (c *Client) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error { 411 return client.DoDeadline(ctx, req, resp, deadline, c) 412 } 413 414 // DoRedirects performs the given http request and fills the given http response, 415 // following up to maxRedirectsCount redirects. When the redirect count exceeds 416 // maxRedirectsCount, ErrTooManyRedirects is returned. 417 // 418 // Request must contain at least non-zero RequestURI with full url (including 419 // scheme and host) or non-zero Host header + RequestURI. 420 // 421 // Client determines the server to be requested in the following order: 422 // 423 // - from RequestURI if it contains full url with scheme and host; 424 // - from Host header otherwise. 425 // 426 // Response is ignored if resp is nil. 427 // 428 // ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections 429 // to the requested host are busy. 430 // 431 // It is recommended obtaining req and resp via AcquireRequest 432 // and AcquireResponse in performance-critical code. 433 func (c *Client) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error { 434 _, _, err := client.DoRequestFollowRedirects(ctx, req, resp, req.URI().String(), maxRedirectsCount, c) 435 return err 436 } 437 438 // Do performs the given http request and fills the given http response. 439 // 440 // Request must contain at least non-zero RequestURI with full url (including 441 // scheme and host) or non-zero Host header + RequestURI. 442 // 443 // Client determines the server to be requested in the following order: 444 // 445 // - from RequestURI if it contains full url with scheme and host; 446 // - from Host header otherwise. 447 // 448 // Response is ignored if resp is nil. 449 // 450 // The function doesn't follow redirects. Use Get* for following redirects. 451 // 452 // ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections 453 // to the requested host are busy. 454 // 455 // It is recommended obtaining req and resp via AcquireRequest 456 // and AcquireResponse in performance-critical code. 457 func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error { 458 if c.mws == nil { 459 return c.do(ctx, req, resp) 460 } 461 if c.lastMiddleware != nil { 462 return c.mws(c.lastMiddleware(c.do))(ctx, req, resp) 463 } 464 return c.mws(c.do)(ctx, req, resp) 465 } 466 467 func (c *Client) do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error { 468 if !c.options.KeepAlive { 469 req.Header.SetConnectionClose(true) 470 } 471 uri := req.URI() 472 if uri == nil { 473 return errorInvalidURI 474 } 475 476 var proxyURI *protocol.URI 477 var err error 478 479 if c.Proxy != nil { 480 proxyURI, err = c.Proxy(req) 481 if err != nil { 482 return fmt.Errorf("proxy error=%w", err) 483 } 484 } 485 486 isTLS := false 487 scheme := uri.Scheme() 488 if bytes.Equal(scheme, bytestr.StrHTTPS) { 489 isTLS = true 490 } else if !bytes.Equal(scheme, bytestr.StrHTTP) && !bytes.Equal(scheme, bytestr.StrSD) { 491 return fmt.Errorf("unsupported protocol %q. http and https are supported", scheme) 492 } 493 host := uri.Host() 494 startCleaner := false 495 496 c.mLock.Lock() 497 498 m := c.m 499 if isTLS { 500 m = c.ms 501 } 502 503 h := string(host) 504 hc := m[h] 505 if hc == nil { 506 if c.clientFactory == nil { 507 // load http1 client by default 508 c.clientFactory = factory.NewClientFactory(newHttp1OptionFromClient(c)) 509 } 510 hc, _ = c.clientFactory.NewHostClient() 511 hc.SetDynamicConfig(&client.DynamicConfig{ 512 Addr: utils.AddMissingPort(h, isTLS), 513 ProxyURI: proxyURI, 514 IsTLS: isTLS, 515 }) 516 517 // re-configure hook 518 if c.options.HostClientConfigHook != nil { 519 err = c.options.HostClientConfigHook(hc) 520 if err != nil { 521 c.mLock.Unlock() 522 return err 523 } 524 } 525 526 m[h] = hc 527 if len(m) == 1 { 528 startCleaner = true 529 } 530 } 531 532 c.mLock.Unlock() 533 534 if startCleaner { 535 go c.mCleaner() 536 } 537 538 return hc.Do(ctx, req, resp) 539 } 540 541 // CloseIdleConnections closes any connections which were previously 542 // connected from previous requests but are now sitting idle in a 543 // "keep-alive" state. It does not interrupt any connections currently 544 // in use. 545 func (c *Client) CloseIdleConnections() { 546 c.mLock.Lock() 547 for _, v := range c.m { 548 v.CloseIdleConnections() 549 } 550 c.mLock.Unlock() 551 } 552 553 func (c *Client) mCleaner() { 554 mustStop := false 555 556 for { 557 time.Sleep(10 * time.Second) 558 c.mLock.Lock() 559 for k, v := range c.m { 560 shouldRemove := v.ShouldRemove() 561 562 if shouldRemove { 563 delete(c.m, k) 564 if f, ok := v.(io.Closer); ok { 565 err := f.Close() 566 if err != nil { 567 hlog.Warnf("clean hostclient error, addr: %s, err: %s", k, err.Error()) 568 } 569 } 570 } 571 } 572 if len(c.m) == 0 { 573 mustStop = true 574 } 575 c.mLock.Unlock() 576 577 if mustStop { 578 break 579 } 580 } 581 } 582 583 func (c *Client) SetClientFactory(cf suite.ClientFactory) { 584 c.clientFactory = cf 585 } 586 587 // GetDialerName returns the name of the dialer 588 func (c *Client) GetDialerName() (dName string, err error) { 589 defer func() { 590 err := recover() 591 if err != nil { 592 dName = "unknown" 593 } 594 }() 595 596 opt := c.GetOptions() 597 if opt == nil || opt.Dialer == nil { 598 return "", fmt.Errorf("abnormal process: there is no client options or dialer") 599 } 600 601 dName = reflect.TypeOf(opt.Dialer).String() 602 dSlice := strings.Split(dName, ".") 603 dName = dSlice[0] 604 if dName[0] == '*' { 605 dName = dName[1:] 606 } 607 608 return 609 } 610 611 // NewClient return a client with options 612 func NewClient(opts ...config.ClientOption) (*Client, error) { 613 opt := config.NewClientOptions(opts) 614 if opt.Dialer == nil { 615 opt.Dialer = dialer.DefaultDialer() 616 } 617 c := &Client{ 618 options: opt, 619 m: make(map[string]client.HostClient), 620 ms: make(map[string]client.HostClient), 621 } 622 623 return c, nil 624 } 625 626 func (c *Client) Use(mws ...Middleware) { 627 // Put the original middlewares to the first 628 middlewares := make([]Middleware, 0, 1+len(mws)) 629 if c.mws != nil { 630 middlewares = append(middlewares, c.mws) 631 } 632 middlewares = append(middlewares, mws...) 633 c.mws = chain(middlewares...) 634 } 635 636 // UseAsLast is used to add middleware to the end of the middleware chain. 637 // 638 // Will return an error if last middleware has been set before, to ensure all middleware has the change to work, 639 // Please use `TakeOutLastMiddleware` to take out the already set middleware. 640 // Chain the middleware after or before is both Okay - but remember to put it back. 641 func (c *Client) UseAsLast(mw Middleware) error { 642 if c.lastMiddleware != nil { 643 return errorLastMiddlewareExist 644 } 645 c.lastMiddleware = mw 646 return nil 647 } 648 649 // TakeOutLastMiddleware will return the set middleware and remove it from client. 650 // 651 // Remember to set it back after chain it with other middleware. 652 func (c *Client) TakeOutLastMiddleware() Middleware { 653 last := c.lastMiddleware 654 c.lastMiddleware = nil 655 return last 656 } 657 658 func newHttp1OptionFromClient(c *Client) *http1.ClientOptions { 659 return &http1.ClientOptions{ 660 Name: c.options.Name, 661 NoDefaultUserAgentHeader: c.options.NoDefaultUserAgentHeader, 662 Dialer: c.options.Dialer, 663 DialTimeout: c.options.DialTimeout, 664 DialDualStack: c.options.DialDualStack, 665 TLSConfig: c.options.TLSConfig, 666 MaxConns: c.options.MaxConnsPerHost, 667 MaxConnDuration: c.options.MaxConnDuration, 668 MaxIdleConnDuration: c.options.MaxIdleConnDuration, 669 ReadTimeout: c.options.ReadTimeout, 670 WriteTimeout: c.options.WriteTimeout, 671 MaxResponseBodySize: c.options.MaxResponseBodySize, 672 DisableHeaderNamesNormalizing: c.options.DisableHeaderNamesNormalizing, 673 DisablePathNormalizing: c.options.DisablePathNormalizing, 674 MaxConnWaitTimeout: c.options.MaxConnWaitTimeout, 675 ResponseBodyStream: c.options.ResponseBodyStream, 676 RetryConfig: c.options.RetryConfig, 677 RetryIfFunc: c.RetryIfFunc, 678 StateObserve: c.options.HostClientStateObserve, 679 ObservationInterval: c.options.ObservationInterval, 680 } 681 }