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  }