// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. package client import ( "context" "crypto/rand" "crypto/tls" "fmt" "github.com/gogf/gf" "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gstr" "golang.org/x/net/proxy" "net" "net/http" "net/http/cookiejar" "net/url" "strings" "time" "github.com/gogf/gf/text/gregex" ) // Client is the HTTP client for HTTP request management. type Client struct { http.Client // Underlying HTTP Client. ctx context.Context // Context for each request. dump bool // Mark this request will be dumped. parent *Client // Parent http client, this is used for chaining operations. header map[string]string // Custom header map. cookies map[string]string // Custom cookie map. prefix string // Prefix for request. authUser string // HTTP basic authentication: user. authPass string // HTTP basic authentication: pass. retryCount int // Retry count when request fails. retryInterval time.Duration // Retry interval when request fails. middlewareHandler []HandlerFunc // Interceptor handlers } var ( defaultClientAgent = fmt.Sprintf(`GoFrameHTTPClient %s`, gf.VERSION) ) // New creates and returns a new HTTP client object. func New() *Client { client := &Client{ Client: http.Client{ Transport: &http.Transport{ // No validation for https certification of the server in default. TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, DisableKeepAlives: true, }, }, header: make(map[string]string), cookies: make(map[string]string), } client.header["User-Agent"] = defaultClientAgent return client } // Clone deeply clones current client and returns a new one. func (c *Client) Clone() *Client { newClient := New() *newClient = *c newClient.header = make(map[string]string) newClient.cookies = make(map[string]string) for k, v := range c.header { newClient.header[k] = v } for k, v := range c.cookies { newClient.cookies[k] = v } return newClient } // SetBrowserMode enables browser mode of the client. // When browser mode is enabled, it automatically saves and sends cookie content // from and to server. func (c *Client) SetBrowserMode(enabled bool) *Client { if enabled { jar, _ := cookiejar.New(nil) c.Jar = jar } return c } // SetHeader sets a custom HTTP header pair for the client. func (c *Client) SetHeader(key, value string) *Client { c.header[key] = value return c } // SetHeaderMap sets custom HTTP headers with map. func (c *Client) SetHeaderMap(m map[string]string) *Client { for k, v := range m { c.header[k] = v } return c } // SetAgent sets the User-Agent header for client. func (c *Client) SetAgent(agent string) *Client { c.header["User-Agent"] = agent return c } // SetContentType sets HTTP content type for the client. func (c *Client) SetContentType(contentType string) *Client { c.header["Content-Type"] = contentType return c } // SetHeaderRaw sets custom HTTP header using raw string. func (c *Client) SetHeaderRaw(headers string) *Client { for _, line := range gstr.SplitAndTrim(headers, "\n") { array, _ := gregex.MatchString(`^([\w\-]+):\s*(.+)`, line) if len(array) >= 3 { c.header[array[1]] = array[2] } } return c } // SetCookie sets a cookie pair for the client. func (c *Client) SetCookie(key, value string) *Client { c.cookies[key] = value return c } // SetDump enables/disables dump feature for this request. func (c *Client) SetDump(dump bool) *Client { c.dump = dump return c } // SetCookieMap sets cookie items with map. func (c *Client) SetCookieMap(m map[string]string) *Client { for k, v := range m { c.cookies[k] = v } return c } // SetPrefix sets the request server URL prefix. func (c *Client) SetPrefix(prefix string) *Client { c.prefix = prefix return c } // SetTimeout sets the request timeout for the client. func (c *Client) SetTimeout(t time.Duration) *Client { c.Client.Timeout = t return c } // SetBasicAuth sets HTTP basic authentication information for the client. func (c *Client) SetBasicAuth(user, pass string) *Client { c.authUser = user c.authPass = pass return c } // SetCtx sets context for each request of this client. func (c *Client) SetCtx(ctx context.Context) *Client { c.ctx = ctx return c } // SetRetry sets retry count and interval. func (c *Client) SetRetry(retryCount int, retryInterval time.Duration) *Client { c.retryCount = retryCount c.retryInterval = retryInterval return c } // SetRedirectLimit limit the number of jumps func (c *Client) SetRedirectLimit(redirectLimit int) *Client { c.CheckRedirect = func(req *http.Request, via []*http.Request) error { if len(via) >= redirectLimit { return http.ErrUseLastResponse } return nil } return c } // SetProxy set proxy for the client. // This func will do nothing when the parameter `proxyURL` is empty or in wrong pattern. // The correct pattern is like `http://USER:PASSWORD@IP:PORT` or `socks5://USER:PASSWORD@IP:PORT`. // Only `http` and `socks5` proxies are supported currently. func (c *Client) SetProxy(proxyURL string) { if strings.TrimSpace(proxyURL) == "" { return } _proxy, err := url.Parse(proxyURL) if err != nil { return } if _proxy.Scheme == "http" { if v, ok := c.Transport.(*http.Transport); ok { v.Proxy = http.ProxyURL(_proxy) } } else { var auth = &proxy.Auth{} user := _proxy.User.Username() if user != "" { auth.User = user password, hasPassword := _proxy.User.Password() if hasPassword && password != "" { auth.Password = password } } else { auth = nil } // refer to the source code, error is always nil dialer, err := proxy.SOCKS5( "tcp", _proxy.Host, auth, &net.Dialer{ Timeout: c.Client.Timeout, KeepAlive: c.Client.Timeout, }, ) if err != nil { return } if v, ok := c.Transport.(*http.Transport); ok { v.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { return dialer.Dial(network, addr) } } //c.SetTimeout(10*time.Second) } } // SetTLSKeyCrt sets the certificate and key file for TLS configuration of client. func (c *Client) SetTLSKeyCrt(crtFile, keyFile string) error { tlsConfig, err := LoadKeyCrt(crtFile, keyFile) if err != nil { return gerror.WrapCode(gcode.CodeInternalError, err, "LoadKeyCrt failed") } if v, ok := c.Transport.(*http.Transport); ok { tlsConfig.InsecureSkipVerify = true v.TLSClientConfig = tlsConfig return nil } return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`) } // SetTLSConfig sets the TLS configuration of client. func (c *Client) SetTLSConfig(tlsConfig *tls.Config) error { if v, ok := c.Transport.(*http.Transport); ok { v.TLSClientConfig = tlsConfig return nil } return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`) } // LoadKeyCrt creates and returns a TLS configuration object with given certificate and key files. func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) { crtPath, err := gfile.Search(crtFile) if err != nil { return nil, err } keyPath, err := gfile.Search(keyFile) if err != nil { return nil, err } crt, err := tls.LoadX509KeyPair(crtPath, keyPath) if err != nil { return nil, err } tlsConfig := &tls.Config{} tlsConfig.Certificates = []tls.Certificate{crt} tlsConfig.Time = time.Now tlsConfig.Rand = rand.Reader return tlsConfig, nil }