Files
gf/net/gclient/gclient_request.go
hailaz ee24da4e72 refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395)
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.

**Type alias migration to `any`:**

* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)

**Generated code and templates:**

* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)

**Container and utility updates:**

* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)

These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00

389 lines
12 KiB
Go

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gclient
import (
"bytes"
"context"
"io"
"mime/multipart"
"net/http"
"os"
"strings"
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/httputil"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// Get send GET request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Get(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodGet, url, data...)
}
// Put send PUT request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Put(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodPut, url, data...)
}
// Post sends request using HTTP method POST and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Post(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodPost, url, data...)
}
// Delete send DELETE request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Delete(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodDelete, url, data...)
}
// Head send HEAD request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Head(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodHead, url, data...)
}
// Patch send PATCH request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Patch(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodPatch, url, data...)
}
// Connect send CONNECT request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Connect(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodConnect, url, data...)
}
// Options send OPTIONS request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Options(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodOptions, url, data...)
}
// Trace send TRACE request and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) Trace(ctx context.Context, url string, data ...any) (*Response, error) {
return c.DoRequest(ctx, http.MethodTrace, url, data...)
}
// PostForm is different from net/http.PostForm.
// It's a wrapper of Post method, which sets the Content-Type as "multipart/form-data;".
// and It will automatically set boundary characters for the request body and Content-Type.
//
// It's Seem like the following case:
//
// Content-Type: multipart/form-data; boundary=----Boundarye4Ghaog6giyQ9ncN
//
// And form data is like:
// ------Boundarye4Ghaog6giyQ9ncN
// Content-Disposition: form-data; name="checkType"
//
// none
//
// It's used for sending form data.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) PostForm(ctx context.Context, url string, data map[string]string) (resp *Response, err error) {
body := new(bytes.Buffer)
w := multipart.NewWriter(body)
for k, v := range data {
err := w.WriteField(k, v)
if err != nil {
return nil, err
}
}
err = w.Close()
if err != nil {
return nil, err
}
return c.ContentType(w.FormDataContentType()).Post(ctx, url, body)
}
// DoRequest sends request with given HTTP method and data and returns the response object.
// Note that the response object MUST be closed if it'll never be used.
//
// Note that it uses "multipart/form-data" as its Content-Type if it contains file uploading,
// else it uses "application/x-www-form-urlencoded". It also automatically detects the post
// content for JSON format, and for that it automatically sets the Content-Type as
// "application/json".
func (c *Client) DoRequest(
ctx context.Context, method, url string, data ...any,
) (resp *Response, err error) {
var requestStartTime = gtime.Now()
req, err := c.prepareRequest(ctx, method, url, data...)
if err != nil {
return nil, err
}
// Metrics.
c.handleMetricsBeforeRequest(req)
defer c.handleMetricsAfterRequestDone(req, requestStartTime)
// Client middleware.
if len(c.middlewareHandler) > 0 {
mdlHandlers := make([]HandlerFunc, 0, len(c.middlewareHandler)+1)
mdlHandlers = append(mdlHandlers, c.middlewareHandler...)
mdlHandlers = append(mdlHandlers, func(cli *Client, r *http.Request) (*Response, error) {
return cli.callRequest(r)
})
ctx = context.WithValue(req.Context(), clientMiddlewareKey, &clientMiddleware{
client: c,
handlers: mdlHandlers,
handlerIndex: -1,
})
req = req.WithContext(ctx)
resp, err = c.Next(req)
} else {
resp, err = c.callRequest(req)
}
if resp != nil && resp.Response != nil {
req.Response = resp.Response
}
return resp, err
}
// prepareRequest verifies request parameters, builds and returns http request.
func (c *Client) prepareRequest(ctx context.Context, method, url string, data ...any) (req *http.Request, err error) {
method = strings.ToUpper(method)
if len(c.prefix) > 0 {
url = c.prefix + gstr.Trim(url)
}
if !gstr.ContainsI(url, httpProtocolName) {
url = httpProtocolName + `://` + url
}
var (
params string
allowFileUploading = true
)
if len(data) > 0 {
switch c.header[httpHeaderContentType] {
case httpHeaderContentTypeJson:
switch data[0].(type) {
case string, []byte:
params = gconv.String(data[0])
default:
if b, err := json.Marshal(data[0]); err != nil {
return nil, err
} else {
params = string(b)
}
}
allowFileUploading = false
case httpHeaderContentTypeXml:
switch data[0].(type) {
case string, []byte:
params = gconv.String(data[0])
default:
if b, err := gjson.New(data[0]).ToXml(); err != nil {
return nil, err
} else {
params = string(b)
}
}
allowFileUploading = false
default:
params = httputil.BuildParams(data[0], c.noUrlEncode)
}
}
if method == http.MethodGet {
var bodyBuffer *bytes.Buffer
if params != "" {
switch c.header[httpHeaderContentType] {
case
httpHeaderContentTypeJson,
httpHeaderContentTypeXml:
bodyBuffer = bytes.NewBuffer([]byte(params))
default:
// It appends the parameters to the url
// if http method is GET and Content-Type is not specified.
if gstr.Contains(url, "?") {
url = url + "&" + params
} else {
url = url + "?" + params
}
bodyBuffer = bytes.NewBuffer(nil)
}
} else {
bodyBuffer = bytes.NewBuffer(nil)
}
if req, err = http.NewRequest(method, url, bodyBuffer); err != nil {
err = gerror.Wrapf(err, `http.NewRequest failed with method "%s" and URL "%s"`, method, url)
return nil, err
}
} else {
if allowFileUploading && strings.Contains(params, httpParamFileHolder) {
// File uploading request.
var (
buffer = bytes.NewBuffer(nil)
writer = multipart.NewWriter(buffer)
isFileUploading = false
)
for _, item := range strings.Split(params, "&") {
array := strings.Split(item, "=")
if len(array) < 2 {
continue
}
if len(array[1]) > 6 && strings.Compare(array[1][0:6], httpParamFileHolder) == 0 {
path := array[1][6:]
if !gfile.Exists(path) {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path)
}
var (
file io.Writer
formFileName = gfile.Basename(path)
formFieldName = array[0]
)
// it sets post content type as `application/octet-stream`
if file, err = writer.CreateFormFile(formFieldName, formFileName); err != nil {
return nil, gerror.Wrapf(
err, `CreateFormFile failed with "%s", "%s"`, formFieldName, formFileName,
)
}
var f *os.File
if f, err = gfile.Open(path); err != nil {
return nil, err
}
if _, err = io.Copy(file, f); err != nil {
_ = f.Close()
return nil, gerror.Wrapf(
err, `io.Copy failed from "%s" to form "%s"`, path, formFieldName,
)
}
if err = f.Close(); err != nil {
return nil, gerror.Wrapf(err, `close file descriptor failed for "%s"`, path)
}
isFileUploading = true
} else {
var (
fieldName = array[0]
fieldValue = array[1]
)
if err = writer.WriteField(fieldName, fieldValue); err != nil {
return nil, gerror.Wrapf(
err, `write form field failed with "%s", "%s"`, fieldName, fieldValue,
)
}
}
}
// Close finishes the multipart message and writes the trailing
// boundary end line to the output.
if err = writer.Close(); err != nil {
return nil, gerror.Wrapf(err, `form writer close failed`)
}
if req, err = http.NewRequest(method, url, buffer); err != nil {
return nil, gerror.Wrapf(
err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url,
)
}
if isFileUploading {
req.Header.Set(httpHeaderContentType, writer.FormDataContentType())
}
} else {
// Normal request.
paramBytes := []byte(params)
if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url)
return nil, err
}
if v, ok := c.header[httpHeaderContentType]; ok {
// Custom Content-Type.
req.Header.Set(httpHeaderContentType, v)
} else if len(paramBytes) > 0 {
if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
// Auto-detecting and setting the post content format: JSON.
req.Header.Set(httpHeaderContentType, httpHeaderContentTypeJson)
} else if gregex.IsMatchString(httpRegexParamJson, params) {
// If the parameters passed like "name=value", it then uses form type.
req.Header.Set(httpHeaderContentType, httpHeaderContentTypeForm)
}
}
}
}
// Context.
if ctx != nil {
req = req.WithContext(ctx)
}
// Custom header.
if len(c.header) > 0 {
for k, v := range c.header {
req.Header.Set(k, v)
}
}
// It's necessary set the req.Host if you want to custom the host value of the request.
// It uses the "Host" value from header if it's not empty.
if reqHeaderHost := req.Header.Get(httpHeaderHost); reqHeaderHost != "" {
req.Host = reqHeaderHost
}
// Custom Cookie.
if len(c.cookies) > 0 {
headerCookie := ""
for k, v := range c.cookies {
if len(headerCookie) > 0 {
headerCookie += ";"
}
headerCookie += k + "=" + v
}
if len(headerCookie) > 0 {
req.Header.Set(httpHeaderCookie, headerCookie)
}
}
// HTTP basic authentication.
if len(c.authUser) > 0 {
req.SetBasicAuth(c.authUser, c.authPass)
}
return req, nil
}
// callRequest sends request with give http.Request, and returns the responses object.
// Note that the response object MUST be closed if it'll never be used.
func (c *Client) callRequest(req *http.Request) (resp *Response, err error) {
resp = &Response{
request: req,
}
// Dump feature.
// The request body can be reused for dumping
// raw HTTP request-response procedure.
reqBodyContent, _ := io.ReadAll(req.Body)
resp.requestBody = reqBodyContent
for {
req.Body = utils.NewReadCloser(reqBodyContent, false)
if resp.Response, err = c.Do(req); err != nil {
err = gerror.Wrapf(err, `request failed`)
// The response might not be nil when err != nil.
if resp.Response != nil {
_ = resp.Body.Close()
}
if c.retryCount > 0 {
c.retryCount--
time.Sleep(c.retryInterval)
} else {
// return resp, err
break
}
} else {
break
}
}
return resp, err
}