package starnet import ( "bytes" "context" "crypto/tls" "encoding/json" "fmt" "io" "mime/multipart" "net" "net/http" "net/url" "os" "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), contentLength: r.contentLength, queries: CloneStringMapSlice(r.queries), bodyDataBytes: CloneByteSlice(r.bodyDataBytes), customTransport: r.customTransport, 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 contentLength int64 // 人工设置 } func (r *RequestOpts) ContentLength() int64 { return r.contentLength } // SetContentLength sets the Content-Length header for the request. // This function will overwrite any existing or auto calculated Content-Length header. // if the length is less than 0, it will not set the Content-Length header. chunked transfer encoding will be used instead. // chunked transfer encoding may cause some servers to reject the request if they do not support it. // Note that this function will not work if doRawRequest is true func (r *RequestOpts) SetContentLength(contextLength int64) { r.contentLength = contextLength } 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, data will be cached in memory. // So it may cause high memory usage if the request body is large. // If set to false, the Content-Length header will not be set,unless the request body is a byte slice or bytes.Buffer which has a specific length. // Note that this function will not work if doRawRequest is true or the ContentLength is already set. 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, data will be cached in memory. // So it may cause high memory usage if the request body is large. // If set to false, the Content-Length header will not be set,unless the request body is a byte slice or bytes.Buffer which has a specific length. // Note that this function will not work if doRawRequest is true or ContentLength already set func WithAutoCalcContentLength(autoCalcContentLength bool) RequestOpt { return func(opt *RequestOpts) error { opt.autoCalcContentLength = autoCalcContentLength return nil } } // WithContentLength sets the Content-Length for the request. // This function will overwrite any existing or auto calculated Content-Length header. // if the length is less than 0, it will not set the Content-Length header. chunked transfer encoding will be used instead. // chunked transfer encoding may cause some servers to reject the request if they do not support it. // Note that this function will not work if doRawRequest is true func WithContentLength(length int64) RequestOpt { return func(opt *RequestOpts) error { opt.contentLength = length 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 r.rawRequest.ContentLength = 0 if r.bodyDataReader != nil { switch v := r.bodyDataReader.(type) { case *bytes.Buffer: r.rawRequest.ContentLength = int64(v.Len()) case *bytes.Reader: r.rawRequest.ContentLength = int64(v.Len()) case *strings.Reader: r.rawRequest.ContentLength = int64(v.Len()) } r.rawRequest.Body = io.NopCloser(r.bodyDataReader) return nil } if len(r.bodyDataBytes) != 0 { r.rawRequest.ContentLength = int64(len(r.bodyDataBytes)) r.rawRequest.Body = io.NopCloser(bytes.NewReader(r.bodyDataBytes)) return nil } if len(r.bodyFormData) != 0 && len(r.bodyFileData) == 0 { var body = url.Values{} for k, v := range r.bodyFormData { for _, vv := range v { body.Add(k, vv) } } r.rawRequest.ContentLength = int64(len(body.Encode())) 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.ContentLength = int64(len(data)) req.Body = io.NopCloser(bytes.NewBuffer(data)) } } if r.contentLength > 0 { req.ContentLength = r.contentLength } else if r.contentLength < 0 { //force use chunked transfer encoding req.ContentLength = 0 } } 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 }