333 lines
6.9 KiB
Go
333 lines
6.9 KiB
Go
|
|
package starnet
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"fmt"
|
|||
|
|
"net/http"
|
|||
|
|
"net/url"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Request HTTP 请求
|
|||
|
|
type Request struct {
|
|||
|
|
ctx context.Context
|
|||
|
|
execCtx context.Context // 执行时的 context(注入了配置)
|
|||
|
|
url string
|
|||
|
|
method string
|
|||
|
|
err error // 累积的错误
|
|||
|
|
|
|||
|
|
config *RequestConfig
|
|||
|
|
client *Client
|
|||
|
|
httpClient *http.Client
|
|||
|
|
httpReq *http.Request
|
|||
|
|
|
|||
|
|
applied bool // 是否已应用配置
|
|||
|
|
doRaw bool // 是否使用原始请求(不修改)
|
|||
|
|
autoFetch bool // 是否自动获取响应体
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// newRequest 创建新请求(内部使用)
|
|||
|
|
func newRequest(ctx context.Context, urlStr string, method string, opts ...RequestOpt) (*Request, error) {
|
|||
|
|
if method == "" {
|
|||
|
|
method = http.MethodGet
|
|||
|
|
}
|
|||
|
|
method = strings.ToUpper(method)
|
|||
|
|
|
|||
|
|
// 创建 http.Request
|
|||
|
|
httpReq, err := http.NewRequestWithContext(ctx, method, urlStr, nil)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, wrapError(err, "create http request")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化配置
|
|||
|
|
config := &RequestConfig{
|
|||
|
|
Network: NetworkConfig{
|
|||
|
|
DialTimeout: DefaultDialTimeout,
|
|||
|
|
Timeout: DefaultTimeout,
|
|||
|
|
},
|
|||
|
|
Headers: make(http.Header),
|
|||
|
|
Queries: make(map[string][]string),
|
|||
|
|
Body: BodyConfig{
|
|||
|
|
FormData: make(map[string][]string),
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置默认 User-Agent
|
|||
|
|
config.Headers.Set("User-Agent", DefaultUserAgent)
|
|||
|
|
|
|||
|
|
// POST 请求默认 Content-Type
|
|||
|
|
if method == http.MethodPost {
|
|||
|
|
config.Headers.Set("Content-Type", ContentTypeFormURLEncoded)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
req := &Request{
|
|||
|
|
ctx: ctx,
|
|||
|
|
url: urlStr,
|
|||
|
|
method: method,
|
|||
|
|
config: config,
|
|||
|
|
httpReq: httpReq,
|
|||
|
|
autoFetch: DefaultFetchRespBody,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 应用选项
|
|||
|
|
for _, opt := range opts {
|
|||
|
|
if opt != nil {
|
|||
|
|
if err := opt(req); err != nil {
|
|||
|
|
req.err = err
|
|||
|
|
return req, nil // 不返回错误,累积到 req.err
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return req, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewRequest 创建新请求
|
|||
|
|
func NewRequest(url, method string, opts ...RequestOpt) (*Request, error) {
|
|||
|
|
return newRequest(context.Background(), url, method, opts...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewRequestWithContext 创建新请求(带 context)
|
|||
|
|
func NewRequestWithContext(ctx context.Context, url, method string, opts ...RequestOpt) (*Request, error) {
|
|||
|
|
return newRequest(ctx, url, method, opts...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewSimpleRequest 创建新请求(忽略错误,支持链式调用)
|
|||
|
|
func NewSimpleRequest(url, method string, opts ...RequestOpt) *Request {
|
|||
|
|
req, err := newRequest(context.Background(), url, method, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
// 返回一个带错误的请求
|
|||
|
|
return &Request{
|
|||
|
|
ctx: context.Background(),
|
|||
|
|
url: url,
|
|||
|
|
method: method,
|
|||
|
|
err: err,
|
|||
|
|
config: &RequestConfig{
|
|||
|
|
Headers: make(http.Header),
|
|||
|
|
Queries: make(map[string][]string),
|
|||
|
|
Body: BodyConfig{
|
|||
|
|
FormData: make(map[string][]string),
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return req
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewSimpleRequestWithContext 创建新请求(带 context,忽略错误)
|
|||
|
|
func NewSimpleRequestWithContext(ctx context.Context, url, method string, opts ...RequestOpt) *Request {
|
|||
|
|
req, err := newRequest(ctx, url, method, opts...)
|
|||
|
|
if err != nil {
|
|||
|
|
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),
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return req
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Clone 克隆请求
|
|||
|
|
func (r *Request) Clone() *Request {
|
|||
|
|
cloned := &Request{
|
|||
|
|
ctx: r.ctx,
|
|||
|
|
url: r.url,
|
|||
|
|
method: r.method,
|
|||
|
|
err: r.err,
|
|||
|
|
config: r.config.Clone(),
|
|||
|
|
client: r.client,
|
|||
|
|
httpClient: r.httpClient,
|
|||
|
|
applied: false, // 重置应用状态
|
|||
|
|
doRaw: r.doRaw,
|
|||
|
|
autoFetch: r.autoFetch,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新创建 http.Request
|
|||
|
|
if !r.doRaw {
|
|||
|
|
cloned.httpReq, _ = http.NewRequestWithContext(cloned.ctx, cloned.method, cloned.url, nil)
|
|||
|
|
} else {
|
|||
|
|
cloned.httpReq = r.httpReq
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return cloned
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Err 获取累积的错误
|
|||
|
|
func (r *Request) Err() error {
|
|||
|
|
return r.err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Context 获取 context
|
|||
|
|
func (r *Request) Context() context.Context {
|
|||
|
|
return r.ctx
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetContext 设置 context
|
|||
|
|
func (r *Request) SetContext(ctx context.Context) *Request {
|
|||
|
|
if r.err != nil {
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
r.ctx = ctx
|
|||
|
|
r.httpReq = r.httpReq.WithContext(ctx)
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Method 获取 HTTP 方法
|
|||
|
|
func (r *Request) Method() string {
|
|||
|
|
return r.method
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetMethod 设置 HTTP 方法
|
|||
|
|
func (r *Request) SetMethod(method string) *Request {
|
|||
|
|
if r.err != nil {
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
method = strings.ToUpper(method)
|
|||
|
|
if !validMethod(method) {
|
|||
|
|
r.err = wrapError(ErrInvalidMethod, "method: %s", method)
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.method = method
|
|||
|
|
r.httpReq.Method = method
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// URL 获取 URL
|
|||
|
|
func (r *Request) URL() string {
|
|||
|
|
return r.url
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetURL 设置 URL
|
|||
|
|
func (r *Request) SetURL(urlStr string) *Request {
|
|||
|
|
if r.err != nil {
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if r.doRaw {
|
|||
|
|
r.err = fmt.Errorf("cannot set URL when using raw request")
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
u, err := url.Parse(urlStr)
|
|||
|
|
if err != nil {
|
|||
|
|
r.err = wrapError(ErrInvalidURL, "url: %s", urlStr)
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.url = urlStr
|
|||
|
|
u.Host = removeEmptyPort(u.Host)
|
|||
|
|
r.httpReq.Host = u.Host
|
|||
|
|
r.httpReq.URL = u
|
|||
|
|
|
|||
|
|
// 更新 TLS ServerName
|
|||
|
|
if r.config.TLS.Config != nil {
|
|||
|
|
r.config.TLS.Config.ServerName = u.Hostname()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RawRequest 获取底层 http.Request
|
|||
|
|
func (r *Request) RawRequest() *http.Request {
|
|||
|
|
return r.httpReq
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetRawRequest 设置底层 http.Request(启用原始模式)
|
|||
|
|
func (r *Request) SetRawRequest(httpReq *http.Request) *Request {
|
|||
|
|
if r.err != nil {
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
r.httpReq = httpReq
|
|||
|
|
r.doRaw = true
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EnableRawMode 启用原始模式(不修改请求)
|
|||
|
|
func (r *Request) EnableRawMode() *Request {
|
|||
|
|
r.doRaw = true
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DisableRawMode 禁用原始模式
|
|||
|
|
func (r *Request) DisableRawMode() *Request {
|
|||
|
|
r.doRaw = false
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetAutoFetch 设置是否自动获取响应体
|
|||
|
|
func (r *Request) SetAutoFetch(auto bool) *Request {
|
|||
|
|
r.autoFetch = auto
|
|||
|
|
return r
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Do 执行请求
|
|||
|
|
func (r *Request) Do() (*Response, error) {
|
|||
|
|
// 检查累积的错误
|
|||
|
|
if r.err != nil {
|
|||
|
|
return nil, r.err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 准备请求
|
|||
|
|
if err := r.prepare(); err != nil {
|
|||
|
|
return nil, wrapError(err, "prepare request")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行请求
|
|||
|
|
httpResp, err := r.httpClient.Do(r.httpReq)
|
|||
|
|
if err != nil {
|
|||
|
|
return &Response{
|
|||
|
|
Response: &http.Response{},
|
|||
|
|
request: r,
|
|||
|
|
httpClient: r.httpClient,
|
|||
|
|
body: &Body{},
|
|||
|
|
}, wrapError(err, "do request")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建响应
|
|||
|
|
resp := &Response{
|
|||
|
|
Response: httpResp,
|
|||
|
|
request: r,
|
|||
|
|
httpClient: r.httpClient,
|
|||
|
|
body: &Body{
|
|||
|
|
raw: httpResp.Body,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自动获取响应体
|
|||
|
|
if r.autoFetch {
|
|||
|
|
resp.body.readAll()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return resp, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Get 发送 GET 请求
|
|||
|
|
func (r *Request) Get() (*Response, error) {
|
|||
|
|
return r.SetMethod(http.MethodGet).Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Post 发送 POST 请求
|
|||
|
|
func (r *Request) Post() (*Response, error) {
|
|||
|
|
return r.SetMethod(http.MethodPost).Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Put 发送 PUT 请求
|
|||
|
|
func (r *Request) Put() (*Response, error) {
|
|||
|
|
return r.SetMethod(http.MethodPut).Do()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Delete 发送 DELETE 请求
|
|||
|
|
func (r *Request) Delete() (*Response, error) {
|
|||
|
|
return r.SetMethod(http.MethodDelete).Do()
|
|||
|
|
}
|