chore: 基础设施对齐与代码规范优化 (by AI)
This commit is contained in:
parent
afa035ecfb
commit
35b19b6539
@ -1,5 +1,14 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.4 (2026-05-05)
|
||||||
|
- **基础设施对齐**:
|
||||||
|
- 更新 `go/file` 至 v1.0.5, `go/log` 至 v1.1.1, `go/config` 至 v1.0.5。
|
||||||
|
- `PostMultipart` 现在通过 `go/file` API 透明支持物理文件与内存文件系统的读取。
|
||||||
|
- **鲁棒性增强**:
|
||||||
|
- 优化 `doByRequest` 中对 `X-Forwarded-For` 的处理,增强 `RemoteAddr` 解析的健壮性。
|
||||||
|
- **代码规范**:
|
||||||
|
- 统一变量命名规范,重命名冗余的单字母变量,提升代码可读性。
|
||||||
|
|
||||||
## v1.0.3 (2026-05-03)
|
## v1.0.3 (2026-05-03)
|
||||||
- **API 变更**: 将泛型解析函数 `Bind[T]` 重命名为 `To[T]`,以保持与全局 API 风格一致。
|
- **API 变更**: 将泛型解析函数 `Bind[T]` 重命名为 `To[T]`,以保持与全局 API 风格一致。
|
||||||
- **文档优化**: 移除冗余的 `AI.md`,更新 `README.md` 中的 API 示例。
|
- **文档优化**: 移除冗余的 `AI.md`,更新 `README.md` 中的 API 示例。
|
||||||
|
|||||||
2
TEST.md
2
TEST.md
@ -14,5 +14,5 @@ goos: darwin
|
|||||||
goarch: amd64
|
goarch: amd64
|
||||||
pkg: apigo.cc/go/http
|
pkg: apigo.cc/go/http
|
||||||
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
|
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
|
||||||
BenchmarkGet-16 15244 79100 ns/op
|
BenchmarkGet-16 15944 73379 ns/op
|
||||||
```
|
```
|
||||||
|
|||||||
253
client.go
253
client.go
@ -19,7 +19,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apigo.cc/go/cast"
|
"apigo.cc/go/cast"
|
||||||
"apigo.cc/go/convert"
|
|
||||||
"apigo.cc/go/encoding"
|
"apigo.cc/go/encoding"
|
||||||
"apigo.cc/go/file"
|
"apigo.cc/go/file"
|
||||||
"apigo.cc/go/log"
|
"apigo.cc/go/log"
|
||||||
@ -94,81 +93,82 @@ func NewClientH2C(timeout time.Duration) *Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetRawClient() *http.Client {
|
func (client *Client) GetRawClient() *http.Client {
|
||||||
return c.pool
|
return client.pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) EnableRedirect() {
|
func (client *Client) EnableRedirect() {
|
||||||
c.pool.CheckRedirect = nil
|
client.pool.CheckRedirect = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetGlobalHeader(k, v string) {
|
func (client *Client) SetGlobalHeader(key, value string) {
|
||||||
c.headersMu.Lock()
|
client.headersMu.Lock()
|
||||||
defer c.headersMu.Unlock()
|
defer client.headersMu.Unlock()
|
||||||
if v == "" {
|
if value == "" {
|
||||||
delete(c.globalHeaders, k)
|
delete(client.globalHeaders, key)
|
||||||
} else {
|
} else {
|
||||||
c.globalHeaders[k] = v
|
client.globalHeaders[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetGlobalHeader(k string) string {
|
func (client *Client) GetGlobalHeader(key string) string {
|
||||||
c.headersMu.RLock()
|
client.headersMu.RLock()
|
||||||
defer c.headersMu.RUnlock()
|
defer client.headersMu.RUnlock()
|
||||||
return c.globalHeaders[k]
|
return client.globalHeaders[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Destroy() {
|
func (client *Client) Destroy() {
|
||||||
if c.pool != nil {
|
if client.pool != nil {
|
||||||
c.pool.CloseIdleConnections()
|
client.pool.CloseIdleConnections()
|
||||||
c.pool = nil
|
client.pool = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Get(url string, headers ...string) *Result {
|
func (client *Client) Get(url string, headers ...string) *Result {
|
||||||
return c.Do("GET", url, nil, headers...)
|
return client.Do("GET", url, nil, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Post(url string, data any, headers ...string) *Result {
|
func (client *Client) Post(url string, data any, headers ...string) *Result {
|
||||||
return c.Do("POST", url, data, headers...)
|
return client.Do("POST", url, data, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Put(url string, data any, headers ...string) *Result {
|
func (client *Client) Put(url string, data any, headers ...string) *Result {
|
||||||
return c.Do("PUT", url, data, headers...)
|
return client.Do("PUT", url, data, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Delete(url string, data any, headers ...string) *Result {
|
func (client *Client) Delete(url string, data any, headers ...string) *Result {
|
||||||
return c.Do("DELETE", url, data, headers...)
|
return client.Do("DELETE", url, data, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Head(url string, headers ...string) *Result {
|
func (client *Client) Head(url string, headers ...string) *Result {
|
||||||
return c.Do("HEAD", url, nil, headers...)
|
return client.Do("HEAD", url, nil, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result {
|
func (client *Client) DoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result {
|
||||||
return c.doByRequest(false, request, method, url, data, settedHeaders...)
|
return client.doByRequest(false, request, method, url, data, settedHeaders...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ManualDoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result {
|
func (client *Client) ManualDoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result {
|
||||||
return c.doByRequest(true, request, method, url, data, settedHeaders...)
|
return client.doByRequest(true, request, method, url, data, settedHeaders...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doByRequest(manualDo bool, request *http.Request, method, url string, data any, settedHeaders ...string) *Result {
|
func (client *Client) doByRequest(manualDo bool, request *http.Request, method, url string, data any, settedHeaders ...string) *Result {
|
||||||
headers := make([]string, 0, len(RelayHeaders)*2+len(settedHeaders)+4)
|
headers := make([]string, 0, len(RelayHeaders)*2+len(settedHeaders)+4)
|
||||||
|
|
||||||
// 续传指定的头
|
// 续传指定的头
|
||||||
for _, h := range RelayHeaders {
|
for _, headerName := range RelayHeaders {
|
||||||
if v := request.Header.Get(h); v != "" {
|
if value := request.Header.Get(headerName); value != "" {
|
||||||
headers = append(headers, h, v)
|
headers = append(headers, headerName, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 续传 X-Forwarded-For
|
// 续传 X-Forwarded-For
|
||||||
xForwardFor := request.Header.Get(HeaderForwardedFor)
|
xForwardFor := request.Header.Get(HeaderForwardedFor)
|
||||||
remoteIP, _, _ := net.SplitHostPort(request.RemoteAddr)
|
remoteIP, _, err := net.SplitHostPort(request.RemoteAddr)
|
||||||
if remoteIP == "" {
|
if err != nil {
|
||||||
remoteIP = request.RemoteAddr
|
remoteIP = request.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
if xForwardFor != "" {
|
if xForwardFor != "" {
|
||||||
xForwardFor = remoteIP + ", " + xForwardFor
|
xForwardFor = remoteIP + ", " + xForwardFor
|
||||||
} else {
|
} else {
|
||||||
@ -191,17 +191,17 @@ func (c *Client) doByRequest(manualDo bool, request *http.Request, method, url s
|
|||||||
headers = append(headers, settedHeaders...)
|
headers = append(headers, settedHeaders...)
|
||||||
|
|
||||||
if manualDo {
|
if manualDo {
|
||||||
return c.ManualDo(method, url, data, headers...)
|
return client.ManualDo(method, url, data, headers...)
|
||||||
}
|
}
|
||||||
return c.Do(method, url, data, headers...)
|
return client.Do(method, url, data, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Do(method, url string, data any, headers ...string) *Result {
|
func (client *Client) Do(method, url string, data any, headers ...string) *Result {
|
||||||
return c.do(true, method, url, data, headers...)
|
return client.do(true, method, url, data, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ManualDo(method, url string, data any, headers ...string) *Result {
|
func (client *Client) ManualDo(method, url string, data any, headers ...string) *Result {
|
||||||
return c.do(false, method, url, data, headers...)
|
return client.do(false, method, url, data, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type downloadRange struct {
|
type downloadRange struct {
|
||||||
@ -220,29 +220,29 @@ func (w *offsetWriter) Write(p []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) downloadPart(fp *os.File, task *downloadRange, url string, headers ...string) (int64, error) {
|
func (client *Client) downloadPart(fp *os.File, task *downloadRange, url string, headers ...string) (int64, error) {
|
||||||
partHeaders := make([]string, len(headers))
|
partHeaders := make([]string, len(headers))
|
||||||
copy(partHeaders, headers)
|
copy(partHeaders, headers)
|
||||||
partHeaders[len(partHeaders)-1] = fmt.Sprintf("bytes=%d-%d", task.Start, task.End)
|
partHeaders[len(partHeaders)-1] = fmt.Sprintf("bytes=%d-%d", task.Start, task.End)
|
||||||
|
|
||||||
r := c.ManualDo("GET", url, nil, partHeaders...)
|
result := client.ManualDo("GET", url, nil, partHeaders...)
|
||||||
if r.Error != nil {
|
if result.Error != nil {
|
||||||
return 0, r.Error
|
return 0, result.Error
|
||||||
}
|
}
|
||||||
defer r.Response.Body.Close()
|
defer result.Response.Body.Close()
|
||||||
return io.Copy(&offsetWriter{fp: fp, offset: task.Start}, r.Response.Body)
|
return io.Copy(&offsetWriter{fp: fp, offset: task.Start}, result.Response.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Download(filename, url string, callback func(start, end int64, ok bool, finished, total int64), headers ...string) (*Result, error) {
|
func (client *Client) Download(filename, url string, callback func(start, end int64, ok bool, finished, total int64), headers ...string) (*Result, error) {
|
||||||
r1 := c.Head(url, headers...)
|
resultHead := client.Head(url, headers...)
|
||||||
if r1.Error != nil {
|
if resultHead.Error != nil {
|
||||||
return r1, r1.Error
|
return resultHead, resultHead.Error
|
||||||
}
|
}
|
||||||
total := r1.Response.ContentLength
|
total := resultHead.Response.ContentLength
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
tasks := make([]downloadRange, 0)
|
tasks := make([]downloadRange, 0)
|
||||||
for i := int64(0); i < total; i += c.DownloadPartSize {
|
for i := int64(0); i < total; i += client.DownloadPartSize {
|
||||||
end := i + c.DownloadPartSize - 1
|
end := i + client.DownloadPartSize - 1
|
||||||
if end >= total {
|
if end >= total {
|
||||||
end = total - 1
|
end = total - 1
|
||||||
}
|
}
|
||||||
@ -264,8 +264,8 @@ func (c *Client) Download(filename, url string, callback func(start, end int64,
|
|||||||
|
|
||||||
// 限制并发度,默认为 4,可以通过 Client 设置
|
// 限制并发度,默认为 4,可以通过 Client 设置
|
||||||
concurrency := 4
|
concurrency := 4
|
||||||
if c.MaxConnsPerHost > 0 {
|
if client.MaxConnsPerHost > 0 {
|
||||||
concurrency = c.MaxConnsPerHost
|
concurrency = client.MaxConnsPerHost
|
||||||
}
|
}
|
||||||
sem := make(chan struct{}, concurrency)
|
sem := make(chan struct{}, concurrency)
|
||||||
|
|
||||||
@ -276,10 +276,10 @@ func (c *Client) Download(filename, url string, callback func(start, end int64,
|
|||||||
sem <- struct{}{}
|
sem <- struct{}{}
|
||||||
defer func() { <-sem }()
|
defer func() { <-sem }()
|
||||||
|
|
||||||
n, err := c.downloadPart(fp, &t, url, headers...)
|
n, err := client.downloadPart(fp, &t, url, headers...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 重试一次
|
// 重试一次
|
||||||
n, err = c.downloadPart(fp, &t, url, headers...)
|
n, err = client.downloadPart(fp, &t, url, headers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@ -305,25 +305,25 @@ func (c *Client) Download(filename, url string, callback func(start, end int64,
|
|||||||
if finished < total {
|
if finished < total {
|
||||||
return nil, errors.New("download file failed: incomplete")
|
return nil, errors.New("download file failed: incomplete")
|
||||||
}
|
}
|
||||||
return r1, nil
|
return resultHead, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
r := c.ManualDo("GET", url, nil, headers...)
|
result := client.ManualDo("GET", url, nil, headers...)
|
||||||
if r.Error != nil {
|
if result.Error != nil {
|
||||||
return r, r.Error
|
return result, result.Error
|
||||||
}
|
}
|
||||||
defer r.Response.Body.Close()
|
defer result.Response.Body.Close()
|
||||||
file.EnsureParentDir(filename)
|
file.EnsureParentDir(filename)
|
||||||
fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r, err
|
return result, err
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
_, err = io.Copy(fp, r.Response.Body)
|
_, err = io.Copy(fp, result.Response.Body)
|
||||||
return r, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PostMultipart(url string, formData map[string]string, files map[string]any, headers ...string) (*Result, []error) {
|
func (client *Client) PostMultipart(url string, formData map[string]string, files map[string]any, headers ...string) (*Result, []error) {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
buf := bufferPool.Get().(*bytes.Buffer)
|
buf := bufferPool.Get().(*bytes.Buffer)
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
@ -332,47 +332,60 @@ func (c *Client) PostMultipart(url string, formData map[string]string, files map
|
|||||||
writer := multipart.NewWriter(buf)
|
writer := multipart.NewWriter(buf)
|
||||||
|
|
||||||
if formData != nil {
|
if formData != nil {
|
||||||
for k, v := range formData {
|
for key, value := range formData {
|
||||||
if err := writer.WriteField(k, v); err != nil {
|
if err := writer.WriteField(key, value); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if files != nil {
|
if files != nil {
|
||||||
for k, v := range files {
|
for key, value := range files {
|
||||||
if filename, ok := v.(string); ok && file.Exists(filename) {
|
if filename, ok := value.(string); ok && file.Exists(filename) {
|
||||||
if fp, err := os.Open(filename); err == nil {
|
var reader io.Reader
|
||||||
if part, err := writer.CreateFormFile(k, filepath.Base(filename)); err == nil {
|
var closer io.Closer
|
||||||
if _, err = io.Copy(part, fp); err != nil {
|
if mf := file.ReadFileFromMemory(filename); mf != nil {
|
||||||
errs = append(errs, err)
|
reader = bytes.NewReader(mf.GetData())
|
||||||
}
|
} else {
|
||||||
|
if fp, err := os.Open(filename); err == nil {
|
||||||
|
reader = fp
|
||||||
|
closer = fp
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if part, err := writer.CreateFormFile(key, filepath.Base(filename)); err == nil {
|
||||||
|
if _, err = io.Copy(part, reader); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
_ = fp.Close()
|
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if closer != nil {
|
||||||
|
_ = closer.Close()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
h := make(textproto.MIMEHeader)
|
h := make(textproto.MIMEHeader)
|
||||||
var dataBytes []byte
|
var dataBytes []byte
|
||||||
switch t := v.(type) {
|
switch t := value.(type) {
|
||||||
case io.Reader:
|
case io.Reader:
|
||||||
dataBytes, _ = io.ReadAll(t)
|
dataBytes, _ = io.ReadAll(t)
|
||||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, k, k))
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, key, key))
|
||||||
h.Set("Content-Type", "application/octet-stream")
|
h.Set("Content-Type", "application/octet-stream")
|
||||||
case []byte:
|
case []byte:
|
||||||
dataBytes = t
|
dataBytes = t
|
||||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, k, k))
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, key, key))
|
||||||
h.Set("Content-Type", "application/octet-stream")
|
h.Set("Content-Type", "application/octet-stream")
|
||||||
case string:
|
case string:
|
||||||
dataBytes = []byte(t)
|
dataBytes = []byte(t)
|
||||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.txt"`, k, k))
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.txt"`, key, key))
|
||||||
h.Set("Content-Type", "text/plain")
|
h.Set("Content-Type", "text/plain")
|
||||||
default:
|
default:
|
||||||
dataBytes = cast.MustJSONBytes(v)
|
dataBytes = cast.As(cast.ToJSONBytes(value))
|
||||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.json"`, k, k))
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.json"`, key, key))
|
||||||
h.Set("Content-Type", "application/json")
|
h.Set("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,14 +409,14 @@ func (c *Client) PostMultipart(url string, formData map[string]string, files map
|
|||||||
}
|
}
|
||||||
|
|
||||||
headers = append(headers, "Content-Type", writer.FormDataContentType())
|
headers = append(headers, "Content-Type", writer.FormDataContentType())
|
||||||
r := c.Post(url, buf.Bytes(), headers...)
|
result := client.Post(url, buf.Bytes(), headers...)
|
||||||
if r.Error != nil {
|
if result.Error != nil {
|
||||||
errs = append(errs, r.Error)
|
errs = append(errs, result.Error)
|
||||||
}
|
}
|
||||||
return r, errs
|
return result, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) do(fetchBody bool, method, url string, data any, headers ...string) *Result {
|
func (client *Client) do(fetchBody bool, method, url string, data any, headers ...string) *Result {
|
||||||
var req *http.Request
|
var req *http.Request
|
||||||
var err error
|
var err error
|
||||||
contentType := ""
|
contentType := ""
|
||||||
@ -470,17 +483,17 @@ func (c *Client) do(fetchBody bool, method, url string, data any, headers ...str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.headersMu.RLock()
|
client.headersMu.RLock()
|
||||||
for k, v := range c.globalHeaders {
|
for k, v := range client.globalHeaders {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
c.headersMu.RUnlock()
|
client.headersMu.RUnlock()
|
||||||
|
|
||||||
if c.Debug {
|
if client.Debug {
|
||||||
log.DefaultLogger.Info("http request", "method", req.Method, "url", req.URL.String(), "headers", req.Header)
|
log.DefaultLogger.Info("http request", "method", req.Method, "url", req.URL.String(), "headers", req.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.pool.Do(req)
|
res, err := client.pool.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Result{Error: err}
|
return &Result{Error: err}
|
||||||
}
|
}
|
||||||
@ -489,7 +502,7 @@ func (c *Client) do(fetchBody bool, method, url string, data any, headers ...str
|
|||||||
res.ContentLength = cast.Int64(res.Header.Get("Content-Length"))
|
res.ContentLength = cast.Int64(res.Header.Get("Content-Length"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fetchBody || c.NoBody {
|
if !fetchBody || client.NoBody {
|
||||||
return &Result{Response: res}
|
return &Result{Response: res}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,61 +512,61 @@ func (c *Client) do(fetchBody bool, method, url string, data any, headers ...str
|
|||||||
return &Result{Error: err, Response: res}
|
return &Result{Error: err, Response: res}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Debug {
|
if client.Debug {
|
||||||
log.DefaultLogger.Info("http response", "status", res.StatusCode, "len", len(bodyBytes))
|
log.DefaultLogger.Info("http response", "status", res.StatusCode, "len", len(bodyBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Result{data: bodyBytes, Response: res}
|
return &Result{data: bodyBytes, Response: res}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) Save(filename string) error {
|
func (result *Result) Save(filename string) error {
|
||||||
file.EnsureParentDir(filename)
|
file.EnsureParentDir(filename)
|
||||||
if rs.data != nil {
|
if result.data != nil {
|
||||||
return file.WriteBytes(filename, rs.data)
|
return file.WriteBytes(filename, result.data)
|
||||||
}
|
}
|
||||||
if rs.Response != nil && rs.Response.Body != nil {
|
if result.Response != nil && result.Response.Body != nil {
|
||||||
defer rs.Response.Body.Close()
|
defer result.Response.Body.Close()
|
||||||
fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
_, err = io.Copy(fp, rs.Response.Body)
|
_, err = io.Copy(fp, result.Response.Body)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return errors.New("no data to save")
|
return errors.New("no data to save")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) String() string {
|
func (result *Result) String() string {
|
||||||
return string(rs.data)
|
return string(result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) Bytes() []byte {
|
func (result *Result) Bytes() []byte {
|
||||||
return rs.data
|
return result.data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) Map() map[string]any {
|
func (result *Result) Map() map[string]any {
|
||||||
var m map[string]any
|
var m map[string]any
|
||||||
_ = rs.To(&m)
|
_ = result.To(&m)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) Slice() []any {
|
func (result *Result) Slice() []any {
|
||||||
var a []any
|
var a []any
|
||||||
_ = rs.To(&a)
|
_ = result.To(&a)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Result) To(v any) error {
|
func (result *Result) To(v any) error {
|
||||||
if rs.data == nil {
|
if result.data == nil {
|
||||||
return errors.New("no data")
|
return errors.New("no data")
|
||||||
}
|
}
|
||||||
_, err := cast.UnmarshalJSONBytes(rs.data, v)
|
err := cast.UnmarshalJSON(result.data, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果 cast 直接解不出来,尝试通过 convert 做深度映射(处理 struct 字段匹配等)
|
// 如果 cast 直接解不出来,尝试通过 convert 做深度映射(处理 struct 字段匹配等)
|
||||||
var tmp any
|
var tmp any
|
||||||
if _, err2 := cast.UnmarshalJSONBytes(rs.data, &tmp); err2 == nil {
|
if err2 := cast.UnmarshalJSON(result.data, &tmp); err2 == nil {
|
||||||
convert.To(tmp, v)
|
cast.Convert(v, tmp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -561,8 +574,8 @@ func (rs *Result) To(v any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// To 使用泛型获取结果
|
// To 使用泛型获取结果
|
||||||
func To[T any](rs *Result) (T, error) {
|
func To[T any](result *Result) (T, error) {
|
||||||
var v T
|
var v T
|
||||||
err := rs.To(&v)
|
err := result.To(&v)
|
||||||
return v, err
|
return v, err
|
||||||
}
|
}
|
||||||
|
|||||||
14
go.mod
14
go.mod
@ -3,21 +3,25 @@ module apigo.cc/go/http
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/cast v1.1.1
|
apigo.cc/go/cast v1.2.6
|
||||||
apigo.cc/go/convert v1.0.4
|
|
||||||
apigo.cc/go/encoding v1.0.4
|
apigo.cc/go/encoding v1.0.4
|
||||||
apigo.cc/go/file v1.0.4
|
apigo.cc/go/file v1.0.5
|
||||||
apigo.cc/go/log v1.0.0
|
apigo.cc/go/log v1.1.1
|
||||||
apigo.cc/go/rand v1.0.4
|
apigo.cc/go/rand v1.0.4
|
||||||
golang.org/x/net v0.53.0
|
golang.org/x/net v0.53.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require apigo.cc/go/convert v1.0.4 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/config v1.0.4 // indirect
|
apigo.cc/go/config v1.0.5 // indirect
|
||||||
apigo.cc/go/safe v1.0.4 // indirect
|
apigo.cc/go/safe v1.0.4 // indirect
|
||||||
apigo.cc/go/shell v1.0.4 // indirect
|
apigo.cc/go/shell v1.0.4 // indirect
|
||||||
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
golang.org/x/crypto v0.50.0 // indirect
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/text v0.36.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
25
go.sum
25
go.sum
@ -1,5 +1,5 @@
|
|||||||
apigo.cc/go/cast v1.1.1 h1:+5pluN8g1RK2J4byr2xkfOmEdKSmy1PByOqDOHtt/Ns=
|
apigo.cc/go/cast v1.2.6 h1:xnWiaQAGsRCrnu1p8fIFQfg5HFSc7CxR+3ItiDIDMaY=
|
||||||
apigo.cc/go/cast v1.1.1/go.mod h1:vh9ZqISCmTUiyinkNMI/s4f045fRlDK3xC+nPWQYBzI=
|
apigo.cc/go/cast v1.2.6/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
|
||||||
apigo.cc/go/config v1.0.4 h1:WG9zrQkqfFPkrKIL7RNvvAbbkuUBt1Av11ZP/aIfldM=
|
apigo.cc/go/config v1.0.4 h1:WG9zrQkqfFPkrKIL7RNvvAbbkuUBt1Av11ZP/aIfldM=
|
||||||
apigo.cc/go/config v1.0.4/go.mod h1:obryzJiK6j7lQex/58d5eWYOGx5O5IABguqNWxyyXJo=
|
apigo.cc/go/config v1.0.4/go.mod h1:obryzJiK6j7lQex/58d5eWYOGx5O5IABguqNWxyyXJo=
|
||||||
apigo.cc/go/convert v1.0.4 h1:5+qPjC3dlPB59GnWZRlmthxcaXQtKvN+iOuiLdJ1GvQ=
|
apigo.cc/go/convert v1.0.4 h1:5+qPjC3dlPB59GnWZRlmthxcaXQtKvN+iOuiLdJ1GvQ=
|
||||||
@ -8,14 +8,26 @@ apigo.cc/go/encoding v1.0.4 h1:aezB0J/qFuHs6iXkbtuJP5JIHUtmjsr5SFb0NNvbObY=
|
|||||||
apigo.cc/go/encoding v1.0.4/go.mod h1:V5CgT7rBbCxy+uCU20q0ptcNNRSgMtpA8cNOs6r8IeI=
|
apigo.cc/go/encoding v1.0.4/go.mod h1:V5CgT7rBbCxy+uCU20q0ptcNNRSgMtpA8cNOs6r8IeI=
|
||||||
apigo.cc/go/file v1.0.4 h1:qCKegV7OYh7r0qc3jZjGA/aKh0vIHgmr1OEbhfEmGX8=
|
apigo.cc/go/file v1.0.4 h1:qCKegV7OYh7r0qc3jZjGA/aKh0vIHgmr1OEbhfEmGX8=
|
||||||
apigo.cc/go/file v1.0.4/go.mod h1:C9gNo7386iA21OiBmuWh6CznKWlVBDFkhE4f0H0Susg=
|
apigo.cc/go/file v1.0.4/go.mod h1:C9gNo7386iA21OiBmuWh6CznKWlVBDFkhE4f0H0Susg=
|
||||||
apigo.cc/go/log v1.0.0 h1:lI1NGTSS+Jm12G8BD7ZJO4/hrkfuLTu5O8z36GD8GpU=
|
apigo.cc/go/log v1.0.2 h1:OY6T3SC28blDNkMpdRvDK2N4sGdriAB9DBItGl/qOos=
|
||||||
apigo.cc/go/log v1.0.0/go.mod h1:tvPgFpebY9Wf/DlqMHZ0ZjxDp9AaQTywOQKvtBaNqNo=
|
apigo.cc/go/log v1.0.2/go.mod h1:tvPgFpebY9Wf/DlqMHZ0ZjxDp9AaQTywOQKvtBaNqNo=
|
||||||
apigo.cc/go/rand v1.0.4 h1:we070eWSL0dB8NEMaWjXj43+EekXQTm/h0kKpZ/frqw=
|
apigo.cc/go/rand v1.0.4 h1:we070eWSL0dB8NEMaWjXj43+EekXQTm/h0kKpZ/frqw=
|
||||||
apigo.cc/go/rand v1.0.4/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
apigo.cc/go/rand v1.0.4/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
||||||
apigo.cc/go/safe v1.0.4 h1:07pRSdEHprF/2v6SsqAjICYFoeLcqjjvHGEdh6Dzrzg=
|
apigo.cc/go/safe v1.0.4 h1:07pRSdEHprF/2v6SsqAjICYFoeLcqjjvHGEdh6Dzrzg=
|
||||||
apigo.cc/go/safe v1.0.4/go.mod h1:o568sHS5rTRSVPmhxWod0tGdc+8l1KjidsNY1/OVZr0=
|
apigo.cc/go/safe v1.0.4/go.mod h1:o568sHS5rTRSVPmhxWod0tGdc+8l1KjidsNY1/OVZr0=
|
||||||
apigo.cc/go/shell v1.0.4 h1:EL9zjI39YBe1h+kRYQeAi/8zVGHe5W198DYYN7cENiY=
|
apigo.cc/go/shell v1.0.4 h1:EL9zjI39YBe1h+kRYQeAi/8zVGHe5W198DYYN7cENiY=
|
||||||
apigo.cc/go/shell v1.0.4/go.mod h1:N2gDkgK4tJ9TadD60/+gAGuWxyVAWHs5YPBmytw6ELA=
|
apigo.cc/go/shell v1.0.4/go.mod h1:N2gDkgK4tJ9TadD60/+gAGuWxyVAWHs5YPBmytw6ELA=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
@ -24,7 +36,10 @@ golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
|||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user