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