Skip to content

Commit

Permalink
from branch v2: feat: option to enable URL query params without encod…
Browse files Browse the repository at this point in the history
…ing (#885)
  • Loading branch information
jeevatkm committed Nov 2, 2024
1 parent 3ec8619 commit 8dabe08
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 0 deletions.
15 changes: 15 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ type Client struct {
requestLog RequestLogCallback
responseLog ResponseLogCallback
generateCurlOnDebug bool
unescapeQueryParams bool
loadBalancer LoadBalancer
beforeRequest []RequestMiddleware
udBeforeRequest []RequestMiddleware
Expand Down Expand Up @@ -654,6 +655,7 @@ func (c *Client) R() *Request {
log: c.log,
setContentLength: c.setContentLength,
generateCurlOnDebug: c.generateCurlOnDebug,
unescapeQueryParams: c.unescapeQueryParams,
}

if c.ctx != nil {
Expand Down Expand Up @@ -1861,6 +1863,19 @@ func (c *Client) SetGenerateCurlOnDebug(b bool) *Client {
return c
}

// SetUnescapeQueryParams method sets the choice of unescape query parameters for the request URL.
// To prevent broken URL, Resty replaces space (" ") with "+" in the query parameters.
//
// See [Request.SetUnescapeQueryParams]
//
// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
func (c *Client) SetUnescapeQueryParams(unescape bool) *Client {
c.lock.Lock()
defer c.lock.Unlock()
c.unescapeQueryParams = unescape
return c
}

// ResponseBodyUnlimitedReads method returns true if enabled. Otherwise, it returns false
func (c *Client) ResponseBodyUnlimitedReads() bool {
c.lock.RLock()
Expand Down
9 changes: 9 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ func parseRequestURL(c *Client, r *Request) error {
}
}

// GH#797 Unescape query parameters (non-standard - not recommended)
if r.unescapeQueryParams && len(reqURL.RawQuery) > 0 {
// at this point, all errors caught up in the above operations
// so ignore the return error on query unescape; I realized
// while writing the unit test
unescapedQuery, _ := url.QueryUnescape(reqURL.RawQuery)
reqURL.RawQuery = strings.ReplaceAll(unescapedQuery, " ", "+") // otherwise request becomes bad request
}

r.URL = reqURL.String()

return nil
Expand Down
38 changes: 38 additions & 0 deletions middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,24 @@ func Test_parseRequestURL(t *testing.T) {
},
expectedURL: "https://example.com/?foo=1&foo=2",
},
{
name: "unescape query params",
initClient: func(c *Client) {
c.SetBaseURL("https://example.com/").
SetUnescapeQueryParams(true). // this line is just code coverage; I will restructure this test in v3 for the client and request the respective init method
SetQueryParam("fromclient", "hey unescape").
SetQueryParam("initone", "cáfe")
},
initRequest: func(r *Request) {
r.SetUnescapeQueryParams(true) // this line takes effect
r.SetQueryParams(
map[string]string{
"registry": "nacos://test:6801", // GH #797
},
)
},
expectedURL: "https://example.com?initone=cáfe&fromclient=hey+unescape&registry=nacos://test:6801",
},
} {
t.Run(tt.name, func(t *testing.T) {
c := New()
Expand Down Expand Up @@ -1076,6 +1094,26 @@ func TestSaveResponseToFile(t *testing.T) {
assertEqual(t, errFileMsg, err2.Error())
}

func TestRequestURL_GH797(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
c := dcnl().
SetBaseURL(ts.URL).
SetUnescapeQueryParams(true). // this line is just code coverage; I will restructure this test in v3 for the client and request the respective init method
SetQueryParam("fromclient", "hey unescape").
SetQueryParam("initone", "cáfe")
resp, err := c.R().
SetUnescapeQueryParams(true). // this line takes effect
SetQueryParams(
map[string]string{
"registry": "nacos://test:6801", // GH #797
},
).
Get("/unescape-query-params")
assertError(t, err)
assertEqual(t, "query params looks good", resp.String())
}

func TestMiddlewareCoverage(t *testing.T) {
c := dcnl()

Expand Down
12 changes: 12 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type Request struct {
retryConditions []RetryConditionFunc
resultCurlCmd *string
generateCurlOnDebug bool
unescapeQueryParams bool
multipartErrChan chan error
}

Expand Down Expand Up @@ -1072,6 +1073,17 @@ func (r *Request) SetGenerateCurlOnDebug(b bool) *Request {
return r
}

// SetUnescapeQueryParams method sets the choice of unescape query parameters for the request URL.
// To prevent broken URL, Resty replaces space (" ") with "+" in the query parameters.
//
// This method overrides the value set by [Client.SetUnescapeQueryParams]
//
// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
func (r *Request) SetUnescapeQueryParams(unescape bool) *Request {
r.unescapeQueryParams = unescape
return r
}

// SetAllowMethodGetPayload method allows the GET method with payload on the request level.
// By default, Resty does not allow.
//
Expand Down
8 changes: 8 additions & 0 deletions resty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ func createGetServer(t *testing.T) *httptest.Server {
_, _ = w.Write([]byte(`{ "message": "hello" }`))
}
atomic.AddInt32(&attempt, 1)
case "/unescape-query-params":
initOne := r.URL.Query().Get("initone")
fromClient := r.URL.Query().Get("fromclient")
registry := r.URL.Query().Get("registry")
assertEqual(t, "cáfe", initOne)
assertEqual(t, "hey unescape", fromClient)
assertEqual(t, "nacos://test:6801", registry)
_, _ = w.Write([]byte(`query params looks good`))
}

switch {
Expand Down

0 comments on commit 8dabe08

Please sign in to comment.