Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
00f3563
feat(gclient): 添加查询参数功能支持
LanceAdd Jan 20, 2026
fd5ff60
Merge branch 'gogf:master' into feature/gclient-params
LanceAdd Jan 21, 2026
a57af4b
refactor(gclient): 优化请求查询参数处理逻辑
LanceAdd Jan 22, 2026
6a0a221
refactor(net/gclient): 重构HTTP客户端请求构建逻辑
LanceAdd Jan 22, 2026
e5ce61c
fix(gclient): 修复查询参数处理逻辑
LanceAdd Jan 22, 2026
8bc6076
fix(gclient): 解决查询参数设置中的空值过滤问题
LanceAdd Jan 22, 2026
99ebd0b
fix(gclient): 移除URL参数中为空的值切片,防止被编码为"key="格式 ,处理"?age"或"?age="这样的边缘情况,…
LanceAdd Jan 22, 2026
b49e724
feat(gclient): 添加GetMergedURL方法并完善查询参数功能
LanceAdd Jan 22, 2026
6409ee9
Apply gci import order changes
github-actions[bot] Jan 22, 2026
ea4670f
docs(test): 添加查询参数测试服务器创建函数注释
LanceAdd Jan 22, 2026
bc07e2e
Merge branch 'master' into feature/gclient-params
LanceAdd Jan 22, 2026
f8e2e8e
refactor(net/gclient): 重构查询参数测试服务器创建函数,新增文件上传与查询参数组合功能测试
LanceAdd Jan 22, 2026
7401ec5
Merge branch 'master' into feature/gclient-params
LanceAdd Jan 30, 2026
360dbf3
refactor(net/gclient): 重构HTTP请求参数处理逻辑
LanceAdd Jan 30, 2026
02639cc
feat(gclient): 支持请求对象参数分类功能
LanceAdd Jan 30, 2026
38a5c45
refactor(net/gclient): 重构请求参数分类逻辑
LanceAdd Jan 30, 2026
231722d
refactor(net/gclient): 重构测试代码中的请求体参数解析方式
LanceAdd Feb 2, 2026
09402ed
feat(gclient): 增强请求对象功能支持MIME类型设置和参数分类优化
LanceAdd Feb 3, 2026
8da95ba
refactor(gclient): 移除请求对象标签测试中的冗余测试用例
LanceAdd Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions net/gclient/gclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Client struct {
authPass string // HTTP basic authentication: pass.
retryCount int // Retry count when request fails.
noUrlEncode bool // No url encoding for request parameters.
queryParams map[string]any // Query parameters map.
retryInterval time.Duration // Retry interval when request fails.
middlewareHandler []HandlerFunc // Interceptor handlers
discovery gsvc.Discovery // Discovery for service.
Expand Down Expand Up @@ -83,10 +84,11 @@ func New() *Client {
}).DialContext,
},
},
header: make(map[string]string),
cookies: make(map[string]string),
builder: gsel.GetBuilder(),
discovery: nil,
header: make(map[string]string),
cookies: make(map[string]string),
queryParams: make(map[string]any),
builder: gsel.GetBuilder(),
discovery: nil,
}
c.header[httpHeaderUserAgent] = defaultClientAgent
// It enables OpenTelemetry for client in default.
Expand All @@ -106,6 +108,10 @@ func (c *Client) Clone() *Client {
for k, v := range c.cookies {
newClient.cookies[k] = v
}
newClient.queryParams = make(map[string]any, len(c.queryParams))
for k, v := range c.queryParams {
newClient.queryParams[k] = v
}
return newClient
}

Expand Down
22 changes: 22 additions & 0 deletions net/gclient/gclient_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,25 @@ func (c *Client) NoUrlEncode() *Client {
newClient.SetNoUrlEncode(true)
return newClient
}

// Query is a chaining function, which sets query parameters with map for next request.
func (c *Client) Query(m map[string]any) *Client {
newClient := c.Clone()
newClient.SetQueryMap(m)
return newClient
}

// QueryParams is a chaining function, which sets query parameters with struct or map object for next request.
// The `params` can be type of: string/[]byte/map/struct/*struct.
func (c *Client) QueryParams(params any) *Client {
newClient := c.Clone()
newClient.SetQueryParams(params)
return newClient
}

// QueryPair is a chaining function, which sets a query parameter pair for next request.
func (c *Client) QueryPair(key string, value any) *Client {
newClient := c.Clone()
newClient.SetQuery(key, value)
return newClient
}
37 changes: 37 additions & 0 deletions net/gclient/gclient_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
"golang.org/x/net/proxy"

"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/net/gsel"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)

