Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
}

// 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"
"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
}

// 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
}
// HTTP basic authentication.
if len(c.authUser) > 0 {
req.SetBasicAuth(c.authUser, c.authPass)
Expand Down
Loading
Loading