starnet/request.go
2026-03-08 20:19:40 +08:00

333 lines
6.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
}