// SetBrowserMode enables browser mode of the client.
Expand Down Expand Up @@ -216,3 +218,38 @@ func (c *Client) SetBuilder(builder gsel.Builder) {
func (c *Client) SetDiscovery(discovery gsvc.Discovery) {
c.discovery = discovery
}

// SetQuery sets a query parameter pair for the client.
func (c *Client) SetQuery(key string, value any) *Client {
if c.queryParams == nil {
c.queryParams = make(map[string]any)
}
c.queryParams[key] = value
return c
}

// SetQueryMap sets query parameters with map.
func (c *Client) SetQueryMap(m map[string]any) *Client {
if c.queryParams == nil {
c.queryParams = make(map[string]any)
}
for k, v := range m {
c.queryParams[k] = v
}
return c
}
Comment thread
LanceAdd marked this conversation as resolved.

// SetQueryParams sets query parameters with struct or map object.
// The `params` can be type of: string/[]byte/map/struct/*struct.
func (c *Client) SetQueryParams(params any) *Client {
if c.queryParams == nil {
c.queryParams = make(map[string]any)
}
m := gconv.Map(params)
for k, v := range m {
if !empty.IsNil(v) {
c.queryParams[k] = v
}
}
return c
}
78 changes: 65 additions & 13 deletions net/gclient/gclient_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
"mime"
"mime/multipart"
"net/http"
url2 "net/url"
Comment thread
LanceAdd marked this conversation as resolved.
Outdated
"os"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -161,13 +163,13 @@ func (c *Client) DoRequest(
}

// 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) {
func (c *Client) prepareRequest(ctx context.Context, method, u string, data ...any) (req *http.Request, err error) {
method = strings.ToUpper(method)
if len(c.prefix) > 0 {
url = c.prefix + gstr.Trim(url)
u = c.prefix + gstr.Trim(u)
}
if !gstr.ContainsI(url, httpProtocolName) {
url = httpProtocolName + `://` + url
if !gstr.ContainsI(u, httpProtocolName) {
u = httpProtocolName + `://` + u
}
var (
params string
Expand Down Expand Up @@ -226,18 +228,18 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
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
if gstr.Contains(u, "?") {
u = u + "&" + params
} else {
url = url + "?" + params
u = u + "?" + 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)
if req, err = http.NewRequest(method, u, bodyBuffer); err != nil {
err = gerror.Wrapf(err, `http.NewRequest failed with method "%s" and URL "%s"`, method, u)
return nil, err
}
} else {
Expand Down Expand Up @@ -309,9 +311,9 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
return nil, gerror.Wrapf(err, `form writer close failed`)
}

if req, err = http.NewRequest(method, url, buffer); err != nil {
if req, err = http.NewRequest(method, u, buffer); err != nil {
return nil, gerror.Wrapf(
err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url,
err, `http.NewRequest failed for method "%s" and URL "%s"`, method, u,
)
}
if isFileUploading {
Expand All @@ -320,8 +322,8 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
} 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)
if req, err = http.NewRequest(method, u, bytes.NewReader(paramBytes)); err != nil {
err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, u)
return nil, err
}
if v, ok := c.header[httpHeaderContentType]; ok {
Expand Down Expand Up @@ -367,6 +369,56 @@ func (c *Client) prepareRequest(ctx context.Context, method, url string, data ..
req.Header.Set(httpHeaderCookie, headerCookie)
}
}
// Handle query parameters for all request methods
if len(c.queryParams) > 0 {
// Parse the URL to get existing query parameters
urlObj, err := url2.Parse(req.URL.String())
if err != nil {
return nil, gerror.Wrapf(err, `url2.Parse failed for URL "%s"`, req.URL.String())
}
// Add query parameters from c.queryParams
queryValues := urlObj.Query()
for k, v := range c.queryParams {
// Skip explicit nil values
if v == nil {
continue
}

// Use reflection to handle slice/array types generically
reflectValue := reflect.Indirect(reflect.ValueOf(v)) // Dereference pointers

// Check if the reflect value is valid (covers dereferenced nil pointers)
if !reflectValue.IsValid() {
continue
}
Comment thread
LanceAdd marked this conversation as resolved.
Outdated

// Check if it's a slice or array
if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array {
// Skip nil slices
if reflectValue.Kind() == reflect.Slice && reflectValue.IsNil() {
continue
}

if reflectValue.Len() > 0 { // Only process non-empty slices/arrays
for i := 0; i < reflectValue.Len(); i++ {
item := reflectValue.Index(i).Interface()
if i == 0 {
queryValues.Set(k, gconv.String(item))
} else {
queryValues.Add(k, gconv.String(item))
}
}
} else {
// Skip empty slices/arrays instead of adding empty value
continue
}
} else {
queryValues.Set(k, gconv.String(v))
}
}
urlObj.RawQuery = queryValues.Encode()
req.URL = urlObj
}
Comment thread
LanceAdd marked this conversation as resolved.
Comment thread
LanceAdd marked this conversation as resolved.
Outdated
Comment thread
LanceAdd marked this conversation as resolved.
Outdated
// HTTP basic authentication.
if len(c.authUser) > 0 {
req.SetBasicAuth(c.authUser, c.authPass)
Expand Down
Loading
Loading