1818 lines
56 KiB
Go
1818 lines
56 KiB
Go
package starnet
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"crypto/tls"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"mime/multipart"
|
||
"net"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
type Request struct {
|
||
ctx context.Context
|
||
doCtx context.Context // 用于在请求中传递上下文信息
|
||
uri string
|
||
method string
|
||
errInfo error
|
||
RequestOpts
|
||
}
|
||
|
||
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),
|
||
proxy: r.proxy,
|
||
timeout: r.timeout,
|
||
dialTimeout: r.dialTimeout,
|
||
dialFn: r.dialFn,
|
||
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,
|
||
},
|
||
}
|
||
clonedRequest.rawClient = r.rawClient
|
||
|
||
// 手动深拷贝嵌套引用类型
|
||
if r.bodyDataReader != nil {
|
||
clonedRequest.bodyDataReader = r.bodyDataReader
|
||
}
|
||
|
||
if r.fileUploadRecallFn != nil {
|
||
clonedRequest.fileUploadRecallFn = r.fileUploadRecallFn
|
||
}
|
||
|
||
// 对于 tlsConfig 类型,需要手动复制
|
||
if r.tlsConfig != nil {
|
||
clonedRequest.tlsConfig = r.tlsConfig.Clone()
|
||
}
|
||
if r.transport != nil {
|
||
clonedRequest.transport = r.transport
|
||
}
|
||
if r.doRawRequest {
|
||
clonedRequest.rawRequest = r.rawRequest
|
||
}
|
||
|
||
if clonedRequest.rawRequest == nil {
|
||
clonedRequest.rawRequest, _ = http.NewRequestWithContext(clonedRequest.ctx, clonedRequest.method, clonedRequest.uri, nil)
|
||
}
|
||
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
|
||
}
|
||
|
||
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 {
|
||
if r.doRawRequest {
|
||
return fmt.Errorf("doRawRequest is true, cannot set uri")
|
||
}
|
||
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
|
||
if r.tlsConfig != nil {
|
||
r.tlsConfig.ServerName = u.Hostname()
|
||
}
|
||
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
|
||
}
|
||
|
||
// Do sends the HTTP request and returns the response.
|
||
func (r *Request) Do() (*Response, error) {
|
||
return Curl(r)
|
||
}
|
||
|
||
// Get sends a GET request to the specified URI and returns the response.
|
||
func (r *Request) Get() (*Response, error) {
|
||
err := r.SetMethod("GET")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return Curl(r)
|
||
}
|
||
|
||
// Post sends a POST request with the provided data to the specified URI and returns the response.
|
||
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)
|
||
}
|
||
|
||
type RequestOpts struct {
|
||
rawRequest *http.Request
|
||
rawClient *http.Client
|
||
transport *http.Transport
|
||
customTransport bool
|
||
|
||
alreadyApply bool
|
||
bodyDataBytes []byte
|
||
bodyDataReader io.Reader
|
||
bodyFormData map[string][]string
|
||
bodyFileData []RequestFile
|
||
//以上优先度为 bodyDataReader> bodyDataBytes > bodyFormData > bodyFileData
|
||
fileUploadRecallFn func(filename string, upPos int64, total int64)
|
||
proxy string
|
||
timeout time.Duration
|
||
dialTimeout time.Duration
|
||
dialFn func(ctx context.Context, network, addr string) (net.Conn, error)
|
||
headers http.Header
|
||
cookies []*http.Cookie
|
||
|
||
queries map[string][]string
|
||
//doRawRequest=true 不对request修改,直接发送
|
||
doRawRequest bool
|
||
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
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
func (r *Request) DialFn() func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||
return r.dialFn
|
||
}
|
||
|
||
// SetDialFn sets the dial function for the request.
|
||
func (r *Request) SetDialFn(dialFn func(ctx context.Context, network, addr string) (net.Conn, error)) {
|
||
r.dialFn = dialFn
|
||
}
|
||
|
||
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
|
||
func (r *Request) SetAutoCalcContentLength(autoCalcContentLength bool) error {
|
||
if r.doRawRequest {
|
||
return fmt.Errorf("doRawRequest is true, cannot set autoCalcContentLength")
|
||
}
|
||
r.autoCalcContentLength = autoCalcContentLength
|
||
return nil
|
||
}
|
||
|
||
func (r *Request) SetAutoCalcContentLengthNoError(autoCalcContentLength bool) *Request {
|
||
r.SetAutoCalcContentLength(autoCalcContentLength)
|
||
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
|
||
}
|
||
|
||
// 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
|
||
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
|
||
}
|
||
|
||
// 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
|
||
func (r *Request) SetCustomDNSNoError(customDNS []string) *Request {
|
||
r.SetCustomDNS(customDNS)
|
||
return r
|
||
}
|
||
|
||
// 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
|
||
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
|
||
}
|
||
|
||
// 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
|
||
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
|
||
}
|
||
|
||
// 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.
|
||
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
|
||
}
|
||
|
||
// CustomHostIP returns the custom IP addresses used for the request.
|
||
func (r *Request) CustomHostIP() []string {
|
||
return r.customIP
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetCustomHostIP(customIP []string) *Request {
|
||
r.customIP = customIP
|
||
return r
|
||
}
|
||
|
||
// AddCustomHostIP adds a custom IP address to the request.
|
||
func (r *Request) AddCustomHostIP(customIP string) *Request {
|
||
r.customIP = append(r.customIP, customIP)
|
||
return r
|
||
}
|
||
|
||
// BodyDataBytes returns the raw body data as a byte slice.
|
||
func (r *Request) BodyDataBytes() []byte {
|
||
return r.bodyDataBytes
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetBodyDataBytes(bodyDataBytes []byte) *Request {
|
||
r.bodyDataBytes = bodyDataBytes
|
||
return r
|
||
}
|
||
|
||
// BodyDataReader returns the raw body data as an io.Reader.
|
||
func (r *Request) BodyDataReader() io.Reader {
|
||
return r.bodyDataReader
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetBodyDataReader(bodyDataReader io.Reader) *Request {
|
||
r.bodyDataReader = bodyDataReader
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) BodyFormData() map[string][]string {
|
||
return r.bodyFormData
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetBodyFormData(bodyFormData map[string][]string) *Request {
|
||
r.bodyFormData = bodyFormData
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetFormData(bodyFormData map[string][]string) *Request {
|
||
return r.SetBodyFormData(bodyFormData)
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFormMapData(bodyFormData map[string]string) *Request {
|
||
for k, v := range bodyFormData {
|
||
r.bodyFormData[k] = append(r.bodyFormData[k], v)
|
||
}
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFormData(k, v string) *Request {
|
||
r.bodyFormData[k] = append(r.bodyFormData[k], v)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) BodyFileData() []RequestFile {
|
||
return r.bodyFileData
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetBodyFileData(bodyFileData []RequestFile) *Request {
|
||
r.bodyFileData = bodyFileData
|
||
return r
|
||
}
|
||
|
||
// Proxy returns the proxy URL for the request.
|
||
func (r *Request) Proxy() string {
|
||
return r.proxy
|
||
}
|
||
|
||
// SetProxy sets the proxy URL for the request.
|
||
func (r *Request) SetProxy(proxy string) *Request {
|
||
r.proxy = proxy
|
||
return r
|
||
}
|
||
|
||
// Timeout returns the timeout duration for the request.
|
||
func (r *Request) Timeout() time.Duration {
|
||
return r.timeout
|
||
}
|
||
|
||
// SetTimeout sets the timeout duration for the request.
|
||
func (r *Request) SetTimeout(timeout time.Duration) *Request {
|
||
r.timeout = timeout
|
||
return r
|
||
}
|
||
|
||
// DialTimeout returns the dial timeout duration for the request.
|
||
func (r *Request) DialTimeout() time.Duration {
|
||
return r.dialTimeout
|
||
}
|
||
|
||
// SetDialTimeout sets the dial timeout duration for the request.
|
||
func (r *Request) SetDialTimeout(dialTimeout time.Duration) *Request {
|
||
r.dialTimeout = dialTimeout
|
||
return r
|
||
}
|
||
|
||
// Headers returns the request headers as an http.Header.
|
||
func (r *Request) Headers() http.Header {
|
||
return r.headers
|
||
}
|
||
|
||
// SetHeaders sets the request headers using an http.Header.
|
||
// Note: If doRawRequest is true, this function will not work.
|
||
func (r *Request) SetHeaders(headers http.Header) *Request {
|
||
r.headers = headers
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddHeader(key, val string) *Request {
|
||
r.headers.Add(key, val)
|
||
return r
|
||
}
|
||
|
||
// SetHeader sets a single header in the request.
|
||
// Note: If doRawRequest is true, this function will not work.
|
||
func (r *Request) SetHeader(key, val string) *Request {
|
||
r.headers.Set(key, val)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetContentType(ct string) *Request {
|
||
r.headers.Set("Content-Type", ct)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetUserAgent(ua string) *Request {
|
||
r.headers.Set("User-Agent", ua)
|
||
return r
|
||
}
|
||
|
||
// Cookies returns the request cookies as a slice of http.Cookie.
|
||
func (r *Request) Cookies() []*http.Cookie {
|
||
return r.cookies
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetCookies(cookies []*http.Cookie) *Request {
|
||
r.cookies = cookies
|
||
return r
|
||
}
|
||
|
||
// Transport returns the http.Transport used for the request.
|
||
func (r *Request) Transport() *http.Transport {
|
||
return r.transport
|
||
}
|
||
|
||
// SetTransport set the http.Transport used for the request.
|
||
// Note: If doRawClient is true, this function will not work.
|
||
func (r *Request) SetTransport(transport *http.Transport) *Request {
|
||
r.transport = transport
|
||
r.customTransport = true
|
||
return r
|
||
}
|
||
|
||
// Queries returns the request queries as a map of string slices.
|
||
func (r *Request) Queries() map[string][]string {
|
||
return r.queries
|
||
}
|
||
|
||
// SetQueries sets the request queries using a map of string slices.
|
||
func (r *Request) SetQueries(queries map[string][]string) *Request {
|
||
r.queries = queries
|
||
return r
|
||
}
|
||
|
||
// AddQueries adds multiple query parameters to the request.
|
||
func (r *Request) AddQueries(queries map[string]string) *Request {
|
||
for k, v := range queries {
|
||
r.queries[k] = append(r.queries[k], v)
|
||
}
|
||
return r
|
||
}
|
||
|
||
// AddQuery adds a single query parameter to the request.
|
||
func (r *Request) AddQuery(key, value string) *Request {
|
||
r.queries[key] = append(r.queries[key], value)
|
||
return r
|
||
}
|
||
|
||
// DelQueryKv removes a specific value from a query parameter.
|
||
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
|
||
}
|
||
|
||
// DelQuery removes a query parameter from the request.
|
||
func (r *Request) DelQuery(key string) *Request {
|
||
if _, ok := r.queries[key]; !ok {
|
||
return r
|
||
}
|
||
delete(r.queries, key)
|
||
return r
|
||
}
|
||
|
||
// DoRawRequest returns whether the request will be sent as a raw request.
|
||
func (r *Request) DoRawRequest() bool {
|
||
return r.doRawRequest
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetDoRawRequest(doRawRequest bool) *Request {
|
||
r.doRawRequest = doRawRequest
|
||
return r
|
||
}
|
||
|
||
// SkipTLSVerify returns whether the request will skip TLS verification.
|
||
func (r *RequestOpts) SkipTLSVerify() bool {
|
||
return r.skipTLSVerify
|
||
}
|
||
|
||
// SetSkipTLSVerify Sets whether the request will skip TLS verification.
|
||
func (r *Request) SetSkipTLSVerify(skipTLSVerify bool) *Request {
|
||
r.skipTLSVerify = skipTLSVerify
|
||
return r
|
||
}
|
||
|
||
// TlsConfig returns the TLS configuration used for the request.
|
||
func (r *Request) TlsConfig() *tls.Config {
|
||
return r.tlsConfig
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetTlsConfig(tlsConfig *tls.Config) *Request {
|
||
r.tlsConfig = tlsConfig
|
||
return r
|
||
}
|
||
|
||
// AutoFetchRespBody returns whether the response body will be automatically fetched.
|
||
func (r *Request) AutoFetchRespBody() bool {
|
||
return r.autoFetchRespBody
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) SetAutoFetchRespBody(autoFetchRespBody bool) *Request {
|
||
r.autoFetchRespBody = autoFetchRespBody
|
||
return r
|
||
}
|
||
|
||
// ResetReqHeader resets the request headers to an empty http.Header.
|
||
func (r *Request) ResetReqHeader() *Request {
|
||
r.headers = make(http.Header)
|
||
return r
|
||
}
|
||
|
||
// ResetReqCookies resets the request cookies to an empty slice.
|
||
func (r *Request) ResetReqCookies() *Request {
|
||
r.cookies = []*http.Cookie{}
|
||
return r
|
||
}
|
||
|
||
// AddSimpleCookie add a key-value cookie to the request.
|
||
// the path will be set to "/"
|
||
func (r *Request) AddSimpleCookie(key, value string) *Request {
|
||
r.cookies = append(r.cookies, &http.Cookie{Name: key, Value: value, Path: "/"})
|
||
return r
|
||
}
|
||
|
||
// AddCookie adds a cookie to the request with the specified key, value, and path.
|
||
func (r *Request) AddCookie(key, value, path string) *Request {
|
||
r.cookies = append(r.cookies, &http.Cookie{Name: key, Value: value, Path: path})
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFile(formName, filepath string) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.bodyFileData = append(r.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: stat.Name(),
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: "application/octet-stream",
|
||
FilePath: filepath,
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
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",
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileWithName(formName, filepath, filename string) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.bodyFileData = append(r.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: filename,
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: "application/octet-stream",
|
||
FilePath: filepath,
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileWithType(formName, filepath, filetype string) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.bodyFileData = append(r.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: stat.Name(),
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: filetype,
|
||
FilePath: filepath,
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileWithNameAndType(formName, filepath, filename, filetype string) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.bodyFileData = append(r.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: filename,
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: filetype,
|
||
FilePath: filepath,
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
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,
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileNoError(formName, filepath string) *Request {
|
||
r.AddFile(formName, filepath)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileWithNameNoError(formName, filepath, filename string) *Request {
|
||
r.AddFileWithName(formName, filepath, filename)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileWithTypeNoError(formName, filepath, filetype string) *Request {
|
||
r.AddFileWithType(formName, filepath, filetype)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileWithNameAndTypeNoError(formName, filepath, filename, filetype string) *Request {
|
||
r.AddFileWithNameAndType(formName, filepath, filename, filetype)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileStreamNoError(formName, filename string, size int64, stream io.Reader) *Request {
|
||
r.AddFileStream(formName, filename, size, stream)
|
||
return r
|
||
}
|
||
|
||
// 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.
|
||
func (r *Request) AddFileStreamWithTypeNoError(formName, filename, filetype string, size int64, stream io.Reader) *Request {
|
||
r.AddFileStreamWithType(formName, filename, filetype, size, stream)
|
||
return r
|
||
}
|
||
|
||
// HttpClient returns the http.Client used for the request.
|
||
func (r *Request) HttpClient() (*http.Client, error) {
|
||
err := applyOptions(r)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return r.rawClient, nil
|
||
}
|
||
|
||
type RequestFile struct {
|
||
FormName string
|
||
FileName string
|
||
FileData io.Reader
|
||
FileSize int64
|
||
FileType string
|
||
FilePath string
|
||
}
|
||
|
||
type RequestOpt func(opt *RequestOpts) error
|
||
|
||
// WithDialTimeout sets the dial timeout for the request.
|
||
// If use custom Transport Dialer, this function will nolonger work.
|
||
func WithDialTimeout(timeout time.Duration) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.dialTimeout = timeout
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
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
|
||
}
|
||
}
|
||
|
||
// WithTimeout sets the timeout for the request.
|
||
// If use custom Transport Dialer, this function will nolonger work.
|
||
func WithTimeout(timeout time.Duration) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.timeout = timeout
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithTlsConfig sets the TLS configuration for the request.
|
||
// If use custom Transport Dialer, this function will nolonger work.
|
||
func WithTlsConfig(tlscfg *tls.Config) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.tlsConfig = tlscfg
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithHeaders sets the request headers using an http.Header.
|
||
// If doRawRequest is true, this function will not work.
|
||
func WithHeader(key, val string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.headers.Set(key, val)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithHeaderMap sets the request headers using a map of string to string.
|
||
// If doRawRequest is true, this function will not work.
|
||
func WithHeaderMap(header map[string]string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
for key, val := range header {
|
||
opt.headers.Set(key, val)
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithReader(r io.Reader) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.bodyDataReader = r
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithBytes(r []byte) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.bodyDataBytes = r
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithFormData(data map[string][]string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.bodyFormData = data
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithFileDatas(data []RequestFile) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.bodyFileData = data
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithFileData(data RequestFile) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.bodyFileData = append(opt.bodyFileData, data)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithAddFile(formName, filepath string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: stat.Name(),
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: "application/octet-stream",
|
||
FilePath: filepath,
|
||
})
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithAddFileWithName(formName, filepath, filename string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: filename,
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: "application/octet-stream",
|
||
})
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithAddFileWithType(formName, filepath, filetype string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: stat.Name(),
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: filetype,
|
||
})
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithAddFileWithNameAndType(formName, filepath, filename, filetype string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
stat, err := os.Stat(filepath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
opt.bodyFileData = append(opt.bodyFileData, RequestFile{
|
||
FormName: formName,
|
||
FileName: filename,
|
||
FileData: nil,
|
||
FileSize: stat.Size(),
|
||
FileType: filetype,
|
||
})
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
func WithFetchRespBody(fetch bool) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.autoFetchRespBody = fetch
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithCookies sets the request cookies using a slice of http.Cookie.
|
||
func WithCookies(ck []*http.Cookie) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.cookies = ck
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithCookie adds a cookie to the request with the specified key, value, and path.
|
||
func WithCookie(key, val, path string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.cookies = append(opt.cookies, &http.Cookie{Name: key, Value: val, Path: path})
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithSimpleCookie adds a simple cookie to the request with the specified key and value.
|
||
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
|
||
}
|
||
}
|
||
|
||
// WithCookieMap sets the request cookies using a map of string to string.
|
||
func WithCookieMap(header map[string]string, path string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
for key, val := range header {
|
||
opt.cookies = append(opt.cookies, &http.Cookie{Name: key, Value: val, Path: path})
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithQueries sets the request queries using a map of string slices.
|
||
func WithQueries(queries map[string][]string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.queries = queries
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
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
|
||
}
|
||
}
|
||
|
||
// WithAddQuery adds a single query parameter to the request.
|
||
func WithAddQuery(key, val string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.queries[key] = append(opt.queries[key], val)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithProxy sets the proxy URL for the request.
|
||
func WithProxy(proxy string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.proxy = proxy
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
// })
|
||
func WithProcess(fn func(string, int64, int64)) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.fileUploadRecallFn = fn
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithContentType sets the Content-Type header for the request.
|
||
// This function will overwrite any existing Content-Type header.
|
||
func WithContentType(ct string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.headers.Set("Content-Type", ct)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithUserAgent sets the User-Agent header for the request.
|
||
// This function will overwrite any existing User-Agent header.
|
||
func WithUserAgent(ua string) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.headers.Set("User-Agent", ua)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithSkipTLSVerify sets whether the request will skip TLS verification.
|
||
// If set to true, the request will not verify the server's TLS certificate.
|
||
func WithSkipTLSVerify(skip bool) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.skipTLSVerify = skip
|
||
return nil
|
||
}
|
||
}
|
||
|
||
/*
|
||
// 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.
|
||
func WithDisableRedirect(disable bool) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.disableRedirect = disable
|
||
return nil
|
||
}
|
||
}
|
||
*/
|
||
|
||
// 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.
|
||
func WithDoRawRequest(doRawRequest bool) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.doRawRequest = doRawRequest
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithTransport sets the http.Transport used for the request.
|
||
func WithTransport(hs *http.Transport) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.transport = hs
|
||
opt.customTransport = true
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithRawRequest sets a custom http.Request for the request.
|
||
func WithRawRequest(req *http.Request) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.rawRequest = req
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithRawClient sets a custom http.Client for the request.
|
||
func WithRawClient(hc *http.Client) RequestOpt {
|
||
return func(opt *RequestOpts) error {
|
||
opt.rawClient = hc
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
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)
|
||
}
|
||
}
|
||
opt.customIP = ip
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// WithAddCustomHostIP adds a custom IP to the request.
|
||
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
|
||
}
|
||
}
|
||
|
||
// 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.
|
||
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
|
||
}
|
||
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)
|
||
}
|
||
}
|
||
opt.customDNS = customDNS
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
}
|
||
|
||
type Response struct {
|
||
*http.Response
|
||
req Request
|
||
data *Body
|
||
rawClient *http.Client
|
||
}
|
||
|
||
type Body struct {
|
||
full []byte
|
||
raw io.ReadCloser
|
||
isFull bool
|
||
sync.Mutex
|
||
}
|
||
|
||
func (b *Body) readAll() {
|
||
b.Lock()
|
||
defer b.Unlock()
|
||
if !b.isFull {
|
||
if b.raw == nil {
|
||
b.isFull = true
|
||
return
|
||
}
|
||
b.full, _ = io.ReadAll(b.raw)
|
||
b.isFull = true
|
||
b.raw.Close()
|
||
}
|
||
}
|
||
|
||
// String will read the body and return it as a string.
|
||
// if the body is too large, it may cause high memory usage.
|
||
func (b *Body) String() string {
|
||
b.readAll()
|
||
return string(b.full)
|
||
}
|
||
|
||
// Bytes will read the body and return it as a byte slice.
|
||
// if the body is too large, it may cause high memory usage.
|
||
func (b *Body) Bytes() []byte {
|
||
b.readAll()
|
||
return b.full
|
||
}
|
||
|
||
// 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.
|
||
func (b *Body) Unmarshal(u interface{}) error {
|
||
b.readAll()
|
||
return json.Unmarshal(b.full, u)
|
||
}
|
||
|
||
// Reader returns a reader for the body
|
||
// if this function is called, other functions like String, Bytes, Unmarshal not work
|
||
func (b *Body) Reader() io.ReadCloser {
|
||
b.Lock()
|
||
defer b.Unlock()
|
||
if b.isFull {
|
||
return io.NopCloser(bytes.NewReader(b.full))
|
||
}
|
||
b.isFull = true
|
||
return b.raw
|
||
}
|
||
|
||
// Close closes the body reader.
|
||
func (b *Body) Close() error {
|
||
return b.raw.Close()
|
||
}
|
||
|
||
// GetRequest returns the original Request object associated with the Response.
|
||
func (r *Response) GetRequest() Request {
|
||
return r.req
|
||
}
|
||
|
||
// Body returns the Body object associated with the Response.
|
||
func (r *Response) Body() *Body {
|
||
return r.data
|
||
}
|
||
|
||
// Close closes the response body and releases any resources associated with it.
|
||
func (r *Response) Close() error {
|
||
if r != nil && r.data != nil && r.data.raw != nil {
|
||
return r.Response.Body.Close()
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 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.
|
||
func (r *Response) CloseAll() error {
|
||
if r.rawClient != nil {
|
||
r.rawClient.CloseIdleConnections()
|
||
}
|
||
return r.Close()
|
||
}
|
||
|
||
// HttpClient returns the http.Client used for the request.
|
||
func (r *Response) HttpClient() *http.Client {
|
||
return r.rawClient
|
||
}
|
||
|
||
// Curl sends the HTTP request and returns the response.
|
||
func Curl(r *Request) (*Response, error) {
|
||
r.errInfo = nil
|
||
err := applyOptions(r)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("apply options error: %s", err)
|
||
}
|
||
r.rawRequest = r.rawRequest.WithContext(r.doCtx)
|
||
resp, err := r.rawClient.Do(r.rawRequest)
|
||
var res = Response{
|
||
Response: resp,
|
||
req: *r,
|
||
data: new(Body),
|
||
rawClient: r.rawClient,
|
||
}
|
||
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()
|
||
}
|
||
return &res, r.errInfo
|
||
}
|
||
|
||
// NewReq creates a new Request with the specified URI and default method "GET".
|
||
func NewReq(uri string, opts ...RequestOpt) *Request {
|
||
return NewSimpleRequest(uri, "GET", opts...)
|
||
}
|
||
|
||
// NewReqWithContext creates a new Request with the specified URI and default method "GET" using the provided context.
|
||
func NewReqWithContext(ctx context.Context, uri string, opts ...RequestOpt) *Request {
|
||
return NewSimpleRequestWithContext(ctx, uri, "GET", opts...)
|
||
}
|
||
|
||
// NewSimpleRequest creates a new Request with the specified URI and method.
|
||
func NewSimpleRequest(uri string, method string, opts ...RequestOpt) *Request {
|
||
r, _ := newRequest(context.Background(), uri, method, opts...)
|
||
return r
|
||
}
|
||
|
||
// NewRequest creates a new Request with the specified URI and method.
|
||
func NewRequest(uri string, method string, opts ...RequestOpt) (*Request, error) {
|
||
return newRequest(context.Background(), uri, method, opts...)
|
||
}
|
||
|
||
// NewSimpleRequestWithContext creates a new Request with the specified URI and method using the provided context.
|
||
func NewSimpleRequestWithContext(ctx context.Context, uri string, method string, opts ...RequestOpt) *Request {
|
||
r, _ := newRequest(ctx, uri, method, opts...)
|
||
return r
|
||
}
|
||
|
||
// NewRequestWithContext creates a new Request with the specified URI and method using the provided context.
|
||
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) {
|
||
var req *http.Request
|
||
var err error
|
||
if method == "" {
|
||
method = "GET"
|
||
}
|
||
method = strings.ToUpper(method)
|
||
req, err = http.NewRequestWithContext(ctx, method, uri, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var r = &Request{
|
||
ctx: ctx,
|
||
uri: uri,
|
||
method: method,
|
||
RequestOpts: RequestOpts{
|
||
rawRequest: req,
|
||
rawClient: nil,
|
||
timeout: DefaultTimeout,
|
||
dialTimeout: DefaultDialTimeout,
|
||
autoFetchRespBody: DefaultFetchRespBody,
|
||
lookUpIPfn: net.DefaultResolver.LookupIPAddr,
|
||
bodyFormData: make(map[string][]string),
|
||
queries: make(map[string][]string),
|
||
},
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
}
|
||
if r.rawClient == nil {
|
||
r.rawClient = new(http.Client)
|
||
}
|
||
if r.tlsConfig == nil {
|
||
r.tlsConfig = &tls.Config{
|
||
NextProtos: []string{"h2", "http/1.1"},
|
||
}
|
||
}
|
||
r.tlsConfig.InsecureSkipVerify = r.skipTLSVerify
|
||
if r.transport == nil {
|
||
r.transport = &http.Transport{
|
||
ForceAttemptHTTP2: true,
|
||
DialContext: DefaultDialFunc,
|
||
DialTLSContext: DefaultDialTlsFunc,
|
||
Proxy: DefaultProxyURL(),
|
||
}
|
||
}
|
||
return r, nil
|
||
}
|
||
|
||
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
|
||
|
||
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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for idx, v := range r.bodyFileData {
|
||
var fw, err = w.CreateFormFile(v.FormName, v.FileName)
|
||
if err != nil {
|
||
r.errInfo = err
|
||
pw.CloseWithError(err) // close pipe with error
|
||
return
|
||
}
|
||
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
|
||
}
|
||
}
|
||
if _, err := copyWithContext(r.ctx, r.fileUploadRecallFn, v.FileName, v.FileSize, fw, v.FileData); err != nil {
|
||
r.errInfo = err
|
||
r.bodyFileData[idx] = v
|
||
pw.CloseWithError(err) // close pipe with error
|
||
return
|
||
}
|
||
}
|
||
|
||
if err := w.Close(); err != nil {
|
||
pw.CloseWithError(err) // close pipe with error if writer close fails
|
||
}
|
||
}()
|
||
|
||
r.rawRequest.Body = pr
|
||
return nil
|
||
}
|
||
return nil
|
||
}
|
||
|
||
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))
|
||
}
|
||
}
|
||
}
|
||
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
|
||
}
|
||
}
|
||
return
|
||
},
|
||
}
|
||
r.lookUpIPfn = resolver.LookupIPAddr
|
||
}
|
||
if r.tlsConfig == nil {
|
||
r.tlsConfig = &tls.Config{
|
||
NextProtos: []string{"h2", "http/1.1"},
|
||
}
|
||
}
|
||
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)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
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()
|
||
|
||
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 {
|
||
if recall != nil {
|
||
go recall(filename, count, total)
|
||
}
|
||
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)
|
||
}
|
||
}
|
||
}
|
||
|
||
func NewReqWithClient(client Client, uri string, opts ...RequestOpt) *Request {
|
||
return NewSimpleRequestWithClient(client, uri, "GET", opts...)
|
||
}
|
||
|
||
func NewReqWithContextWithClient(ctx context.Context, client Client, uri string, opts ...RequestOpt) *Request {
|
||
return NewSimpleRequestWithContextWithClient(ctx, client, uri, "GET", opts...)
|
||
}
|
||
|
||
func NewSimpleRequestWithClient(client Client, uri string, method string, opts ...RequestOpt) *Request {
|
||
r, _ := NewRequestWithContextWithClient(context.Background(), client, uri, method, opts...)
|
||
return r
|
||
}
|
||
|
||
func NewRequestWithClient(client Client, uri string, method string, opts ...RequestOpt) (*Request, error) {
|
||
return NewRequestWithContextWithClient(context.Background(), client, uri, method, opts...)
|
||
}
|
||
|
||
func NewSimpleRequestWithContextWithClient(ctx context.Context, client Client, uri string, method string, opts ...RequestOpt) *Request {
|
||
r, _ := NewRequestWithContextWithClient(ctx, client, uri, method, opts...)
|
||
return r
|
||
}
|
||
|
||
func NewRequestWithContextWithClient(ctx context.Context, client Client, uri string, method string, opts ...RequestOpt) (*Request, error) {
|
||
req, err := newRequest(ctx, uri, method, opts...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
req.rawClient = client.Client
|
||
return req, err
|
||
}
|