github.com/gogf/gf@v1.16.9/net/ghttp/internal/client/client.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package client
     8  
     9  import (
    10  	"context"
    11  	"crypto/rand"
    12  	"crypto/tls"
    13  	"fmt"
    14  	"github.com/gogf/gf"
    15  	"github.com/gogf/gf/errors/gcode"
    16  	"github.com/gogf/gf/errors/gerror"
    17  	"github.com/gogf/gf/os/gfile"
    18  	"github.com/gogf/gf/text/gstr"
    19  	"golang.org/x/net/proxy"
    20  	"net"
    21  	"net/http"
    22  	"net/http/cookiejar"
    23  	"net/url"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/gogf/gf/text/gregex"
    28  )
    29  
    30  // Client is the HTTP client for HTTP request management.
    31  type Client struct {
    32  	http.Client                         // Underlying HTTP Client.
    33  	ctx               context.Context   // Context for each request.
    34  	dump              bool              // Mark this request will be dumped.
    35  	parent            *Client           // Parent http client, this is used for chaining operations.
    36  	header            map[string]string // Custom header map.
    37  	cookies           map[string]string // Custom cookie map.
    38  	prefix            string            // Prefix for request.
    39  	authUser          string            // HTTP basic authentication: user.
    40  	authPass          string            // HTTP basic authentication: pass.
    41  	retryCount        int               // Retry count when request fails.
    42  	retryInterval     time.Duration     // Retry interval when request fails.
    43  	middlewareHandler []HandlerFunc     // Interceptor handlers
    44  }
    45  
    46  var (
    47  	defaultClientAgent = fmt.Sprintf(`GoFrameHTTPClient %s`, gf.VERSION)
    48  )
    49  
    50  // New creates and returns a new HTTP client object.
    51  func New() *Client {
    52  	client := &Client{
    53  		Client: http.Client{
    54  			Transport: &http.Transport{
    55  				// No validation for https certification of the server in default.
    56  				TLSClientConfig: &tls.Config{
    57  					InsecureSkipVerify: true,
    58  				},
    59  				DisableKeepAlives: true,
    60  			},
    61  		},
    62  		header:  make(map[string]string),
    63  		cookies: make(map[string]string),
    64  	}
    65  	client.header["User-Agent"] = defaultClientAgent
    66  	return client
    67  }
    68  
    69  // Clone deeply clones current client and returns a new one.
    70  func (c *Client) Clone() *Client {
    71  	newClient := New()
    72  	*newClient = *c
    73  	newClient.header = make(map[string]string)
    74  	newClient.cookies = make(map[string]string)
    75  	for k, v := range c.header {
    76  		newClient.header[k] = v
    77  	}
    78  	for k, v := range c.cookies {
    79  		newClient.cookies[k] = v
    80  	}
    81  	return newClient
    82  }
    83  
    84  // SetBrowserMode enables browser mode of the client.
    85  // When browser mode is enabled, it automatically saves and sends cookie content
    86  // from and to server.
    87  func (c *Client) SetBrowserMode(enabled bool) *Client {
    88  	if enabled {
    89  		jar, _ := cookiejar.New(nil)
    90  		c.Jar = jar
    91  	}
    92  	return c
    93  }
    94  
    95  // SetHeader sets a custom HTTP header pair for the client.
    96  func (c *Client) SetHeader(key, value string) *Client {
    97  	c.header[key] = value
    98  	return c
    99  }
   100  
   101  // SetHeaderMap sets custom HTTP headers with map.
   102  func (c *Client) SetHeaderMap(m map[string]string) *Client {
   103  	for k, v := range m {
   104  		c.header[k] = v
   105  	}
   106  	return c
   107  }
   108  
   109  // SetAgent sets the User-Agent header for client.
   110  func (c *Client) SetAgent(agent string) *Client {
   111  	c.header["User-Agent"] = agent
   112  	return c
   113  }
   114  
   115  // SetContentType sets HTTP content type for the client.
   116  func (c *Client) SetContentType(contentType string) *Client {
   117  	c.header["Content-Type"] = contentType
   118  	return c
   119  }
   120  
   121  // SetHeaderRaw sets custom HTTP header using raw string.
   122  func (c *Client) SetHeaderRaw(headers string) *Client {
   123  	for _, line := range gstr.SplitAndTrim(headers, "\n") {
   124  		array, _ := gregex.MatchString(`^([\w\-]+):\s*(.+)`, line)
   125  		if len(array) >= 3 {
   126  			c.header[array[1]] = array[2]
   127  		}
   128  	}
   129  	return c
   130  }
   131  
   132  // SetCookie sets a cookie pair for the client.
   133  func (c *Client) SetCookie(key, value string) *Client {
   134  	c.cookies[key] = value
   135  	return c
   136  }
   137  
   138  // SetDump enables/disables dump feature for this request.
   139  func (c *Client) SetDump(dump bool) *Client {
   140  	c.dump = dump
   141  	return c
   142  }
   143  
   144  // SetCookieMap sets cookie items with map.
   145  func (c *Client) SetCookieMap(m map[string]string) *Client {
   146  	for k, v := range m {
   147  		c.cookies[k] = v
   148  	}
   149  	return c
   150  }
   151  
   152  // SetPrefix sets the request server URL prefix.
   153  func (c *Client) SetPrefix(prefix string) *Client {
   154  	c.prefix = prefix
   155  	return c
   156  }
   157  
   158  // SetTimeout sets the request timeout for the client.
   159  func (c *Client) SetTimeout(t time.Duration) *Client {
   160  	c.Client.Timeout = t
   161  	return c
   162  }
   163  
   164  // SetBasicAuth sets HTTP basic authentication information for the client.
   165  func (c *Client) SetBasicAuth(user, pass string) *Client {
   166  	c.authUser = user
   167  	c.authPass = pass
   168  	return c
   169  }
   170  
   171  // SetCtx sets context for each request of this client.
   172  func (c *Client) SetCtx(ctx context.Context) *Client {
   173  	c.ctx = ctx
   174  	return c
   175  }
   176  
   177  // SetRetry sets retry count and interval.
   178  func (c *Client) SetRetry(retryCount int, retryInterval time.Duration) *Client {
   179  	c.retryCount = retryCount
   180  	c.retryInterval = retryInterval
   181  	return c
   182  }
   183  
   184  // SetRedirectLimit limit the number of jumps
   185  func (c *Client) SetRedirectLimit(redirectLimit int) *Client {
   186  	c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
   187  		if len(via) >= redirectLimit {
   188  			return http.ErrUseLastResponse
   189  		}
   190  		return nil
   191  	}
   192  	return c
   193  }
   194  
   195  // SetProxy set proxy for the client.
   196  // This func will do nothing when the parameter `proxyURL` is empty or in wrong pattern.
   197  // The correct pattern is like `http://USER:PASSWORD@IP:PORT` or `socks5://USER:PASSWORD@IP:PORT`.
   198  // Only `http` and `socks5` proxies are supported currently.
   199  func (c *Client) SetProxy(proxyURL string) {
   200  	if strings.TrimSpace(proxyURL) == "" {
   201  		return
   202  	}
   203  	_proxy, err := url.Parse(proxyURL)
   204  	if err != nil {
   205  		return
   206  	}
   207  	if _proxy.Scheme == "http" {
   208  		if v, ok := c.Transport.(*http.Transport); ok {
   209  			v.Proxy = http.ProxyURL(_proxy)
   210  		}
   211  	} else {
   212  		var auth = &proxy.Auth{}
   213  		user := _proxy.User.Username()
   214  
   215  		if user != "" {
   216  			auth.User = user
   217  			password, hasPassword := _proxy.User.Password()
   218  			if hasPassword && password != "" {
   219  				auth.Password = password
   220  			}
   221  		} else {
   222  			auth = nil
   223  		}
   224  		// refer to the source code, error is always nil
   225  		dialer, err := proxy.SOCKS5(
   226  			"tcp",
   227  			_proxy.Host,
   228  			auth,
   229  			&net.Dialer{
   230  				Timeout:   c.Client.Timeout,
   231  				KeepAlive: c.Client.Timeout,
   232  			},
   233  		)
   234  		if err != nil {
   235  			return
   236  		}
   237  		if v, ok := c.Transport.(*http.Transport); ok {
   238  			v.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
   239  				return dialer.Dial(network, addr)
   240  			}
   241  		}
   242  		//c.SetTimeout(10*time.Second)
   243  	}
   244  }
   245  
   246  // SetTLSKeyCrt sets the certificate and key file for TLS configuration of client.
   247  func (c *Client) SetTLSKeyCrt(crtFile, keyFile string) error {
   248  	tlsConfig, err := LoadKeyCrt(crtFile, keyFile)
   249  	if err != nil {
   250  		return gerror.WrapCode(gcode.CodeInternalError, err, "LoadKeyCrt failed")
   251  	}
   252  	if v, ok := c.Transport.(*http.Transport); ok {
   253  		tlsConfig.InsecureSkipVerify = true
   254  		v.TLSClientConfig = tlsConfig
   255  		return nil
   256  	}
   257  	return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`)
   258  }
   259  
   260  // SetTLSConfig sets the TLS configuration of client.
   261  func (c *Client) SetTLSConfig(tlsConfig *tls.Config) error {
   262  	if v, ok := c.Transport.(*http.Transport); ok {
   263  		v.TLSClientConfig = tlsConfig
   264  		return nil
   265  	}
   266  	return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`)
   267  }
   268  
   269  // LoadKeyCrt creates and returns a TLS configuration object with given certificate and key files.
   270  func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) {
   271  	crtPath, err := gfile.Search(crtFile)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	keyPath, err := gfile.Search(keyFile)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	crt, err := tls.LoadX509KeyPair(crtPath, keyPath)
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	tlsConfig := &tls.Config{}
   284  	tlsConfig.Certificates = []tls.Certificate{crt}
   285  	tlsConfig.Time = time.Now
   286  	tlsConfig.Rand = rand.Reader
   287  	return tlsConfig, nil
   288  }