Fix the abnormal path retrieval issue in some systems.

This commit is contained in:
2026-04-09 01:00:31 +08:00
parent 76f0d815aa
commit 792f547dc3
7 changed files with 453 additions and 336 deletions

View File

@@ -1,326 +1,343 @@
package request
import (
"bytes"
"compress/flate"
"compress/gzip"
"encoding/json"
"io"
"net/http"
"net/http/cookiejar"
"reflect"
"strings"
"time"
"unsafe"
"github.com/andybalholm/brotli"
"github.com/go-resty/resty/v2"
"github.com/skycheung803/go-bypasser"
)
type RestyClient struct {
client *resty.Client
}
func (request *RestyClient) Resty() *resty.Client {
return request.client
}
// NewClient 创建一个基于 uTLS 指纹与 HTTP/2 指纹的 Resty 客户端
// baseURL 不为空则设置默认 BaseURLproxyStr 不为空则启用 HTTP 代理(仅 HTTP/1.1
// persistCookies 启用持久化 CookiefollowRedirect 启用重定向跟随timeout 设置超时时间0 或负数则默认 60 秒)
func NewClient(baseURL string, proxyStr string, persistCookies bool, timeout int) *RestyClient {
rc := resty.New()
if baseURL != "" {
rc.SetBaseURL(baseURL)
}
if persistCookies {
jar, _ := cookiejar.New(nil)
rc.SetCookieJar(jar)
}
// 设置请求超时时间,如果传入 0 或负数则默认 60 秒
if timeout <= 0 {
timeout = 60
}
rc.SetTimeout(time.Duration(timeout) * time.Second)
// 统一设置客户端默认请求头(调用级 headers 可覆盖),字段按字母顺序排列
rc.SetHeader("accept", "*/*")
rc.SetHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
rc.SetHeader("connection", "keep-alive")
rc.SetHeader("pragma", "no-cache")
rc.SetHeader("priority", "u=1,i")
rc.SetHeader("sec-ch-ua", "\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Google Chrome\";v=\"146\"")
rc.SetHeader("sec-ch-ua-mobile", "?0")
rc.SetHeader("sec-ch-ua-platform", "\"macOS\"")
rc.SetHeader("sec-fetch-dest", "empty")
rc.SetHeader("sec-fetch-mode", "cors")
rc.SetHeader("sec-fetch-site", "same-origin")
rc.SetHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36")
// 初始化 go-bypasser 替代原有的 spoofed-round-tripper
opts := []bypasser.BypasserOption{
bypasser.WithInsecureSkipVerify(true),
}
if proxyStr != "" {
opts = append(opts, bypasser.WithProxy(proxyStr))
}
bypass, err := bypasser.NewBypasser(opts...)
if err != nil {
panic(err)
}
rc.SetTransport(bypass.Transport)
return &RestyClient{client: rc}
}
// fillResponseBody 使用反射强制填充响应体
// 当 Resty 因为重定向策略错误而提前返回时,它可能不会读取 Body
// 此方法手动读取 RawResponse.Body 并回填到 resty.Response 的私有 body 字段中
func (request *RestyClient) fillResponseBody(resp *resty.Response) {
if resp == nil || resp.RawResponse == nil {
return
}
// 如果已经有 body 内容,则不处理
if len(resp.Body()) > 0 {
return
}
// 读取底层 Body
bodyBytes, err := io.ReadAll(resp.RawResponse.Body)
if err != nil {
return
}
resp.RawResponse.Body.Close()
// 重置 Body 以便后续可能得读取
resp.RawResponse.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 使用反射设置私有字段 body
v := reflect.ValueOf(resp).Elem()
f := v.FieldByName("body")
if f.IsValid() {
// 必须使用 UnsafeAddr 获取未导出字段的地址
rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
rf.SetBytes(bodyBytes)
}
// 设置 size 字段
s := v.FieldByName("size")
if s.IsValid() {
rs := reflect.NewAt(s.Type(), unsafe.Pointer(s.UnsafeAddr())).Elem()
rs.SetInt(int64(len(bodyBytes)))
}
}
// makeReq 构造带可选请求头的 resty.Request
// 功能:基于客户端创建请求对象,并在传入 headers 时进行设置
// 返回:带有请求头的请求对象
func (request *RestyClient) makeReq(headers map[string]string, cookies []*http.Cookie) *resty.Request {
req := request.client.R()
if len(headers) > 0 {
req = req.SetHeaders(headers)
}
if len(cookies) > 0 {
req = req.SetCookies(cookies)
}
return req
}
// doWithEncodingFallback 封装请求发送并在出现压缩相关错误时进行一次降级重试
// 逻辑:首次请求失败且错误包含 gzip/zstd/brotli/magic number mismatch 时,设置 accept-encoding 为 identity 重试一次
func (request *RestyClient) doWithEncodingFallback(headers map[string]string, cookies []*http.Cookie, allowRedirect bool, do func(*resty.Request) (*resty.Response, error)) (*resty.Response, error) {
req := request.makeReq(headers, cookies)
if allowRedirect {
request.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(10))
} else {
// 使用 http.ErrUseLastResponse 确保 302 响应被返回且 Body 可读,而不是报错
request.client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}))
}
resp, err := do(req)
// 尝试补救响应体(特别是当重定向被禁用导致报错时)
request.fillResponseBody(resp)
if err == nil {
return resp, nil
}
s := err.Error()
if strings.Contains(s, "gzip: invalid header") || strings.Contains(s, "magic number mismatch") || strings.Contains(s, "zstd") || strings.Contains(s, "brotli") {
h2 := map[string]string{}
for k, v := range headers {
if strings.ToLower(k) != "accept-encoding" {
h2[k] = v
}
}
h2["Accept-Encoding"] = "identity"
req2 := request.makeReq(h2, cookies)
resp2, err2 := do(req2)
request.fillResponseBody(resp2)
if err2 == nil {
return resp2, nil
}
}
return resp, err
}
// decodeResponse 处理响应解压与 JSON 解析
// 功能:自动识别 gzip 压缩并解压;在 result 非空时按 JSON 解析到 result
// 返回:解析错误(成功时为 nil
func (request *RestyClient) decodeResponse(resp *resty.Response, result interface{}) error {
if resp == nil {
return nil
}
ct := strings.ToLower(resp.Header().Get("Content-Type"))
ce := strings.ToLower(resp.Header().Get("Content-Encoding"))
body := resp.Body()
if strings.Contains(ce, "gzip") && len(body) > 0 {
gr, gerr := gzip.NewReader(bytes.NewReader(body))
if gerr == nil {
defer gr.Close()
if dec, derr := io.ReadAll(gr); derr == nil {
body = dec
resp.SetBody(body)
}
}
} else if strings.Contains(ce, "deflate") && len(body) > 0 {
// 处理 deflate 压缩
dr := flate.NewReader(bytes.NewReader(body))
defer dr.Close()
if dec, derr := io.ReadAll(dr); derr == nil {
body = dec
resp.SetBody(body)
}
} else if strings.Contains(ce, "br") && len(body) > 0 {
// 处理 brotli 压缩
br := brotli.NewReader(bytes.NewReader(body))
if dec, derr := io.ReadAll(br); derr == nil {
body = dec
resp.SetBody(body) // 将解压后的 body 写回 response
}
}
if result != nil && (strings.Contains(ct, "application/json") || json.Valid(body)) {
if err := json.Unmarshal(body, result); err != nil {
return err
}
}
return nil
}
// RestyGet 发送 GET 请求
func (request *RestyClient) RestyGet(path string, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Get(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyPost 发送 POST 请求
func (request *RestyClient) RestyPost(path string, data any, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.SetBody(data).Post(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyPut 发送 PUT 请求
// 功能:发送 PUT支持请求级 headers 覆盖客户端默认,自动识别 gzip 并解析 JSON
// 返回:响应对象与错误信息
func (request *RestyClient) RestyPut(path string, data any, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.SetBody(data).Put(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyPatch 发送 PATCH 请求
// 功能:发送 PATCH支持请求级 headers 覆盖客户端默认,自动识别 gzip 并解析 JSON
// 返回:响应对象与错误信息
func (request *RestyClient) RestyPatch(path string, data any, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.SetBody(data).Patch(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyDelete 发送 DELETE 请求
// 功能:发送 DELETE支持请求级 headers 覆盖客户端默认,自动识别 gzip 并解析 JSON
// 返回:响应对象与错误信息
func (request *RestyClient) RestyDelete(path string, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Delete(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyHead 发送 HEAD 请求
// 功能:发送 HEAD支持请求级 headers 覆盖客户端默认HEAD 通常无正文
// 返回:响应对象与错误信息
func (request *RestyClient) RestyHead(path string, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Head(path)
})
if resp == nil && err != nil {
return nil, err
}
return resp, err
}
// RestyOptions 发送 OPTIONS 请求
// 功能:发送 OPTIONS支持请求级 headers 覆盖客户端默认
// 返回:响应对象与错误信息
func (request *RestyClient) RestyOptions(path string, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Options(path)
})
if resp == nil && err != nil {
return nil, err
}
return resp, err
}
package request
import (
"bytes"
"compress/flate"
"compress/gzip"
"encoding/json"
"io"
"net/http"
"net/http/cookiejar"
"reflect"
"strings"
"time"
"unsafe"
"github.com/andybalholm/brotli"
"github.com/go-resty/resty/v2"
"github.com/skycheung803/go-bypasser"
)
type RestyClient struct {
client *resty.Client
}
func (request *RestyClient) Resty() *resty.Client {
return request.client
}
// NewClient 创建一个基于 uTLS 指纹与 HTTP/2 指纹的 Resty 客户端
// baseURL 不为空则设置默认 BaseURLproxyStr 不为空则启用 HTTP 代理(仅 HTTP/1.1
// persistCookies 启用持久化 CookiefollowRedirect 启用重定向跟随timeout 设置超时时间0 或负数则默认 60 秒)
func NewClient(baseURL string, proxyStr string, persistCookies bool, timeout int) *RestyClient {
rc := resty.New()
if baseURL != "" {
rc.SetBaseURL(baseURL)
}
if persistCookies {
jar, _ := cookiejar.New(nil)
rc.SetCookieJar(jar)
}
// 设置请求超时时间,如果传入 0 或负数则默认 60 秒
if timeout <= 0 {
timeout = 60
}
rc.SetTimeout(time.Duration(timeout) * time.Second)
// 统一设置客户端默认请求头(调用级 headers 可覆盖),字段按字母顺序排列
rc.SetHeader("accept", "*/*")
rc.SetHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
rc.SetHeader("connection", "keep-alive")
rc.SetHeader("pragma", "no-cache")
rc.SetHeader("priority", "u=1,i")
rc.SetHeader("sec-ch-ua", "\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Google Chrome\";v=\"146\"")
rc.SetHeader("sec-ch-ua-mobile", "?0")
rc.SetHeader("sec-ch-ua-platform", "\"macOS\"")
rc.SetHeader("sec-fetch-dest", "empty")
rc.SetHeader("sec-fetch-mode", "cors")
rc.SetHeader("sec-fetch-site", "same-origin")
rc.SetHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36")
// 初始化 go-bypasser 替代原有的 spoofed-round-tripper
opts := []bypasser.BypasserOption{
bypasser.WithInsecureSkipVerify(true),
}
if proxyStr != "" {
opts = append(opts, bypasser.WithProxy(proxyStr))
}
bypass, err := bypasser.NewBypasser(opts...)
if err != nil {
panic(err)
}
rc.SetTransport(&sanitizeTransport{t: bypass.Transport})
return &RestyClient{client: rc}
}
// sanitizeTransport 包装 http.RoundTripper 以修复底层库可能违背 Go 接口约定的行为
type sanitizeTransport struct {
t http.RoundTripper
}
func (s *sanitizeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := s.t.RoundTrip(req)
// net/http 规定 RoundTripper 要么返回有效的 resp 和 nil error要么返回 nil resp 和有效的 error。
// 某些第三方库(如部分 tls-client 封装)在遇到网络小问题时会同时返回 resp 和 err。
// 这会导致 net/http 打印 "RoundTripper returned a response & error; ignoring response" 并强制丢弃响应。
// 在这里我们进行修正:如果已经拿到了响应(哪怕是不完整的),我们优先保留响应并将 err 置空,让上层通过读取 Body 自行发现错误。
if resp != nil && err != nil {
err = nil
}
return resp, err
}
// fillResponseBody 使用反射强制填充响应体
// 当 Resty 因为重定向策略错误而提前返回时,它可能不会读取 Body
// 此方法手动读取 RawResponse.Body 并回填到 resty.Response 的私有 body 字段中
func (request *RestyClient) fillResponseBody(resp *resty.Response) {
if resp == nil || resp.RawResponse == nil {
return
}
// 如果已经有 body 内容,则不处理
if len(resp.Body()) > 0 {
return
}
// 读取底层 Body
bodyBytes, err := io.ReadAll(resp.RawResponse.Body)
if err != nil {
return
}
resp.RawResponse.Body.Close()
// 重置 Body 以便后续可能得读取
resp.RawResponse.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 使用反射设置私有字段 body
v := reflect.ValueOf(resp).Elem()
f := v.FieldByName("body")
if f.IsValid() {
// 必须使用 UnsafeAddr 获取未导出字段的地址
rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
rf.SetBytes(bodyBytes)
}
// 设置 size 字段
s := v.FieldByName("size")
if s.IsValid() {
rs := reflect.NewAt(s.Type(), unsafe.Pointer(s.UnsafeAddr())).Elem()
rs.SetInt(int64(len(bodyBytes)))
}
}
// makeReq 构造带可选请求头的 resty.Request
// 功能:基于客户端创建请求对象,并在传入 headers 时进行设置
// 返回:带有请求头的请求对象
func (request *RestyClient) makeReq(headers map[string]string, cookies []*http.Cookie) *resty.Request {
req := request.client.R()
if len(headers) > 0 {
req = req.SetHeaders(headers)
}
if len(cookies) > 0 {
req = req.SetCookies(cookies)
}
return req
}
// doWithEncodingFallback 封装请求发送并在出现压缩相关错误时进行一次降级重试
// 逻辑:首次请求失败且错误包含 gzip/zstd/brotli/magic number mismatch 时,设置 accept-encoding 为 identity 重试一次
func (request *RestyClient) doWithEncodingFallback(headers map[string]string, cookies []*http.Cookie, allowRedirect bool, do func(*resty.Request) (*resty.Response, error)) (*resty.Response, error) {
req := request.makeReq(headers, cookies)
if allowRedirect {
request.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(10))
} else {
// 使用 http.ErrUseLastResponse 确保 302 响应被返回且 Body 可读,而不是报错
request.client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}))
}
resp, err := do(req)
// 尝试补救响应体(特别是当重定向被禁用导致报错时)
request.fillResponseBody(resp)
if err == nil {
return resp, nil
}
s := err.Error()
if strings.Contains(s, "gzip: invalid header") || strings.Contains(s, "magic number mismatch") || strings.Contains(s, "zstd") || strings.Contains(s, "brotli") {
h2 := map[string]string{}
for k, v := range headers {
if strings.ToLower(k) != "accept-encoding" {
h2[k] = v
}
}
h2["Accept-Encoding"] = "identity"
req2 := request.makeReq(h2, cookies)
resp2, err2 := do(req2)
request.fillResponseBody(resp2)
if err2 == nil {
return resp2, nil
}
}
return resp, err
}
// decodeResponse 处理响应解压与 JSON 解析
// 功能:自动识别 gzip 压缩并解压;在 result 非空时按 JSON 解析到 result
// 返回:解析错误(成功时为 nil
func (request *RestyClient) decodeResponse(resp *resty.Response, result interface{}) error {
if resp == nil {
return nil
}
ct := strings.ToLower(resp.Header().Get("Content-Type"))
ce := strings.ToLower(resp.Header().Get("Content-Encoding"))
body := resp.Body()
if strings.Contains(ce, "gzip") && len(body) > 0 {
gr, gerr := gzip.NewReader(bytes.NewReader(body))
if gerr == nil {
defer gr.Close()
if dec, derr := io.ReadAll(gr); derr == nil {
body = dec
resp.SetBody(body)
}
}
} else if strings.Contains(ce, "deflate") && len(body) > 0 {
// 处理 deflate 压缩
dr := flate.NewReader(bytes.NewReader(body))
defer dr.Close()
if dec, derr := io.ReadAll(dr); derr == nil {
body = dec
resp.SetBody(body)
}
} else if strings.Contains(ce, "br") && len(body) > 0 {
// 处理 brotli 压缩
br := brotli.NewReader(bytes.NewReader(body))
if dec, derr := io.ReadAll(br); derr == nil {
body = dec
resp.SetBody(body) // 将解压后的 body 写回 response
}
}
if result != nil && (strings.Contains(ct, "application/json") || json.Valid(body)) {
if err := json.Unmarshal(body, result); err != nil {
return err
}
}
return nil
}
// RestyGet 发送 GET 请求
func (request *RestyClient) RestyGet(path string, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Get(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyPost 发送 POST 请求
func (request *RestyClient) RestyPost(path string, data any, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.SetBody(data).Post(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyPut 发送 PUT 请求
// 功能:发送 PUT支持请求级 headers 覆盖客户端默认,自动识别 gzip 并解析 JSON
// 返回:响应对象与错误信息
func (request *RestyClient) RestyPut(path string, data any, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.SetBody(data).Put(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyPatch 发送 PATCH 请求
// 功能:发送 PATCH支持请求级 headers 覆盖客户端默认,自动识别 gzip 并解析 JSON
// 返回:响应对象与错误信息
func (request *RestyClient) RestyPatch(path string, data any, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.SetBody(data).Patch(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyDelete 发送 DELETE 请求
// 功能:发送 DELETE支持请求级 headers 覆盖客户端默认,自动识别 gzip 并解析 JSON
// 返回:响应对象与错误信息
func (request *RestyClient) RestyDelete(path string, result interface{}, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Delete(path)
})
if resp == nil && err != nil {
return nil, err
}
if err := request.decodeResponse(resp, result); err != nil {
return nil, err
}
return resp, err
}
// RestyHead 发送 HEAD 请求
// 功能:发送 HEAD支持请求级 headers 覆盖客户端默认HEAD 通常无正文
// 返回:响应对象与错误信息
func (request *RestyClient) RestyHead(path string, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Head(path)
})
if resp == nil && err != nil {
return nil, err
}
return resp, err
}
// RestyOptions 发送 OPTIONS 请求
// 功能:发送 OPTIONS支持请求级 headers 覆盖客户端默认
// 返回:响应对象与错误信息
func (request *RestyClient) RestyOptions(path string, headers map[string]string, cookies []*http.Cookie, allowRedirect bool) (*resty.Response, error) {
resp, err := request.doWithEncodingFallback(headers, cookies, allowRedirect, func(r *resty.Request) (*resty.Response, error) {
return r.Options(path)
})
if resp == nil && err != nil {
return nil, err
}
return resp, err
}