2021-01-25 14:54:38 +08:00
|
|
|
// 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.
|
|
|
|
|
2021-12-03 23:32:00 +08:00
|
|
|
// Package gclient provides convenient http client functionalities.
|
|
|
|
package gclient
|
2021-01-25 14:54:38 +08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-02-23 22:00:11 +08:00
|
|
|
"crypto/rand"
|
2021-01-25 14:54:38 +08:00
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2021-03-11 18:58:13 +08:00
|
|
|
"net/http/cookiejar"
|
2021-01-25 14:54:38 +08:00
|
|
|
"net/url"
|
2021-12-10 18:08:36 +08:00
|
|
|
"os"
|
2021-01-25 14:54:38 +08:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2021-12-10 18:08:36 +08:00
|
|
|
"github.com/gogf/gf/v2/net/gtrace"
|
2021-11-16 19:43:02 +08:00
|
|
|
"golang.org/x/net/proxy"
|
|
|
|
|
2021-11-13 23:23:55 +08:00
|
|
|
"github.com/gogf/gf/v2"
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
"github.com/gogf/gf/v2/os/gfile"
|
2021-10-11 21:41:56 +08:00
|
|
|
"github.com/gogf/gf/v2/text/gregex"
|
2021-11-16 19:43:02 +08:00
|
|
|
"github.com/gogf/gf/v2/text/gstr"
|
2021-01-25 14:54:38 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// Client is the HTTP client for HTTP request management.
|
|
|
|
type Client struct {
|
|
|
|
http.Client // Underlying HTTP Client.
|
|
|
|
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 (
|
2021-12-10 18:08:36 +08:00
|
|
|
host, _ = os.Hostname()
|
|
|
|
defaultClientAgent = fmt.Sprintf(`GClient %s at %s`, gf.VERSION, host)
|
2021-01-25 14:54:38 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// New creates and returns a new HTTP client object.
|
|
|
|
func New() *Client {
|
2021-12-10 18:08:36 +08:00
|
|
|
c := &Client{
|
2021-01-25 14:54:38 +08:00
|
|
|
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),
|
|
|
|
}
|
2021-12-10 18:08:36 +08:00
|
|
|
c.header["User-Agent"] = defaultClientAgent
|
|
|
|
// It enables OpenTelemetry for client if tracing feature is enabled.
|
|
|
|
if gtrace.IsEnabled() {
|
|
|
|
c.Use(MiddlewareTracing)
|
|
|
|
}
|
|
|
|
return c
|
2021-01-25 14:54:38 +08:00
|
|
|
}
|
|
|
|
|
2021-01-30 23:05:02 +08:00
|
|
|
// Clone deeply clones current client and returns a new one.
|
2021-01-25 14:54:38 +08:00
|
|
|
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 {
|
2021-03-11 18:58:13 +08:00
|
|
|
if enabled {
|
|
|
|
jar, _ := cookiejar.New(nil)
|
|
|
|
c.Jar = jar
|
|
|
|
}
|
2021-01-25 14:54:38 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-08-02 19:58:04 +08:00
|
|
|
// SetTimeout sets the request timeout for the client.
|
2021-01-25 14:54:38 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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" {
|
2021-02-23 22:00:11 +08:00
|
|
|
if v, ok := c.Transport.(*http.Transport); ok {
|
|
|
|
v.Proxy = http.ProxyURL(_proxy)
|
2021-01-25 14:54:38 +08:00
|
|
|
}
|
|
|
|
} 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
|
|
|
|
}
|
2021-02-23 22:00:11 +08:00
|
|
|
if v, ok := c.Transport.(*http.Transport); ok {
|
|
|
|
v.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
|
2021-01-25 14:54:38 +08:00
|
|
|
return dialer.Dial(network, addr)
|
|
|
|
}
|
|
|
|
}
|
2021-11-13 23:23:55 +08:00
|
|
|
// c.SetTimeout(10*time.Second)
|
2021-01-25 14:54:38 +08:00
|
|
|
}
|
|
|
|
}
|
2021-02-23 22:00:11 +08:00
|
|
|
|
2021-07-20 23:02:02 +08:00
|
|
|
// SetTLSKeyCrt sets the certificate and key file for TLS configuration of client.
|
2021-02-23 22:00:11 +08:00
|
|
|
func (c *Client) SetTLSKeyCrt(crtFile, keyFile string) error {
|
|
|
|
tlsConfig, err := LoadKeyCrt(crtFile, keyFile)
|
|
|
|
if err != nil {
|
2021-08-24 21:18:59 +08:00
|
|
|
return gerror.WrapCode(gcode.CodeInternalError, err, "LoadKeyCrt failed")
|
2021-02-23 22:00:11 +08:00
|
|
|
}
|
|
|
|
if v, ok := c.Transport.(*http.Transport); ok {
|
|
|
|
tlsConfig.InsecureSkipVerify = true
|
|
|
|
v.TLSClientConfig = tlsConfig
|
|
|
|
return nil
|
|
|
|
}
|
2021-08-24 21:18:59 +08:00
|
|
|
return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`)
|
2021-02-23 22:00:11 +08:00
|
|
|
}
|
|
|
|
|
2021-07-20 23:02:02 +08:00
|
|
|
// SetTLSConfig sets the TLS configuration of client.
|
2021-02-23 22:00:11 +08:00
|
|
|
func (c *Client) SetTLSConfig(tlsConfig *tls.Config) error {
|
|
|
|
if v, ok := c.Transport.(*http.Transport); ok {
|
|
|
|
v.TLSClientConfig = tlsConfig
|
|
|
|
return nil
|
|
|
|
}
|
2021-08-24 21:18:59 +08:00
|
|
|
return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`)
|
2021-02-23 22:00:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|