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"
2024-08-08 22:03:10 +08:00
"strconv"
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 ) ,
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
}
2025-08-21 15:02:02 +08:00
func ( r * RequestOpts ) CustomTransport ( ) bool {
return r . customTransport
}
func ( r * RequestOpts ) SetCustomTransport ( customTransport bool ) {
r . customTransport = customTransport
}
func ( r * RequestOpts ) FileUploadRecallFn ( ) func ( filename string , upPos int64 , total int64 ) {
return r . fileUploadRecallFn
}
func ( r * RequestOpts ) SetFileUploadRecallFn ( FileUploadRecallFn func ( filename string , upPos int64 , total int64 ) ) {
r . fileUploadRecallFn = FileUploadRecallFn
}
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, which may cause issues with chunked transfer encoding.
// also the memory usage will be higher
// Note that this function will not work if doRawRequest is true
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.
func ( r * RequestOpts ) BasicAuth ( ) ( string , string ) {
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
func ( r * RequestOpts ) SetBasicAuth ( username , password string ) * RequestOpts {
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.
2024-08-08 22:03:10 +08:00
func ( r * RequestOpts ) SkipTLSVerify ( ) bool {
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, which may cause issues with chunked transfer encoding.
// also the memory usage will be higher
// Note that this function will not work if doRawRequest is true
func ( r * RequestOpts ) WithAutoCalcContentLength ( autoCalcContentLength bool ) RequestOpt {
return func ( opt * RequestOpts ) error {
r . autoCalcContentLength = autoCalcContentLength
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 )
}
r . headers . Set ( "User-Agent" , "B612 / 1.2.0" )
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
if r . bodyDataReader != nil {
r . rawRequest . Body = io . NopCloser ( r . bodyDataReader )
return nil
}
if len ( r . bodyDataBytes ) != 0 {
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 . 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
2020-10-19 21:04:56 +08:00
2024-08-08 22:03:10 +08:00
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 . Header . Set ( "Content-Length" , strconv . Itoa ( len ( data ) ) )
req . Body = io . NopCloser ( bytes . NewReader ( data ) )
}
}
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
}
2022-09-06 15:15:33 +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 ( )
2022-09-06 15:15:33 +08:00
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 )
}
}
2022-09-06 15:15:33 +08:00
}
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
}