starnet/curl.go

1866 lines
58 KiB
Go
Raw Normal View History

2020-07-20 11:17:29 +08:00
package starnet
import (
"bytes"
2022-03-11 09:29:09 +08:00
"context"
2022-03-14 15:43:56 +08:00
"crypto/tls"
2024-08-08 22:03:10 +08:00
"encoding/json"
2020-07-20 11:17:29 +08:00
"fmt"
"io"
2024-08-08 22:03:10 +08:00
"mime/multipart"
2020-07-20 11:17:29 +08:00
"net"
"net/http"
"net/url"
2020-07-21 09:20:02 +08:00
"os"
2020-10-19 21:04:56 +08:00
"strings"
2025-04-28 13:19:45 +08:00
"sync"
2020-07-20 11:17:29 +08:00
"time"
)
type Request struct {
2024-08-08 22:03:10 +08:00
ctx context.Context
2025-08-21 15:02:02 +08:00
doCtx context.Context // 用于在请求中传递上下文信息
2024-08-08 22:03:10 +08:00
uri string
method string
errInfo error
2022-03-14 15:43:56 +08:00
RequestOpts
}
2025-04-28 13:19:45 +08:00
func (r *Request) Clone() *Request {
clonedRequest := &Request{
ctx: r.ctx,
uri: r.uri,
method: r.method,
errInfo: r.errInfo,
RequestOpts: RequestOpts{
headers: CloneHeader(r.headers),
cookies: CloneCookies(r.cookies),
bodyFormData: CloneStringMapSlice(r.bodyFormData),
bodyFileData: CloneFiles(r.bodyFileData),
contentLength: r.contentLength,
2025-04-28 13:19:45 +08:00
queries: CloneStringMapSlice(r.queries),
bodyDataBytes: CloneByteSlice(r.bodyDataBytes),
2025-08-21 15:32:19 +08:00
customTransport: r.customTransport,
2025-04-28 13:19:45 +08:00
proxy: r.proxy,
timeout: r.timeout,
dialTimeout: r.dialTimeout,
2025-06-12 16:50:47 +08:00
dialFn: r.dialFn,
2025-04-28 13:19:45 +08:00
alreadyApply: r.alreadyApply,
doRawRequest: r.doRawRequest,
skipTLSVerify: r.skipTLSVerify,
autoFetchRespBody: r.autoFetchRespBody,
customIP: CloneStringSlice(r.customIP),
alreadySetLookUpIPfn: r.alreadySetLookUpIPfn,
lookUpIPfn: r.lookUpIPfn,
customDNS: CloneStringSlice(r.customDNS),
basicAuth: r.basicAuth,
autoCalcContentLength: r.autoCalcContentLength,
},
}
2025-08-21 15:02:02 +08:00
clonedRequest.rawClient = r.rawClient
2025-04-28 13:19:45 +08:00
// 手动深拷贝嵌套引用类型
if r.bodyDataReader != nil {
clonedRequest.bodyDataReader = r.bodyDataReader
}
2025-08-21 15:02:02 +08:00
if r.fileUploadRecallFn != nil {
clonedRequest.fileUploadRecallFn = r.fileUploadRecallFn
2025-04-28 13:19:45 +08:00
}
// 对于 tlsConfig 类型,需要手动复制
if r.tlsConfig != nil {
2025-08-21 15:02:02 +08:00
clonedRequest.tlsConfig = r.tlsConfig.Clone()
2025-04-28 13:19:45 +08:00
}
if r.transport != nil {
2025-08-21 15:02:02 +08:00
clonedRequest.transport = r.transport
}
if r.doRawRequest {
clonedRequest.rawRequest = r.rawRequest
2025-04-28 13:19:45 +08:00
}
2025-08-13 10:16:08 +08:00
if clonedRequest.rawRequest == nil {
clonedRequest.rawRequest, _ = http.NewRequestWithContext(clonedRequest.ctx, clonedRequest.method, clonedRequest.uri, nil)
}
2025-04-28 13:19:45 +08:00
return clonedRequest
}
// CloneHeader 复制 http.Header
func CloneHeader(original http.Header) http.Header {
newHeader := make(http.Header)
for key, values := range original {
copiedValues := make([]string, len(values))
copy(copiedValues, values)
newHeader[key] = copiedValues
}
return newHeader
}
// CloneCookies 复制 []*http.Cookie
func CloneCookies(original []*http.Cookie) []*http.Cookie {
cloned := make([]*http.Cookie, len(original))
for i, cookie := range original {
cloned[i] = &http.Cookie{
Name: cookie.Name,
Value: cookie.Value,
Path: cookie.Path,
Domain: cookie.Domain,
Expires: cookie.Expires,
RawExpires: cookie.RawExpires,
MaxAge: cookie.MaxAge,
Secure: cookie.Secure,
HttpOnly: cookie.HttpOnly,
SameSite: cookie.SameSite,
Raw: cookie.Raw,
Unparsed: append([]string(nil), cookie.Unparsed...),
}
}
return cloned
}
// CloneStringMapSlice 复制 map[string][]string
func CloneStringMapSlice(original map[string][]string) map[string][]string {
newMap := make(map[string][]string)
for key, values := range original {
copiedValues := make([]string, len(values))
copy(copiedValues, values)
newMap[key] = copiedValues
}
return newMap
}
// CloneFiles 复制 []RequestFile
func CloneFiles(original []RequestFile) []RequestFile {
newFiles := make([]RequestFile, len(original))
copy(newFiles, original)
return newFiles
}
// CloneByteSlice 复制 []byte
func CloneByteSlice(original []byte) []byte {
if original == nil {
return nil
}
newSlice := make([]byte, len(original))
copy(newSlice, original)
return newSlice
}
// CloneStringSlice 复制 []string
func CloneStringSlice(original []string) []string {
newSlice := make([]string, len(original))
copy(newSlice, original)
return newSlice
}
2024-08-08 22:03:10 +08:00
func (r *Request) Method() string {
return r.method
}
func (r *Request) SetMethod(method string) error {
method = strings.ToUpper(method)
if !validMethod(method) {
return fmt.Errorf("invalid method: %s", method)
}
r.method = method
r.rawRequest.Method = method
return nil
}
func (r *Request) SetMethodNoError(method string) *Request {
r.SetMethod(method)
return r
}
func (r *Request) Uri() string {
return r.uri
}
func (r *Request) SetUri(uri string) error {
2025-08-21 15:02:02 +08:00
if r.doRawRequest {
return fmt.Errorf("doRawRequest is true, cannot set uri")
}
2024-08-08 22:03:10 +08:00
u, err := url.Parse(uri)
if err != nil {
return fmt.Errorf("parse uri error: %s", err)
}
r.uri = uri
u.Host = removeEmptyPort(u.Host)
r.rawRequest.Host = u.Host
r.rawRequest.URL = u
2025-08-21 15:02:02 +08:00
if r.tlsConfig != nil {
r.tlsConfig.ServerName = u.Hostname()
}
2024-08-08 22:03:10 +08:00
return nil
}
func (r *Request) SetUriNoError(uri string) *Request {
r.SetUri(uri)
return r
}
func (r *Request) RawRequest() *http.Request {
return r.rawRequest
}
func (r *Request) SetRawRequest(rawRequest *http.Request) *Request {
r.rawRequest = rawRequest
return r
}
func (r *Request) RawClient() *http.Client {
return r.rawClient
}
func (r *Request) SetRawClient(rawClient *http.Client) *Request {
r.rawClient = rawClient
return r
}
2025-08-21 15:02:02 +08:00
// Do sends the HTTP request and returns the response.
2024-08-08 22:03:10 +08:00
func (r *Request) Do() (*Response, error) {
return Curl(r)
}
2025-08-21 15:02:02 +08:00
// Get sends a GET request to the specified URI and returns the response.
2024-08-08 22:03:10 +08:00
func (r *Request) Get() (*Response, error) {
err := r.SetMethod("GET")
if err != nil {
return nil, err
}
return Curl(r)
}
2025-08-21 15:02:02 +08:00
// Post sends a POST request with the provided data to the specified URI and returns the response.
2024-08-08 22:03:10 +08:00
func (r *Request) Post(data []byte) (*Response, error) {
err := r.SetMethod("POST")
if err != nil {
return nil, err
}
r.bodyDataBytes = data
r.bodyDataReader = nil
return Curl(r)
}
2022-03-14 15:43:56 +08:00
type RequestOpts struct {
2025-08-21 15:02:02 +08:00
rawRequest *http.Request
rawClient *http.Client
transport *http.Transport
customTransport bool
2024-08-08 22:03:10 +08:00
alreadyApply bool
bodyDataBytes []byte
bodyDataReader io.Reader
bodyFormData map[string][]string
bodyFileData []RequestFile
//以上优先度为 bodyDataReader> bodyDataBytes > bodyFormData > bodyFileData
2025-08-21 15:02:02 +08:00
fileUploadRecallFn func(filename string, upPos int64, total int64)
2024-08-08 22:03:10 +08:00
proxy string
timeout time.Duration
dialTimeout time.Duration
2025-06-12 16:50:47 +08:00
dialFn func(ctx context.Context, network, addr string) (net.Conn, error)
2024-08-08 22:03:10 +08:00
headers http.Header
cookies []*http.Cookie
2025-08-21 15:02:02 +08:00
queries map[string][]string
2024-08-08 22:03:10 +08:00
//doRawRequest=true 不对request修改直接发送
2025-08-21 15:02:02 +08:00
doRawRequest bool
2024-08-08 22:03:10 +08:00
skipTLSVerify bool
tlsConfig *tls.Config
autoFetchRespBody bool
customIP []string
alreadySetLookUpIPfn bool
lookUpIPfn func(ctx context.Context, host string) ([]net.IPAddr, error)
customDNS []string
basicAuth [2]string
autoCalcContentLength bool
contentLength int64 // 人工设置
}
2025-08-21 21:37:21 +08:00
func (r *Request) ContentLength() int64 {
return r.contentLength
}
// SetContentLength sets the Content-Length header for the request.
// This function will overwrite any existing or auto calculated Content-Length header.
// if the length is less than 0, it will not set the Content-Length header. chunked transfer encoding will be used instead.
// chunked transfer encoding may cause some servers to reject the request if they do not support it.
// Note that this function will not work if doRawRequest is true
2025-08-21 21:37:21 +08:00
func (r *Request) SetContentLength(contextLength int64) *Request {
r.contentLength = contextLength
2025-08-21 21:37:21 +08:00
return r
2024-08-08 22:03:10 +08:00
}
2025-08-21 21:37:21 +08:00
func (r *Request) CustomTransport() bool {
2025-08-21 15:02:02 +08:00
return r.customTransport
}
2025-08-21 21:37:21 +08:00
func (r *Request) SetCustomTransport(customTransport bool) *Request {
2025-08-21 15:02:02 +08:00
r.customTransport = customTransport
2025-08-21 21:37:21 +08:00
return r
2025-08-21 15:02:02 +08:00
}
2025-08-21 21:37:21 +08:00
func (r *Request) FileUploadRecallFn() func(filename string, upPos int64, total int64) {
2025-08-21 15:02:02 +08:00
return r.fileUploadRecallFn
}
2025-08-21 21:37:21 +08:00
func (r *Request) SetFileUploadRecallFn(FileUploadRecallFn func(filename string, upPos int64, total int64)) *Request {
2025-08-21 15:02:02 +08:00
r.fileUploadRecallFn = FileUploadRecallFn
2025-08-21 21:37:21 +08:00
return r
2025-08-21 15:02:02 +08:00
}
2025-06-12 16:50:47 +08:00
func (r *Request) DialFn() func(ctx context.Context, network, addr string) (net.Conn, error) {
return r.dialFn
}
2025-08-21 15:02:02 +08:00
// SetDialFn sets the dial function for the request.
2025-06-12 16:50:47 +08:00
func (r *Request) SetDialFn(dialFn func(ctx context.Context, network, addr string) (net.Conn, error)) {
r.dialFn = dialFn
}
2024-08-08 22:03:10 +08:00
func (r *Request) AutoCalcContentLength() bool {
return r.autoCalcContentLength
}
// SetAutoCalcContentLength sets whether to automatically calculate the Content-Length header based on the request body.
// WARN: If set to true, the Content-Length header will be set to the length of the request body, data will be cached in memory.
// So it may cause high memory usage if the request body is large.
// If set to false, the Content-Length header will not be set,unless the request body is a byte slice or bytes.Buffer which has a specific length.
// Note that this function will not work if doRawRequest is true or the ContentLength is already set.
2025-08-21 15:02:02 +08:00
func (r *Request) SetAutoCalcContentLength(autoCalcContentLength bool) error {
if r.doRawRequest {
return fmt.Errorf("doRawRequest is true, cannot set autoCalcContentLength")
}
2024-08-08 22:03:10 +08:00
r.autoCalcContentLength = autoCalcContentLength
2025-08-21 15:02:02 +08:00
return nil
}
func (r *Request) SetAutoCalcContentLengthNoError(autoCalcContentLength bool) *Request {
r.SetAutoCalcContentLength(autoCalcContentLength)
2024-08-08 22:03:10 +08:00
return r
}
// BasicAuth returns the username and password provided in the request's Authorization header.
2025-08-21 21:37:21 +08:00
func (r *Request) BasicAuth() (string, string) {
2024-08-08 22:03:10 +08:00
return r.basicAuth[0], r.basicAuth[1]
}
// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
// Note: If doRawRequest is true, this function will nolonger work
2025-08-21 21:37:21 +08:00
func (r *Request) SetBasicAuth(username, password string) *Request {
2024-08-08 22:03:10 +08:00
r.basicAuth = [2]string{username, password}
return r
}
func (r *Request) CustomDNS() []string {
return r.customDNS
}
2025-08-21 15:02:02 +08:00
// SetCustomDNS sets the custom DNS servers for the request.
// Note: if LookUpIPfn is set, this function will not be used.
// if use custom Transport Dialer, this function will not work by default,but if the *http.Client is create by this package, it will work
2024-08-08 22:03:10 +08:00
func (r *Request) SetCustomDNS(customDNS []string) error {
for _, v := range customDNS {
if net.ParseIP(v) == nil {
return fmt.Errorf("invalid custom dns: %s", v)
}
}
r.customDNS = customDNS
return nil
}
2025-08-21 15:02:02 +08:00
// SetCustomDNSNoError sets the custom DNS servers for the request.
// Note: if LookUpIPfn is set, this function will not be used.
// if use custom Transport Dialer, this function will not work by default,but if the *http.Client is create by this package, it will work
2024-08-08 22:03:10 +08:00
func (r *Request) SetCustomDNSNoError(customDNS []string) *Request {
r.SetCustomDNS(customDNS)
return r
}
2025-08-21 15:02:02 +08:00
// AddCustomDNS adds custom DNS servers to the request.
// Note: if LookUpIPfn is set, this function will not be used.
// if use custom Transport Dialer, this function will not work by default,but if the *http.Client is create by this package, it will work
2024-08-08 22:03:10 +08:00
func (r *Request) AddCustomDNS(customDNS []string) error {
for _, v := range customDNS {
if net.ParseIP(v) == nil {
return fmt.Errorf("invalid custom dns: %s", v)
}
}
r.customDNS = customDNS
return nil
}
2025-08-21 15:02:02 +08:00
// AddCustomDNSNoError adds custom DNS servers to the request.
// Note: if LookUpIPfn is set, this function will not be used.
// if use custom Transport Dialer, this function will not work by default,but if the *http.Client is create by this package, it will work
2024-08-08 22:03:10 +08:00
func (r *Request) AddCustomDNSNoError(customDNS []string) *Request {
r.AddCustomDNS(customDNS)
return r
}
func (r *Request) LookUpIPfn() func(ctx context.Context, host string) ([]net.IPAddr, error) {
return r.lookUpIPfn
}
2025-08-21 15:02:02 +08:00
// SetLookUpIPfn sets the function used to look up IP addresses for a given host.
// If lookUpIPfn is nil, it will use the default resolver's LookupIPAddr function.
// Note: if use custom Transport Dialer, this function will not work by default,but if the *http.Client is create by this package, it will work
// Note: if CustomHostIP is set, this function will not be used.
2024-08-08 22:03:10 +08:00
func (r *Request) SetLookUpIPfn(lookUpIPfn func(ctx context.Context, host string) ([]net.IPAddr, error)) *Request {
if lookUpIPfn == nil {
r.alreadySetLookUpIPfn = false
r.lookUpIPfn = net.DefaultResolver.LookupIPAddr
return r
}
r.lookUpIPfn = lookUpIPfn
r.alreadySetLookUpIPfn = true
return r
}
2025-08-21 15:02:02 +08:00
// CustomHostIP returns the custom IP addresses used for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) CustomHostIP() []string {
return r.customIP
}
2025-08-21 15:02:02 +08:00
// SetCustomHostIP sets the custom IP addresses used for the request.
// if you want to use a specific IP address for a host without DNS resolution, you can set this.
// Set nil to clear the custom IP addresses.
// Note: lookUpIPfn will not be used if customIP is set.
2024-08-08 22:03:10 +08:00
func (r *Request) SetCustomHostIP(customIP []string) *Request {
r.customIP = customIP
return r
}
2025-08-21 15:02:02 +08:00
// AddCustomHostIP adds a custom IP address to the request.
2024-08-08 22:03:10 +08:00
func (r *Request) AddCustomHostIP(customIP string) *Request {
r.customIP = append(r.customIP, customIP)
return r
}
2025-08-21 15:02:02 +08:00
// BodyDataBytes returns the raw body data as a byte slice.
2024-08-08 22:03:10 +08:00
func (r *Request) BodyDataBytes() []byte {
return r.bodyDataBytes
}
2025-08-21 15:02:02 +08:00
// SetBodyDataBytes sets the raw body data for the request.
// The priority order of the data is: bodyDataReader > **bodyDataBytes** > bodyFormData > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetBodyDataBytes(bodyDataBytes []byte) *Request {
r.bodyDataBytes = bodyDataBytes
return r
}
2025-08-21 15:02:02 +08:00
// BodyDataReader returns the raw body data as an io.Reader.
2024-08-08 22:03:10 +08:00
func (r *Request) BodyDataReader() io.Reader {
return r.bodyDataReader
}
2025-08-21 15:02:02 +08:00
// SetBodyDataReader sets the raw body data for the request as an io.Reader.
// The priority order of the data is: **bodyDataReader** > bodyDataBytes > bodyFormData > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetBodyDataReader(bodyDataReader io.Reader) *Request {
r.bodyDataReader = bodyDataReader
return r
}
2025-08-21 15:02:02 +08:00
// BodyFormData returns the form data as a map of string slices.
// The priority order of the data is: bodyDataReader > bodyDataBytes > **bodyFormData** > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) BodyFormData() map[string][]string {
return r.bodyFormData
}
2025-08-21 15:02:02 +08:00
// SetBodyFormData sets the form data for the request.
// The priority order of the data is: bodyDataReader > bodyDataBytes > **bodyFormData** > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetBodyFormData(bodyFormData map[string][]string) *Request {
r.bodyFormData = bodyFormData
return r
}
2025-08-21 15:02:02 +08:00
// SetFormData is an alias for SetBodyFormData.
// It allows you to set form data in the request body.
// This is useful when you want to use a more descriptive name for the function.
// The priority order of the data is: bodyDataReader > bodyDataBytes > **bodyFormData** > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetFormData(bodyFormData map[string][]string) *Request {
return r.SetBodyFormData(bodyFormData)
}
2025-08-21 15:02:02 +08:00
// AddFormMapData adds form data from a map to the request body.
// The priority order of the data is: bodyDataReader > bodyDataBytes > **bodyFormData** > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFormMapData(bodyFormData map[string]string) *Request {
for k, v := range bodyFormData {
r.bodyFormData[k] = append(r.bodyFormData[k], v)
}
return r
}
2025-08-21 15:02:02 +08:00
// AddFormData adds a single key-value pair to the form data in the request body.
// The priority order of the data is: bodyDataReader > bodyDataBytes > **bodyFormData** > bodyFileData.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFormData(k, v string) *Request {
r.bodyFormData[k] = append(r.bodyFormData[k], v)
return r
}
2025-08-21 15:02:02 +08:00
// BodyFileData returns the file data as a slice of RequestFile.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) BodyFileData() []RequestFile {
return r.bodyFileData
}
2025-08-21 15:02:02 +08:00
// SetBodyFileData sets the file data for the request.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetBodyFileData(bodyFileData []RequestFile) *Request {
r.bodyFileData = bodyFileData
return r
}
2025-08-21 15:02:02 +08:00
// Proxy returns the proxy URL for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) Proxy() string {
return r.proxy
}
2025-08-21 15:02:02 +08:00
// SetProxy sets the proxy URL for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) SetProxy(proxy string) *Request {
r.proxy = proxy
return r
}
2025-08-21 15:02:02 +08:00
// Timeout returns the timeout duration for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) Timeout() time.Duration {
return r.timeout
}
2025-08-21 15:02:02 +08:00
// SetTimeout sets the timeout duration for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) SetTimeout(timeout time.Duration) *Request {
r.timeout = timeout
return r
}
2025-08-21 15:02:02 +08:00
// DialTimeout returns the dial timeout duration for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) DialTimeout() time.Duration {
return r.dialTimeout
}
2025-08-21 15:02:02 +08:00
// SetDialTimeout sets the dial timeout duration for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) SetDialTimeout(dialTimeout time.Duration) *Request {
r.dialTimeout = dialTimeout
return r
}
2025-08-21 15:02:02 +08:00
// Headers returns the request headers as an http.Header.
2024-08-08 22:03:10 +08:00
func (r *Request) Headers() http.Header {
return r.headers
}
2025-08-21 15:02:02 +08:00
// SetHeaders sets the request headers using an http.Header.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetHeaders(headers http.Header) *Request {
r.headers = headers
return r
}
2025-08-21 15:02:02 +08:00
// AddHeader adds a single header to the request.
// This function will append the header if it already exists.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) AddHeader(key, val string) *Request {
r.headers.Add(key, val)
return r
}
2025-08-21 15:02:02 +08:00
// SetHeader sets a single header in the request.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetHeader(key, val string) *Request {
r.headers.Set(key, val)
return r
}
2025-08-21 15:02:02 +08:00
// DeleteHeader removes a header from the request.
// if the header has multiple values, it will remove all values for that header.
// Note: If doRawRequest is true, this function will not work.
func (r *Request) DeleteHeader(key string) *Request {
r.headers.Del(key)
return r
}
// SetContentType sets the Content-Type header for the request.
// This function will overwrite any existing Content-Type header.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetContentType(ct string) *Request {
r.headers.Set("Content-Type", ct)
return r
}
2025-08-21 15:02:02 +08:00
// SetUserAgent sets the User-Agent header for the request.
// This function will overwrite any existing User-Agent header.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetUserAgent(ua string) *Request {
r.headers.Set("User-Agent", ua)
return r
}
2025-08-21 15:02:02 +08:00
// Cookies returns the request cookies as a slice of http.Cookie.
2024-08-08 22:03:10 +08:00
func (r *Request) Cookies() []*http.Cookie {
return r.cookies
}
2025-08-21 15:02:02 +08:00
// SetCookies sets the request cookies using a slice of http.Cookie.
// you can also use SetHeader("Cookie", "cookie1=value1; cookie2=value2") to set cookies.
// Note: If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetCookies(cookies []*http.Cookie) *Request {
r.cookies = cookies
return r
}
2025-08-21 15:02:02 +08:00
// Transport returns the http.Transport used for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) Transport() *http.Transport {
return r.transport
}
2025-08-21 15:02:02 +08:00
// SetTransport set the http.Transport used for the request.
// Note: If doRawClient is true, this function will not work.
2024-08-08 22:03:10 +08:00
func (r *Request) SetTransport(transport *http.Transport) *Request {
r.transport = transport
2025-08-21 15:02:02 +08:00
r.customTransport = true
2024-08-08 22:03:10 +08:00
return r
}
2025-08-21 15:02:02 +08:00
// Queries returns the request queries as a map of string slices.
2024-08-08 22:03:10 +08:00
func (r *Request) Queries() map[string][]string {
return r.queries
}
2025-08-21 15:02:02 +08:00
// SetQueries sets the request queries using a map of string slices.
2024-08-08 22:03:10 +08:00
func (r *Request) SetQueries(queries map[string][]string) *Request {
r.queries = queries
return r
}
2025-08-21 15:02:02 +08:00
// AddQueries adds multiple query parameters to the request.
2024-08-08 22:03:10 +08:00
func (r *Request) AddQueries(queries map[string]string) *Request {
for k, v := range queries {
r.queries[k] = append(r.queries[k], v)
}
return r
}
2025-08-21 15:02:02 +08:00
// AddQuery adds a single query parameter to the request.
2024-08-08 22:03:10 +08:00
func (r *Request) AddQuery(key, value string) *Request {
r.queries[key] = append(r.queries[key], value)
return r
}
2025-08-21 15:02:02 +08:00
// DelQueryKv removes a specific value from a query parameter.
2024-08-08 22:03:10 +08:00
func (r *Request) DelQueryKv(key, value string) *Request {
if _, ok := r.queries[key]; !ok {
return r
}
for i, v := range r.queries[key] {
if v == value {
r.queries[key] = append(r.queries[key][:i], r.queries[key][i+1:]...)
}
}
return r
}
2025-08-21 15:02:02 +08:00
// DelQuery removes a query parameter from the request.
2024-08-08 22:03:10 +08:00
func (r *Request) DelQuery(key string) *Request {
if _, ok := r.queries[key]; !ok {
return r
}
delete(r.queries, key)
return r
}
2025-08-21 15:02:02 +08:00
// DoRawRequest returns whether the request will be sent as a raw request.
2024-08-08 22:03:10 +08:00
func (r *Request) DoRawRequest() bool {
return r.doRawRequest
}
2025-08-21 15:02:02 +08:00
// SetDoRawRequest sets whether the request will be sent as a raw request without any modifications.
// you can use this with function SetRawRequest to set a custom http.Request.
2024-08-08 22:03:10 +08:00
func (r *Request) SetDoRawRequest(doRawRequest bool) *Request {
r.doRawRequest = doRawRequest
return r
}
2025-08-21 15:02:02 +08:00
// SkipTLSVerify returns whether the request will skip TLS verification.
2025-08-21 21:37:21 +08:00
func (r *Request) SkipTLSVerify() bool {
2024-08-08 22:03:10 +08:00
return r.skipTLSVerify
}
2025-08-21 15:02:02 +08:00
// SetSkipTLSVerify Sets whether the request will skip TLS verification.
2024-08-08 22:03:10 +08:00
func (r *Request) SetSkipTLSVerify(skipTLSVerify bool) *Request {
r.skipTLSVerify = skipTLSVerify
return r
}
2025-08-21 15:02:02 +08:00
// TlsConfig returns the TLS configuration used for the request.
2024-08-08 22:03:10 +08:00
func (r *Request) TlsConfig() *tls.Config {
return r.tlsConfig
}
2025-08-21 15:02:02 +08:00
// SetTlsConfig sets the TLS configuration for the request.
// Note: If you use SetSkipTLSVerify function, it will automatically set the InsecureSkipVerify field to true in the tls.Config.
2024-08-08 22:03:10 +08:00
func (r *Request) SetTlsConfig(tlsConfig *tls.Config) *Request {
r.tlsConfig = tlsConfig
return r
}
2025-08-21 15:02:02 +08:00
// AutoFetchRespBody returns whether the response body will be automatically fetched.
2024-08-08 22:03:10 +08:00
func (r *Request) AutoFetchRespBody() bool {
return r.autoFetchRespBody
}
2025-08-21 15:02:02 +08:00
// SetAutoFetchRespBody sets whether the response body will be automatically fetched after the request is sent.
// If set to true, the response body will be read and stored in the Response object.
// if the body is too large, it may cause high memory usage.
// If set to false, you will need to manually read the response body using Response.Body() method.
2024-08-08 22:03:10 +08:00
func (r *Request) SetAutoFetchRespBody(autoFetchRespBody bool) *Request {
r.autoFetchRespBody = autoFetchRespBody
return r
}
2025-08-21 15:02:02 +08:00
// ResetReqHeader resets the request headers to an empty http.Header.
2024-08-08 22:03:10 +08:00
func (r *Request) ResetReqHeader() *Request {
r.headers = make(http.Header)
return r
}
2025-08-21 15:02:02 +08:00
// ResetReqCookies resets the request cookies to an empty slice.
2024-08-08 22:03:10 +08:00
func (r *Request) ResetReqCookies() *Request {
r.cookies = []*http.Cookie{}
return r
}
2025-08-21 15:02:02 +08:00
// AddSimpleCookie add a key-value cookie to the request.
// the path will be set to "/"
2024-08-08 22:03:10 +08:00
func (r *Request) AddSimpleCookie(key, value string) *Request {
r.cookies = append(r.cookies, &http.Cookie{Name: key, Value: value, Path: "/"})
return r
}
2025-08-21 15:02:02 +08:00
// AddCookie adds a cookie to the request with the specified key, value, and path.
2024-08-08 22:03:10 +08:00
func (r *Request) AddCookie(key, value, path string) *Request {
r.cookies = append(r.cookies, &http.Cookie{Name: key, Value: value, Path: path})
return r
}
2025-08-21 15:02:02 +08:00
// AddFile adds a file to the request with the specified form name and file path.
// The file will be read and uploaded as a multipart/form-data request.
// the file type will be set to "application/octet-stream" by default.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFile(formName, filepath string) error {
2025-08-13 10:16:08 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
r.bodyFileData = append(r.bodyFileData, RequestFile{
FormName: formName,
FileName: stat.Name(),
2025-08-13 10:16:08 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: "application/octet-stream",
2025-08-13 10:16:08 +08:00
FilePath: filepath,
})
return nil
}
2025-08-21 15:02:02 +08:00
// AddFileStream adds a file to the request with the specified form name, filename, size, and io.Reader stream.
// The file will be read and uploaded as a multipart/form-data request.
// the file type will be set to "application/octet-stream" by default.
2025-08-13 10:16:08 +08:00
func (r *Request) AddFileStream(formName, filename string, size int64, stream io.Reader) error {
r.bodyFileData = append(r.bodyFileData, RequestFile{
FormName: formName,
FileName: filename,
FileData: stream,
FileSize: size,
FileType: "application/octet-stream",
2024-08-08 22:03:10 +08:00
})
return nil
}
2025-08-21 15:02:02 +08:00
// AddFileWithName adds a file to the request with the specified form name, file path, and filename.
// you can specify a custom filename for the file being uploaded.
// The file will be read and uploaded as a multipart/form-data request.
// the file type will be set to "application/octet-stream" by default.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileWithName(formName, filepath, filename string) error {
2025-08-13 10:16:08 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
r.bodyFileData = append(r.bodyFileData, RequestFile{
FormName: formName,
FileName: filename,
2025-08-13 10:16:08 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: "application/octet-stream",
2025-08-13 10:16:08 +08:00
FilePath: filepath,
2024-08-08 22:03:10 +08:00
})
return nil
}
2025-08-21 15:02:02 +08:00
// AddFileWithType adds a file to the request with the specified form name, file path, and file type.
// you can specify a custom file type for the file being uploaded.
// The file will be read and uploaded as a multipart/form-data request.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileWithType(formName, filepath, filetype string) error {
2025-08-13 10:16:08 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
r.bodyFileData = append(r.bodyFileData, RequestFile{
FormName: formName,
FileName: stat.Name(),
2025-08-13 10:16:08 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: filetype,
2025-08-13 10:16:08 +08:00
FilePath: filepath,
2024-08-08 22:03:10 +08:00
})
return nil
}
2025-08-21 15:02:02 +08:00
// AddFileWithNameAndType adds a file to the request with the specified form name, file path, filename, and file type.
// you can specify a custom filename and file type for the file being uploaded.
// The file will be read and uploaded as a multipart/form-data request.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileWithNameAndType(formName, filepath, filename, filetype string) error {
2025-08-13 10:16:08 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
r.bodyFileData = append(r.bodyFileData, RequestFile{
FormName: formName,
FileName: filename,
2025-08-13 10:16:08 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: filetype,
2025-08-13 10:16:08 +08:00
FilePath: filepath,
})
return nil
}
2025-08-21 15:02:02 +08:00
// AddFileStreamWithType adds a file to the request with the specified form name, filename, file type, size, and io.Reader stream.
// The file will be read and uploaded as a multipart/form-data request.
// you can specify a custom file type for the file being uploaded.
2025-08-13 10:16:08 +08:00
func (r *Request) AddFileStreamWithType(formName, filename, filetype string, size int64, stream io.Reader) error {
r.bodyFileData = append(r.bodyFileData, RequestFile{
FormName: formName,
FileName: filename,
FileData: stream,
FileSize: size,
FileType: filetype,
2024-08-08 22:03:10 +08:00
})
return nil
}
2025-08-21 15:02:02 +08:00
// AddFileNoError adds a file to the request with the specified form name and file path.
// It will not return an error if the file cannot be added.
// this function is useful for chaining methods without error handling.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileNoError(formName, filepath string) *Request {
r.AddFile(formName, filepath)
return r
}
2025-08-21 15:02:02 +08:00
// AddFileWithNameNoError adds a file to the request with the specified form name, file path, and filename.
// It will not return an error if the file cannot be added.
// this function is useful for chaining methods without error handling.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileWithNameNoError(formName, filepath, filename string) *Request {
r.AddFileWithName(formName, filepath, filename)
return r
}
2025-08-21 15:02:02 +08:00
// AddFileWithTypeNoError adds a file to the request with the specified form name, file path, and file type.
// It will not return an error if the file cannot be added.
// this function is useful for chaining methods without error handling.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileWithTypeNoError(formName, filepath, filetype string) *Request {
r.AddFileWithType(formName, filepath, filetype)
return r
}
2025-08-21 15:02:02 +08:00
// AddFileWithNameAndTypeNoError adds a file to the request with the specified form name, file path, filename, and file type.
// It will not return an error if the file cannot be added.
// this function is useful for chaining methods without error handling.
2024-08-08 22:03:10 +08:00
func (r *Request) AddFileWithNameAndTypeNoError(formName, filepath, filename, filetype string) *Request {
r.AddFileWithNameAndType(formName, filepath, filename, filetype)
return r
}
2025-08-21 15:02:02 +08:00
// AddFileStreamNoError adds a file to the request with the specified form name, filename, size, and io.Reader stream.
// It will not return an error if the file cannot be added.
// this function is useful for chaining methods without error handling.
2025-08-13 10:16:08 +08:00
func (r *Request) AddFileStreamNoError(formName, filename string, size int64, stream io.Reader) *Request {
r.AddFileStream(formName, filename, size, stream)
return r
}
2025-08-21 15:02:02 +08:00
// AddFileStreamWithTypeNoError adds a file to the request with the specified form name, filename, file type, size, and io.Reader stream.
// It will not return an error if the file cannot be added.
// this function is useful for chaining methods without error handling.
2025-08-13 10:16:08 +08:00
func (r *Request) AddFileStreamWithTypeNoError(formName, filename, filetype string, size int64, stream io.Reader) *Request {
r.AddFileStreamWithType(formName, filename, filetype, size, stream)
return r
}
2025-08-21 15:02:02 +08:00
// HttpClient returns the http.Client used for the request.
2025-07-14 18:23:14 +08:00
func (r *Request) HttpClient() (*http.Client, error) {
err := applyOptions(r)
if err != nil {
return nil, err
}
return r.rawClient, nil
}
2024-08-08 22:03:10 +08:00
type RequestFile struct {
FormName string
FileName string
FileData io.Reader
FileSize int64
FileType string
2025-08-13 10:16:08 +08:00
FilePath string
2024-08-08 22:03:10 +08:00
}
type RequestOpt func(opt *RequestOpts) error
2022-03-14 15:43:56 +08:00
2025-08-21 15:02:02 +08:00
// WithDialTimeout sets the dial timeout for the request.
// If use custom Transport Dialer, this function will nolonger work.
2022-03-14 15:43:56 +08:00
func WithDialTimeout(timeout time.Duration) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.dialTimeout = timeout
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithDial sets a custom dial function for the request.
// functions like WithDialTimeout will nolonger work if this function is used.
// If use custom Transport Dialer, this function will nolonger work.
2025-06-12 16:50:47 +08:00
func WithDial(fn func(ctx context.Context, network string, addr string) (net.Conn, error)) RequestOpt {
return func(opt *RequestOpts) error {
opt.dialFn = fn
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithTimeout sets the timeout for the request.
// If use custom Transport Dialer, this function will nolonger work.
2022-03-14 15:43:56 +08:00
func WithTimeout(timeout time.Duration) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.timeout = timeout
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithTlsConfig sets the TLS configuration for the request.
// If use custom Transport Dialer, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithTlsConfig(tlscfg *tls.Config) RequestOpt {
return func(opt *RequestOpts) error {
opt.tlsConfig = tlscfg
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithHeaders sets the request headers using an http.Header.
// If doRawRequest is true, this function will not work.
2024-08-08 22:03:10 +08:00
func WithHeader(key, val string) RequestOpt {
return func(opt *RequestOpts) error {
opt.headers.Set(key, val)
return nil
2022-08-22 16:22:22 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithHeaderMap sets the request headers using a map of string to string.
// If doRawRequest is true, this function will not work.
2022-03-14 15:43:56 +08:00
func WithHeaderMap(header map[string]string) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
2022-03-14 15:43:56 +08:00
for key, val := range header {
2024-08-08 22:03:10 +08:00
opt.headers.Set(key, val)
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithReader sets the request body data using an io.Reader.
// The priority order of the data is: **bodyDataReader** > bodyDataBytes > bodyFormData > bodyFileData.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithReader(r io.Reader) RequestOpt {
return func(opt *RequestOpts) error {
opt.bodyDataReader = r
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithBytes sets the request body data using a byte slice.
// The priority order of the data is: bodyDataReader > **bodyDataBytes** > bodyFormData > bodyFileData.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithBytes(r []byte) RequestOpt {
return func(opt *RequestOpts) error {
opt.bodyDataBytes = r
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithFormData sets the request body data using a map of string slices.
// The priority order of the data is: bodyDataReader > bodyDataBytes > **bodyFormData** > bodyFileData.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithFormData(data map[string][]string) RequestOpt {
return func(opt *RequestOpts) error {
opt.bodyFormData = data
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithFileDatas sets the request body file data using a slice of RequestFile.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithFileDatas(data []RequestFile) RequestOpt {
return func(opt *RequestOpts) error {
opt.bodyFileData = data
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithFileData sets the request body file data using a single RequestFile.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithFileData(data RequestFile) RequestOpt {
return func(opt *RequestOpts) error {
opt.bodyFileData = append(opt.bodyFileData, data)
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithAddFile adds a file to the request with the specified form name and file path.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// The file will be read and uploaded as a multipart/form-data request.
// the file type will be set to "application/octet-stream" by default.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithAddFile(formName, filepath string) RequestOpt {
return func(opt *RequestOpts) error {
2025-08-21 15:02:02 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
FormName: formName,
FileName: stat.Name(),
2025-08-21 15:02:02 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: "application/octet-stream",
2025-08-21 15:02:02 +08:00
FilePath: filepath,
2024-08-08 22:03:10 +08:00
})
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithAddFileWithName adds a file to the request with the specified form name, file path, and filename.
// you can specify a custom filename for the file being uploaded.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// The file will be read and uploaded as a multipart/form-data request.
// If doRawRequest is true, this function will nolonger work.
2024-08-08 22:03:10 +08:00
func WithAddFileWithName(formName, filepath, filename string) RequestOpt {
return func(opt *RequestOpts) error {
2025-08-21 15:02:02 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
FormName: formName,
FileName: filename,
2025-08-21 15:02:02 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: "application/octet-stream",
})
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithAddFileWithType adds a file to the request with the specified form name, file path, and file type.
// you can specify a custom file type for the file being uploaded.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// The file will be read and uploaded as a multipart/form-data request.
2024-08-08 22:03:10 +08:00
func WithAddFileWithType(formName, filepath, filetype string) RequestOpt {
return func(opt *RequestOpts) error {
2025-08-21 15:02:02 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
2025-08-21 15:02:02 +08:00
return err
2024-08-08 22:03:10 +08:00
}
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
FormName: formName,
FileName: stat.Name(),
2025-08-21 15:02:02 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: filetype,
})
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithAddFileWithNameAndType adds a file to the request with the specified form name, file path, filename, and file type.
// you can specify a custom filename and file type for the file being uploaded.
// The priority order of the data is: bodyDataReader > bodyDataBytes > bodyFormData > **bodyFileData**.
// The file will be read and uploaded as a multipart/form-data request.
2024-08-08 22:03:10 +08:00
func WithAddFileWithNameAndType(formName, filepath, filename, filetype string) RequestOpt {
return func(opt *RequestOpts) error {
2025-08-21 15:02:02 +08:00
stat, err := os.Stat(filepath)
2024-08-08 22:03:10 +08:00
if err != nil {
return err
}
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
FormName: formName,
FileName: filename,
2025-08-21 15:02:02 +08:00
FileData: nil,
2024-08-08 22:03:10 +08:00
FileSize: stat.Size(),
FileType: filetype,
})
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithFetchRespBody sets whether the response body will be automatically fetched after the request is sent.
// If set to true, the response body will be read and stored in the Response object.
// If the body is too large, it may cause high memory usage.
// If set to false, you will need to manually read the response body using Response.Body() method.
2022-03-14 15:43:56 +08:00
func WithFetchRespBody(fetch bool) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.autoFetchRespBody = fetch
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithCookies sets the request cookies using a slice of http.Cookie.
2022-03-14 15:43:56 +08:00
func WithCookies(ck []*http.Cookie) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.cookies = ck
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithCookie adds a cookie to the request with the specified key, value, and path.
2022-03-14 15:43:56 +08:00
func WithCookie(key, val, path string) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.cookies = append(opt.cookies, &http.Cookie{Name: key, Value: val, Path: path})
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithSimpleCookie adds a simple cookie to the request with the specified key and value.
2024-08-08 22:03:10 +08:00
func WithSimpleCookie(key, val string) RequestOpt {
return func(opt *RequestOpts) error {
opt.cookies = append(opt.cookies, &http.Cookie{Name: key, Value: val, Path: "/"})
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithCookieMap sets the request cookies using a map of string to string.
2022-03-14 15:43:56 +08:00
func WithCookieMap(header map[string]string, path string) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
2022-03-14 15:43:56 +08:00
for key, val := range header {
2024-08-08 22:03:10 +08:00
opt.cookies = append(opt.cookies, &http.Cookie{Name: key, Value: val, Path: path})
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithQueries sets the request queries using a map of string slices.
2024-08-08 22:03:10 +08:00
func WithQueries(queries map[string][]string) RequestOpt {
return func(opt *RequestOpts) error {
opt.queries = queries
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithAddQueries adds multiple query parameters to the request using a map of string to string slices.
// if the key already exists, it will append the value to the existing slice.
2024-08-08 22:03:10 +08:00
func WithAddQueries(queries map[string]string) RequestOpt {
return func(opt *RequestOpts) error {
for k, v := range queries {
opt.queries[k] = append(opt.queries[k], v)
}
return nil
}
}
2025-08-21 15:02:02 +08:00
// WithAddQuery adds a single query parameter to the request.
2024-08-08 22:03:10 +08:00
func WithAddQuery(key, val string) RequestOpt {
return func(opt *RequestOpts) error {
opt.queries[key] = append(opt.queries[key], val)
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithProxy sets the proxy URL for the request.
2022-03-14 15:43:56 +08:00
func WithProxy(proxy string) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.proxy = proxy
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithProcess sets a callback function to process the file upload progress.
// The callback function will be called with the file name, uploaded bytes and total bytes.
// example:
//
// WithProcess(func(name string, uploaded int64, total int64) {
// fmt.Printf("Uploading %s: %d/%d bytes\n", name, uploaded, total)
// })
2024-08-08 22:03:10 +08:00
func WithProcess(fn func(string, int64, int64)) RequestOpt {
return func(opt *RequestOpts) error {
2025-08-21 15:02:02 +08:00
opt.fileUploadRecallFn = fn
2024-08-08 22:03:10 +08:00
return nil
2022-03-14 15:43:56 +08:00
}
2020-07-20 11:17:29 +08:00
}
2025-08-21 15:02:02 +08:00
// WithContentType sets the Content-Type header for the request.
// This function will overwrite any existing Content-Type header.
2022-03-14 15:43:56 +08:00
func WithContentType(ct string) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.headers.Set("Content-Type", ct)
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithUserAgent sets the User-Agent header for the request.
// This function will overwrite any existing User-Agent header.
2022-03-14 15:43:56 +08:00
func WithUserAgent(ua string) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.headers.Set("User-Agent", ua)
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithSkipTLSVerify sets whether the request will skip TLS verification.
// If set to true, the request will not verify the server's TLS certificate.
2024-08-08 22:03:10 +08:00
func WithSkipTLSVerify(skip bool) RequestOpt {
return func(opt *RequestOpts) error {
opt.skipTLSVerify = skip
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
/*
// WithDisableRedirect sets whether the request will disable HTTP redirects.
// If set to true, the request will not follow redirects automatically.
// For example, if the server responds with a 301 or 302 status code, the request will not automatically follow the redirect.
// You will get the original response with the redirect status code and Location header.
2024-08-08 22:03:10 +08:00
func WithDisableRedirect(disable bool) RequestOpt {
return func(opt *RequestOpts) error {
opt.disableRedirect = disable
return nil
2022-03-14 15:43:56 +08:00
}
}
2025-08-21 15:02:02 +08:00
*/
2022-03-14 15:43:56 +08:00
2025-08-21 15:02:02 +08:00
// WithDoRawRequest sets whether the request will be sent as a raw request without any modifications.
// You can use this with function SetRawRequest to set a custom http.Request.
// If set to true, the request will not apply any modifications to the request headers, body, or other settings.
// If set to false, the request will apply the modifications as usual.
2024-08-08 22:03:10 +08:00
func WithDoRawRequest(doRawRequest bool) RequestOpt {
return func(opt *RequestOpts) error {
opt.doRawRequest = doRawRequest
return nil
2022-06-06 11:18:42 +08:00
}
}
2025-08-21 15:02:02 +08:00
// WithTransport sets the http.Transport used for the request.
2024-08-08 22:03:10 +08:00
func WithTransport(hs *http.Transport) RequestOpt {
return func(opt *RequestOpts) error {
opt.transport = hs
2025-08-21 15:02:02 +08:00
opt.customTransport = true
2024-08-08 22:03:10 +08:00
return nil
2020-10-19 21:04:56 +08:00
}
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
// WithRawRequest sets a custom http.Request for the request.
2024-08-08 22:03:10 +08:00
func WithRawRequest(req *http.Request) RequestOpt {
return func(opt *RequestOpts) error {
opt.rawRequest = req
return nil
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
// WithRawClient sets a custom http.Client for the request.
2024-08-08 22:03:10 +08:00
func WithRawClient(hc *http.Client) RequestOpt {
return func(opt *RequestOpts) error {
opt.rawClient = hc
return nil
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
// WithCustomHostIP sets custom IPs for the host.
// it means that the request will use the specified IPs to resolve the host instead of using DNS.
// Note: LookUpIPfn will be ignored if this function is used.
2024-08-08 22:03:10 +08:00
func WithCustomHostIP(ip []string) RequestOpt {
return func(opt *RequestOpts) error {
if len(ip) == 0 {
return nil
}
for _, v := range ip {
if net.ParseIP(v) == nil {
return fmt.Errorf("invalid custom ip: %s", v)
}
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
opt.customIP = ip
return nil
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
// WithAddCustomHostIP adds a custom IP to the request.
2024-08-08 22:03:10 +08:00
func WithAddCustomHostIP(ip string) RequestOpt {
return func(opt *RequestOpts) error {
if net.ParseIP(ip) == nil {
return fmt.Errorf("invalid custom ip: %s", ip)
}
opt.customIP = append(opt.customIP, ip)
return nil
2022-08-22 16:22:22 +08:00
}
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
// WithLookUpFn sets a custom function to look up IP addresses for the host.
// If set to nil, it will use the default net.Resolver.LookupIPAddr function.
// Note: If customDNS is set, this function will not be used.
2024-08-08 22:03:10 +08:00
func WithLookUpFn(lookUpIPfn func(ctx context.Context, host string) ([]net.IPAddr, error)) RequestOpt {
return func(opt *RequestOpts) error {
if lookUpIPfn == nil {
opt.alreadySetLookUpIPfn = false
opt.lookUpIPfn = net.DefaultResolver.LookupIPAddr
return nil
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
opt.lookUpIPfn = lookUpIPfn
opt.alreadySetLookUpIPfn = true
return nil
}
}
// WithCustomDNS will use custom dns to resolve the host
// Note: if LookUpIPfn is set, this function will not be used
func WithCustomDNS(customDNS []string) RequestOpt {
return func(opt *RequestOpts) error {
for _, v := range customDNS {
if net.ParseIP(v) == nil {
return fmt.Errorf("invalid custom dns: %s", v)
}
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
opt.customDNS = customDNS
return nil
2022-03-14 15:43:56 +08:00
}
2020-07-20 11:17:29 +08:00
}
2024-08-08 22:03:10 +08:00
// WithAddCustomDNS will use a custom dns to resolve the host
// Note: if LookUpIPfn is set, this function will not be used
func WithAddCustomDNS(customDNS string) RequestOpt {
return func(opt *RequestOpts) error {
if net.ParseIP(customDNS) == nil {
return fmt.Errorf("invalid custom dns: %s", customDNS)
}
opt.customDNS = append(opt.customDNS, customDNS)
return nil
}
2020-07-20 11:17:29 +08:00
}
2024-08-08 22:03:10 +08:00
// WithAutoCalcContentLength sets whether to automatically calculate the Content-Length header based on the request body.
// WARN: If set to true, the Content-Length header will be set to the length of the request body, data will be cached in memory.
// So it may cause high memory usage if the request body is large.
// If set to false, the Content-Length header will not be set,unless the request body is a byte slice or bytes.Buffer which has a specific length.
// Note that this function will not work if doRawRequest is true or ContentLength already set
func WithAutoCalcContentLength(autoCalcContentLength bool) RequestOpt {
return func(opt *RequestOpts) error {
opt.autoCalcContentLength = autoCalcContentLength
return nil
}
}
// WithContentLength sets the Content-Length for the request.
// This function will overwrite any existing or auto calculated Content-Length header.
// if the length is less than 0, it will not set the Content-Length header. chunked transfer encoding will be used instead.
// chunked transfer encoding may cause some servers to reject the request if they do not support it.
2024-08-08 22:03:10 +08:00
// Note that this function will not work if doRawRequest is true
func WithContentLength(length int64) RequestOpt {
2024-08-08 22:03:10 +08:00
return func(opt *RequestOpts) error {
opt.contentLength = length
2024-08-08 22:03:10 +08:00
return nil
}
2020-07-20 11:17:29 +08:00
}
2024-08-08 22:03:10 +08:00
type Response struct {
*http.Response
2025-07-14 18:23:14 +08:00
req Request
data *Body
rawClient *http.Client
2020-07-20 11:17:29 +08:00
}
2024-08-08 22:03:10 +08:00
type Body struct {
full []byte
raw io.ReadCloser
isFull bool
2025-04-28 13:19:45 +08:00
sync.Mutex
2022-03-14 15:43:56 +08:00
}
2020-07-20 11:17:29 +08:00
2024-08-08 22:03:10 +08:00
func (b *Body) readAll() {
2025-04-28 13:19:45 +08:00
b.Lock()
defer b.Unlock()
2024-08-08 22:03:10 +08:00
if !b.isFull {
2025-04-28 13:19:45 +08:00
if b.raw == nil {
b.isFull = true
return
}
2024-08-08 22:03:10 +08:00
b.full, _ = io.ReadAll(b.raw)
b.isFull = true
b.raw.Close()
2020-07-20 11:17:29 +08:00
}
}
2025-08-21 15:02:02 +08:00
// String will read the body and return it as a string.
// if the body is too large, it may cause high memory usage.
2024-08-08 22:03:10 +08:00
func (b *Body) String() string {
b.readAll()
return string(b.full)
}
2025-08-21 15:02:02 +08:00
// Bytes will read the body and return it as a byte slice.
// if the body is too large, it may cause high memory usage.
2024-08-08 22:03:10 +08:00
func (b *Body) Bytes() []byte {
b.readAll()
return b.full
}
2025-08-21 15:02:02 +08:00
// Unmarshal will read the body and unmarshal it into the given interface using json.Unmarshal
// if the body is too large, it may cause high memory usage.
2024-08-08 22:03:10 +08:00
func (b *Body) Unmarshal(u interface{}) error {
b.readAll()
return json.Unmarshal(b.full, u)
}
// Reader returns a reader for the body
2025-08-21 15:02:02 +08:00
// if this function is called, other functions like String, Bytes, Unmarshal not work
2024-08-08 22:03:10 +08:00
func (b *Body) Reader() io.ReadCloser {
2025-04-28 13:19:45 +08:00
b.Lock()
defer b.Unlock()
2024-08-08 22:03:10 +08:00
if b.isFull {
return io.NopCloser(bytes.NewReader(b.full))
2020-07-21 09:20:02 +08:00
}
2024-08-08 22:03:10 +08:00
b.isFull = true
return b.raw
}
2025-08-21 15:02:02 +08:00
// Close closes the body reader.
2024-08-08 22:03:10 +08:00
func (b *Body) Close() error {
2024-08-30 23:44:49 +08:00
return b.raw.Close()
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
// GetRequest returns the original Request object associated with the Response.
2024-08-08 22:03:10 +08:00
func (r *Response) GetRequest() Request {
return r.req
}
2025-08-21 15:02:02 +08:00
// Body returns the Body object associated with the Response.
2024-08-08 22:03:10 +08:00
func (r *Response) Body() *Body {
return r.data
}
2025-08-21 15:02:02 +08:00
// Close closes the response body and releases any resources associated with it.
2025-07-14 18:23:14 +08:00
func (r *Response) Close() error {
if r != nil && r.data != nil && r.data.raw != nil {
return r.Response.Body.Close()
}
return nil
}
2025-08-21 15:02:02 +08:00
// CloseAll closes the response body and releases any resources associated with it.
// It also closes all idle connections in the http.Client if it is not nil.
2025-07-14 18:23:14 +08:00
func (r *Response) CloseAll() error {
if r.rawClient != nil {
r.rawClient.CloseIdleConnections()
}
return r.Close()
}
2025-08-21 15:02:02 +08:00
// HttpClient returns the http.Client used for the request.
2025-07-14 18:23:14 +08:00
func (r *Response) HttpClient() *http.Client {
return r.rawClient
}
2025-08-21 15:02:02 +08:00
// Curl sends the HTTP request and returns the response.
2024-08-08 22:03:10 +08:00
func Curl(r *Request) (*Response, error) {
r.errInfo = nil
err := applyOptions(r)
2020-07-21 09:20:02 +08:00
if err != nil {
2024-08-08 22:03:10 +08:00
return nil, fmt.Errorf("apply options error: %s", err)
2020-07-21 09:20:02 +08:00
}
2025-08-21 15:02:02 +08:00
r.rawRequest = r.rawRequest.WithContext(r.doCtx)
2024-08-08 22:03:10 +08:00
resp, err := r.rawClient.Do(r.rawRequest)
var res = Response{
2025-07-14 18:23:14 +08:00
Response: resp,
req: *r,
data: new(Body),
rawClient: r.rawClient,
2020-07-21 09:20:02 +08:00
}
2024-08-08 22:03:10 +08:00
if err != nil {
res.Response = &http.Response{}
return &res, fmt.Errorf("do request error: %s", err)
}
res.data.raw = resp.Body
if r.autoFetchRespBody {
res.data.full, _ = io.ReadAll(resp.Body)
res.data.isFull = true
resp.Body.Close()
2020-07-21 09:20:02 +08:00
}
2024-08-08 22:03:10 +08:00
return &res, r.errInfo
}
2025-08-21 15:02:02 +08:00
// NewReq creates a new Request with the specified URI and default method "GET".
2024-08-08 22:03:10 +08:00
func NewReq(uri string, opts ...RequestOpt) *Request {
return NewSimpleRequest(uri, "GET", opts...)
}
2025-08-21 15:02:02 +08:00
// NewReqWithContext creates a new Request with the specified URI and default method "GET" using the provided context.
2024-08-08 22:03:10 +08:00
func NewReqWithContext(ctx context.Context, uri string, opts ...RequestOpt) *Request {
return NewSimpleRequestWithContext(ctx, uri, "GET", opts...)
2020-07-21 09:20:02 +08:00
}
2025-08-21 15:02:02 +08:00
// NewSimpleRequest creates a new Request with the specified URI and method.
2024-08-08 22:03:10 +08:00
func NewSimpleRequest(uri string, method string, opts ...RequestOpt) *Request {
r, _ := newRequest(context.Background(), uri, method, opts...)
return r
2022-03-11 09:29:09 +08:00
}
2025-08-21 15:02:02 +08:00
// NewRequest creates a new Request with the specified URI and method.
2024-08-08 22:03:10 +08:00
func NewRequest(uri string, method string, opts ...RequestOpt) (*Request, error) {
return newRequest(context.Background(), uri, method, opts...)
}
2025-08-21 15:02:02 +08:00
// NewSimpleRequestWithContext creates a new Request with the specified URI and method using the provided context.
2024-08-08 22:03:10 +08:00
func NewSimpleRequestWithContext(ctx context.Context, uri string, method string, opts ...RequestOpt) *Request {
r, _ := newRequest(ctx, uri, method, opts...)
return r
}
2025-08-21 15:02:02 +08:00
// NewRequestWithContext creates a new Request with the specified URI and method using the provided context.
2024-08-08 22:03:10 +08:00
func NewRequestWithContext(ctx context.Context, uri string, method string, opts ...RequestOpt) (*Request, error) {
return newRequest(ctx, uri, method, opts...)
}
func newRequest(ctx context.Context, uri string, method string, opts ...RequestOpt) (*Request, error) {
2020-07-20 11:17:29 +08:00
var req *http.Request
2020-07-21 09:20:02 +08:00
var err error
2024-08-08 22:03:10 +08:00
if method == "" {
method = "GET"
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
method = strings.ToUpper(method)
req, err = http.NewRequestWithContext(ctx, method, uri, nil)
2020-07-20 11:17:29 +08:00
if err != nil {
2024-08-08 22:03:10 +08:00
return nil, err
2020-07-20 11:17:29 +08:00
}
2024-08-08 22:03:10 +08:00
var r = &Request{
ctx: ctx,
uri: uri,
method: method,
RequestOpts: RequestOpts{
rawRequest: req,
2025-07-14 18:23:14 +08:00
rawClient: nil,
2024-08-08 22:03:10 +08:00
timeout: DefaultTimeout,
dialTimeout: DefaultDialTimeout,
autoFetchRespBody: DefaultFetchRespBody,
lookUpIPfn: net.DefaultResolver.LookupIPAddr,
bodyFormData: make(map[string][]string),
queries: make(map[string][]string),
},
2020-07-20 11:17:29 +08:00
}
2024-08-08 22:03:10 +08:00
r.headers = make(http.Header)
if strings.ToUpper(method) == "POST" {
r.headers.Set("Content-Type", HEADER_FORM_URLENCODE)
}
2025-08-21 21:40:29 +08:00
r.headers.Set("User-Agent", "B612 Starnet / 0.3.0")
2024-08-08 22:03:10 +08:00
for _, v := range opts {
if v != nil {
err = v(&r.RequestOpts)
if err != nil {
return nil, err
}
2020-10-19 21:04:56 +08:00
}
2020-07-20 11:17:29 +08:00
}
2025-08-21 15:02:02 +08:00
if r.rawClient == nil {
2025-07-14 18:23:14 +08:00
r.rawClient = new(http.Client)
}
2025-08-21 15:02:02 +08:00
if r.tlsConfig == nil {
r.tlsConfig = &tls.Config{
NextProtos: []string{"h2", "http/1.1"},
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
}
r.tlsConfig.InsecureSkipVerify = r.skipTLSVerify
if r.transport == nil {
r.transport = &http.Transport{
ForceAttemptHTTP2: true,
DialContext: DefaultDialFunc,
DialTLSContext: DefaultDialTlsFunc,
Proxy: DefaultProxyURL(),
2025-06-12 16:50:47 +08:00
}
2022-06-06 11:18:42 +08:00
}
2024-08-08 22:03:10 +08:00
return r, nil
2020-07-20 11:17:29 +08:00
}
2020-10-19 21:04:56 +08:00
2024-08-08 22:03:10 +08:00
func applyDataReader(r *Request) error {
// 优先度为bodyDataReader > bodyDataBytes > bodyFormData > bodyFileData
r.rawRequest.ContentLength = 0
2024-08-08 22:03:10 +08:00
if r.bodyDataReader != nil {
switch v := r.bodyDataReader.(type) {
case *bytes.Buffer:
r.rawRequest.ContentLength = int64(v.Len())
case *bytes.Reader:
r.rawRequest.ContentLength = int64(v.Len())
case *strings.Reader:
r.rawRequest.ContentLength = int64(v.Len())
}
2024-08-08 22:03:10 +08:00
r.rawRequest.Body = io.NopCloser(r.bodyDataReader)
return nil
}
if len(r.bodyDataBytes) != 0 {
r.rawRequest.ContentLength = int64(len(r.bodyDataBytes))
2024-08-08 22:03:10 +08:00
r.rawRequest.Body = io.NopCloser(bytes.NewReader(r.bodyDataBytes))
return nil
}
if len(r.bodyFormData) != 0 && len(r.bodyFileData) == 0 {
var body = url.Values{}
for k, v := range r.bodyFormData {
for _, vv := range v {
body.Add(k, vv)
}
}
r.rawRequest.ContentLength = int64(len(body.Encode()))
2024-08-08 22:03:10 +08:00
r.rawRequest.Body = io.NopCloser(strings.NewReader(body.Encode()))
return nil
}
if len(r.bodyFileData) != 0 {
var pr, pw = io.Pipe()
var w = multipart.NewWriter(pw)
r.rawRequest.Header.Set("Content-Type", w.FormDataContentType())
go func() {
defer pw.Close() // ensure pipe writer is closed
if len(r.bodyFormData) != 0 {
for k, v := range r.bodyFormData {
for _, vv := range v {
if err := w.WriteField(k, vv); err != nil {
r.errInfo = err
pw.CloseWithError(err) // close pipe with error
return
}
}
}
}
2025-08-13 10:16:08 +08:00
for idx, v := range r.bodyFileData {
2024-08-08 22:03:10 +08:00
var fw, err = w.CreateFormFile(v.FormName, v.FileName)
if err != nil {
r.errInfo = err
pw.CloseWithError(err) // close pipe with error
return
}
2025-08-13 10:16:08 +08:00
if v.FileData == nil {
if v.FilePath != "" {
tmpFile, err := os.Open(v.FilePath)
if err != nil {
r.errInfo = err
pw.CloseWithError(err) // close pipe with error
return
}
defer tmpFile.Close()
v.FileData = tmpFile
} else {
r.errInfo = fmt.Errorf("io reader is nil")
pw.CloseWithError(fmt.Errorf("io reader is nil")) // close pipe with error
return
}
}
2025-08-21 15:02:02 +08:00
if _, err := copyWithContext(r.ctx, r.fileUploadRecallFn, v.FileName, v.FileSize, fw, v.FileData); err != nil {
2024-08-08 22:03:10 +08:00
r.errInfo = err
2025-08-13 10:16:08 +08:00
r.bodyFileData[idx] = v
2024-08-08 22:03:10 +08:00
pw.CloseWithError(err) // close pipe with error
return
}
}
2020-10-19 21:04:56 +08:00
2024-08-08 22:03:10 +08:00
if err := w.Close(); err != nil {
pw.CloseWithError(err) // close pipe with error if writer close fails
}
}()
2020-10-19 21:04:56 +08:00
2024-08-08 22:03:10 +08:00
r.rawRequest.Body = pr
return nil
2020-10-19 21:04:56 +08:00
}
2024-08-08 22:03:10 +08:00
return nil
2020-10-19 21:04:56 +08:00
}
2022-03-14 15:43:56 +08:00
2024-08-08 22:03:10 +08:00
func applyOptions(r *Request) error {
defer func() {
r.alreadyApply = true
}()
var req = r.rawRequest
if !r.doRawRequest {
if r.queries != nil {
sid := req.URL.Query()
for k, v := range r.queries {
for _, vv := range v {
sid.Add(k, vv)
}
}
req.URL.RawQuery = sid.Encode()
}
for k, v := range r.headers {
for _, vv := range v {
req.Header.Add(k, vv)
}
}
if len(r.cookies) != 0 {
for _, v := range r.cookies {
req.AddCookie(v)
}
}
if r.basicAuth[0] != "" || r.basicAuth[1] != "" {
req.SetBasicAuth(r.basicAuth[0], r.basicAuth[1])
}
err := applyDataReader(r)
if err != nil {
return fmt.Errorf("apply data reader error: %s", err)
}
if r.autoCalcContentLength {
if req.Body != nil {
data, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("read data error: %s", err)
}
req.ContentLength = int64(len(data))
req.Body = io.NopCloser(bytes.NewBuffer(data))
2024-08-08 22:03:10 +08:00
}
}
if r.contentLength > 0 {
req.ContentLength = r.contentLength
} else if r.contentLength < 0 {
//force use chunked transfer encoding
req.ContentLength = 0
}
2022-03-14 15:43:56 +08:00
}
2025-08-21 15:02:02 +08:00
if !r.alreadySetLookUpIPfn && len(r.customDNS) > 0 {
resolver := net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
for _, addr := range r.customDNS {
if conn, err = net.Dial("udp", addr+":53"); err != nil {
continue
} else {
return conn, nil
}
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
return
},
2024-08-08 22:03:10 +08:00
}
2025-08-21 15:02:02 +08:00
r.lookUpIPfn = resolver.LookupIPAddr
}
if r.tlsConfig == nil {
r.tlsConfig = &tls.Config{
NextProtos: []string{"h2", "http/1.1"},
2024-08-08 22:03:10 +08:00
}
}
2025-08-21 15:02:02 +08:00
if r.tlsConfig.ServerName == "" {
r.tlsConfig.ServerName = r.rawRequest.URL.Hostname()
}
r.tlsConfig.InsecureSkipVerify = r.skipTLSVerify
if r.rawClient.Transport == nil {
r.rawClient.Transport = &Transport{base: r.transport}
}
r.doCtx = context.WithValue(context.WithValue(r.ctx, "dialTimeout", r.dialTimeout), "timeout", r.timeout)
r.doCtx = context.WithValue(r.doCtx, "lookUpIP", r.lookUpIPfn)
if r.customIP != nil && len(r.customIP) > 0 {
r.doCtx = context.WithValue(r.doCtx, "customIP", r.customIP)
}
r.doCtx = context.WithValue(r.doCtx, "tlsConfig", r.tlsConfig)
if r.proxy != "" {
r.doCtx = context.WithValue(r.doCtx, "proxy", r.proxy)
}
if r.dialFn != nil {
r.doCtx = context.WithValue(r.doCtx, "dialFn", r.dialFn)
}
if r.customTransport {
r.doCtx = context.WithValue(r.doCtx, "custom", r.transport)
}
2024-08-08 22:03:10 +08:00
return nil
2022-03-14 15:43:56 +08:00
}
2024-08-08 22:03:10 +08:00
func copyWithContext(ctx context.Context, recall func(string, int64, int64), filename string, total int64, dst io.Writer, src io.Reader) (written int64, err error) {
pr, pw := io.Pipe()
defer pr.Close()
2024-08-08 22:03:10 +08:00
go func() {
defer pw.Close()
_, err := io.Copy(pw, src)
if err != nil {
pw.CloseWithError(err)
}
}()
var count int64
buf := make([]byte, 4096)
for {
select {
case <-ctx.Done():
return written, ctx.Err()
default:
nr, err := pr.Read(buf)
if err != nil {
if err == io.EOF {
2025-08-13 10:16:08 +08:00
if recall != nil {
go recall(filename, count, total)
}
2024-08-08 22:03:10 +08:00
return written, nil
}
return written, err
}
count += int64(nr)
if recall != nil {
go recall(filename, count, total)
}
nw, err := dst.Write(buf[:nr])
if err != nil {
return written, err
}
if nr != nw {
return written, io.ErrShortWrite
}
written += int64(nr)
}
}
}
2025-07-14 18:23:14 +08:00
2025-08-21 15:02:02 +08:00
func NewReqWithClient(client Client, uri string, opts ...RequestOpt) *Request {
2025-07-14 18:23:14 +08:00
return NewSimpleRequestWithClient(client, uri, "GET", opts...)
}
2025-08-21 15:02:02 +08:00
func NewReqWithContextWithClient(ctx context.Context, client Client, uri string, opts ...RequestOpt) *Request {
2025-07-14 18:23:14 +08:00
return NewSimpleRequestWithContextWithClient(ctx, client, uri, "GET", opts...)
}
2025-08-21 15:02:02 +08:00
func NewSimpleRequestWithClient(client Client, uri string, method string, opts ...RequestOpt) *Request {
2025-07-14 18:23:14 +08:00
r, _ := NewRequestWithContextWithClient(context.Background(), client, uri, method, opts...)
return r
}
2025-08-21 15:02:02 +08:00
func NewRequestWithClient(client Client, uri string, method string, opts ...RequestOpt) (*Request, error) {
2025-07-14 18:23:14 +08:00
return NewRequestWithContextWithClient(context.Background(), client, uri, method, opts...)
}
2025-08-21 15:02:02 +08:00
func NewSimpleRequestWithContextWithClient(ctx context.Context, client Client, uri string, method string, opts ...RequestOpt) *Request {
2025-07-14 18:23:14 +08:00
r, _ := NewRequestWithContextWithClient(ctx, client, uri, method, opts...)
return r
}
2025-08-21 15:02:02 +08:00
func NewRequestWithContextWithClient(ctx context.Context, client Client, uri string, method string, opts ...RequestOpt) (*Request, error) {
2025-07-14 18:38:31 +08:00
req, err := newRequest(ctx, uri, method, opts...)
2025-07-14 18:23:14 +08:00
if err != nil {
return nil, err
}
2025-08-21 15:02:02 +08:00
req.rawClient = client.Client
2025-07-14 18:38:31 +08:00
return req, err
2025-07-14 18:23:14 +08:00
}