starnet/request.go

333 lines
6.9 KiB
Go
Raw Normal View History

2026-03-08 20:19:40 +08:00
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()
}