325 lines
7.7 KiB
Go
325 lines
7.7 KiB
Go
|
|
package starnet
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"crypto/tls"
|
|||
|
|
"fmt"
|
|||
|
|
"net/http"
|
|||
|
|
"sync"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Client HTTP 客户端封装
|
|||
|
|
type Client struct {
|
|||
|
|
client *http.Client
|
|||
|
|
opts []RequestOpt
|
|||
|
|
mu sync.RWMutex
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewClient 创建新的 Client
|
|||
|
|
func NewClient(opts ...RequestOpt) (*Client, error) {
|
|||
|
|
// 创建基础 Transport
|
|||
|
|
baseTransport := &http.Transport{
|
|||
|
|
ForceAttemptHTTP2: true,
|
|||
|
|
MaxIdleConns: 100,
|
|||
|
|
MaxIdleConnsPerHost: 10,
|
|||
|
|
IdleConnTimeout: 90 * time.Second,
|
|||
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|||
|
|
ExpectContinueTimeout: 1 * time.Second,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
httpClient := &http.Client{
|
|||
|
|
Transport: &Transport{base: baseTransport},
|
|||
|
|
//Timeout: DefaultTimeout,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 应用选项(如果有)
|
|||
|
|
if len(opts) > 0 {
|
|||
|
|
// 创建临时请求以应用选项
|
|||
|
|
req, err := newRequest(context.Background(), "", http.MethodGet, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, wrapError(err, "create client")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
// 如果选项中有自定义配置,应用到 httpClient
|
|||
|
|
if req.config.Network.Timeout > 0 {
|
|||
|
|
httpClient.Timeout = req.config.Network.Timeout
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// 如果有自定义 Transport
|
|||
|
|
if req.config.CustomTransport && req.config.Transport != nil {
|
|||
|
|
httpClient.Transport = &Transport{base: req.config.Transport}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return &Client{
|
|||
|
|
client: httpClient,
|
|||
|
|
opts: opts,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewClientNoErr 创建新的 Client(忽略错误)
|
|||
|
|
func NewClientNoErr(opts ...RequestOpt) *Client {
|
|||
|
|
client, _ := NewClient(opts...)
|
|||
|
|
if client == nil {
|
|||
|
|
client = &Client{
|
|||
|
|
client: &http.Client{},
|
|||
|
|
opts: opts,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewClientFromHTTP 从 http.Client 创建 Client
|
|||
|
|
func NewClientFromHTTP(httpClient *http.Client) (*Client, error) {
|
|||
|
|
if httpClient == nil {
|
|||
|
|
return nil, ErrNilClient
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保 Transport 是我们的自定义类型
|
|||
|
|
if httpClient.Transport == nil {
|
|||
|
|
httpClient.Transport = &Transport{
|
|||
|
|
base: &http.Transport{},
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
switch t := httpClient.Transport.(type) {
|
|||
|
|
case *Transport:
|
|||
|
|
// 已经是我们的类型
|
|||
|
|
if t.base == nil {
|
|||
|
|
t.base = &http.Transport{}
|
|||
|
|
}
|
|||
|
|
case *http.Transport:
|
|||
|
|
// 包装标准 Transport
|
|||
|
|
httpClient.Transport = &Transport{
|
|||
|
|
base: t,
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
return nil, fmt.Errorf("unsupported transport type: %T", t)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return &Client{
|
|||
|
|
client: httpClient,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HTTPClient 获取底层 http.Client
|
|||
|
|
func (c *Client) HTTPClient() *http.Client {
|
|||
|
|
return c.client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RequestOptions 获取默认选项(返回副本)
|
|||
|
|
func (c *Client) RequestOptions() []RequestOpt {
|
|||
|
|
c.mu.RLock()
|
|||
|
|
defer c.mu.RUnlock()
|
|||
|
|
|
|||
|
|
opts := make([]RequestOpt, len(c.opts))
|
|||
|
|
copy(opts, c.opts)
|
|||
|
|
return opts
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetOptions 设置默认选项
|
|||
|
|
func (c *Client) SetOptions(opts ...RequestOpt) *Client {
|
|||
|
|
c.mu.Lock()
|
|||
|
|
c.opts = opts
|
|||
|
|
c.mu.Unlock()
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AddOptions 追加默认选项
|
|||
|
|
func (c *Client) AddOptions(opts ...RequestOpt) *Client {
|
|||
|
|
c.mu.Lock()
|
|||
|
|
c.opts = append(c.opts, opts...)
|
|||
|
|
c.mu.Unlock()
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Clone 克隆 Client(深拷贝)
|
|||
|
|
func (c *Client) Clone() *Client {
|
|||
|
|
c.mu.RLock()
|
|||
|
|
defer c.mu.RUnlock()
|
|||
|
|
|
|||
|
|
// 克隆 Transport
|
|||
|
|
var transport http.RoundTripper
|
|||
|
|
if c.client.Transport != nil {
|
|||
|
|
switch t := c.client.Transport.(type) {
|
|||
|
|
case *Transport:
|
|||
|
|
transport = &Transport{
|
|||
|
|
base: t.base.Clone(),
|
|||
|
|
}
|
|||
|
|
case *http.Transport:
|
|||
|
|
transport = t.Clone()
|
|||
|
|
default:
|
|||
|
|
transport = c.client.Transport
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return &Client{
|
|||
|
|
client: &http.Client{
|
|||
|
|
Transport: transport,
|
|||
|
|
CheckRedirect: c.client.CheckRedirect,
|
|||
|
|
Jar: c.client.Jar,
|
|||
|
|
Timeout: c.client.Timeout,
|
|||
|
|
},
|
|||
|
|
opts: append([]RequestOpt(nil), c.opts...),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetDefaultTLSConfig 设置默认 TLS 配置
|
|||
|
|
func (c *Client) SetDefaultTLSConfig(tlsConfig *tls.Config) *Client {
|
|||
|
|
if transport, ok := c.client.Transport.(*Transport); ok {
|
|||
|
|
transport.mu.Lock()
|
|||
|
|
if transport.base.TLSClientConfig == nil {
|
|||
|
|
transport.base.TLSClientConfig = &tls.Config{}
|
|||
|
|
}
|
|||
|
|
transport.base.TLSClientConfig = tlsConfig
|
|||
|
|
transport.mu.Unlock()
|
|||
|
|
}
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetDefaultSkipTLSVerify 设置默认跳过 TLS 验证
|
|||
|
|
func (c *Client) SetDefaultSkipTLSVerify(skip bool) *Client {
|
|||
|
|
if transport, ok := c.client.Transport.(*Transport); ok {
|
|||
|
|
transport.mu.Lock()
|
|||
|
|
if transport.base.TLSClientConfig == nil {
|
|||
|
|
transport.base.TLSClientConfig = &tls.Config{}
|
|||
|
|
}
|
|||
|
|
transport.base.TLSClientConfig.InsecureSkipVerify = skip
|
|||
|
|
transport.mu.Unlock()
|
|||
|
|
}
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DisableRedirect 禁用重定向
|
|||
|
|
func (c *Client) DisableRedirect() *Client {
|
|||
|
|
c.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|||
|
|
return http.ErrUseLastResponse
|
|||
|
|
}
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EnableRedirect 启用重定向
|
|||
|
|
func (c *Client) EnableRedirect() *Client {
|
|||
|
|
c.client.CheckRedirect = nil
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewRequest 创建新请求
|
|||
|
|
func (c *Client) NewRequest(url, method string, opts ...RequestOpt) (*Request, error) {
|
|||
|
|
return c.NewRequestWithContext(context.Background(), url, method, opts...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewRequestWithContext 创建新请求(带 context)
|
|||
|
|
func (c *Client) NewRequestWithContext(ctx context.Context, url, method string, opts ...RequestOpt) (*Request, error) {
|
|||
|
|
// 合并 Client 级别和请求级别的选项
|
|||
|
|
c.mu.RLock()
|
|||
|
|
allOpts := append(append([]RequestOpt(nil), c.opts...), opts...)
|
|||
|
|
c.mu.RUnlock()
|
|||
|
|
|
|||
|
|
req, err := newRequest(ctx, url, method, allOpts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
req.client = c
|
|||
|
|
req.httpClient = c.client
|
|||
|
|
return req, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Get 发送 GET 请求
|
|||
|
|
func (c *Client) Get(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodGet, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Post 发送 POST 请求
|
|||
|
|
func (c *Client) Post(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodPost, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Put 发送 PUT 请求
|
|||
|
|
func (c *Client) Put(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodPut, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Delete 发送 DELETE 请求
|
|||
|
|
func (c *Client) Delete(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodDelete, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Head 发送 HEAD 请求
|
|||
|
|
func (c *Client) Head(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodHead, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Patch 发送 PATCH 请求
|
|||
|
|
func (c *Client) Patch(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodPatch, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Options 发送 OPTIONS 请求
|
|||
|
|
func (c *Client) Options(url string, opts ...RequestOpt) (*Response, error) {
|
|||
|
|
req, err := c.NewRequest(url, http.MethodOptions, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
return req.Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewSimpleRequest 创建新请求(忽略错误,支持链式调用)
|
|||
|
|
func (c *Client) NewSimpleRequest(url, method string, opts ...RequestOpt) *Request {
|
|||
|
|
return c.NewSimpleRequestWithContext(context.Background(), url, method, opts...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewSimpleRequestWithContext 创建新请求(带 context,忽略错误)
|
|||
|
|
func (c *Client) NewSimpleRequestWithContext(ctx context.Context, url, method string, opts ...RequestOpt) *Request {
|
|||
|
|
req, err := c.NewRequestWithContext(ctx, url, method, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
// 返回一个带错误的请求,保持与全局 NewSimpleRequest 行为一致
|
|||
|
|
return &Request{
|
|||
|
|
ctx: ctx,
|
|||
|
|
url: url,
|
|||
|
|
method: method,
|
|||
|
|
err: err,
|
|||
|
|
config: &RequestConfig{
|
|||
|
|
Headers: make(http.Header),
|
|||
|
|
Queries: make(map[string][]string),
|
|||
|
|
Body: BodyConfig{
|
|||
|
|
FormData: make(map[string][]string),
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
client: c,
|
|||
|
|
httpClient: c.client,
|
|||
|
|
autoFetch: DefaultFetchRespBody,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return req
|
|||
|
|
}
|