Skip to content

Commit

Permalink
feat(enhancement)!: seperate generate curl cmd from debug flow #928
Browse files Browse the repository at this point in the history
  • Loading branch information
jeevatkm committed Jan 1, 2025
1 parent cf25584 commit 9b82799
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 88 deletions.
61 changes: 42 additions & 19 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ type Client struct {
proxyURL *url.URL
requestDebugLog DebugLogCallback
responseDebugLog DebugLogCallback
generateCurlOnDebug bool
generateCurlCmd bool
debugLogCurlCmd bool
unescapeQueryParams bool
loadBalancer LoadBalancer
beforeRequest []RequestMiddleware
Expand Down Expand Up @@ -641,7 +642,8 @@ func (c *Client) R() *Request {
jsonEscapeHTML: c.jsonEscapeHTML,
log: c.log,
setContentLength: c.setContentLength,
generateCurlOnDebug: c.generateCurlOnDebug,
generateCurlCmd: c.generateCurlCmd,
debugLogCurlCmd: c.debugLogCurlCmd,
unescapeQueryParams: c.unescapeQueryParams,
credentials: c.credentials,
}
Expand Down Expand Up @@ -731,7 +733,7 @@ func (c *Client) requestMiddlewares() []RequestMiddleware {
func (c *Client) AddRequestMiddleware(m RequestMiddleware) *Client {
c.lock.Lock()
defer c.lock.Unlock()
idx := len(c.beforeRequest) - 2
idx := len(c.beforeRequest) - 1
c.beforeRequest = slices.Insert(c.beforeRequest, idx, m)
return c
}
Expand Down Expand Up @@ -1965,35 +1967,56 @@ func (c *Client) SetTrace(t bool) *Client {
return c
}

// EnableGenerateCurlOnDebug method enables the generation of CURL commands in the debug log.
// It works in conjunction with debug mode.
// EnableGenerateCurlCmd method enables the generation of curl command at the
// client instance level.
//
// By default, Resty does not log the curl command in the debug log since it has the potential
// to leak sensitive data unless explicitly enabled via [Client.SetDebugLogCurlCmd].
//
// NOTE: Use with care.
// - Potential to leak sensitive data from [Request] and [Response] in the debug log.
// - Beware of memory usage since the request body is reread.
func (c *Client) EnableGenerateCurlOnDebug() *Client {
c.SetGenerateCurlOnDebug(true)
// - Potential to leak sensitive data from [Request] and [Response] in the debug log
// when the debug log option is enabled.
// - Additional memory usage since the request body was reread.
// - curl body is not generated for [io.Reader] and multipart request flow.
func (c *Client) EnableGenerateCurlCmd() *Client {
c.SetGenerateCurlCmd(true)
return c
}

// DisableGenerateCurlOnDebug method disables the option set by [Client.EnableGenerateCurlOnDebug].
func (c *Client) DisableGenerateCurlOnDebug() *Client {
c.SetGenerateCurlOnDebug(false)
// DisableGenerateCurlCmd method disables the option set by [Client.EnableGenerateCurlCmd] or
// [Client.SetGenerateCurlCmd].
func (c *Client) DisableGenerateCurlCmd() *Client {
c.SetGenerateCurlCmd(false)
return c
}

// SetGenerateCurlOnDebug method is used to turn on/off the generate CURL command in debug mode
// at the client instance level. It works in conjunction with debug mode.
// SetGenerateCurlCmd method is used to turn on/off the generate curl command at the
// client instance level.
//
// By default, Resty does not log the curl command in the debug log since it has the potential
// to leak sensitive data unless explicitly enabled via [Client.SetDebugLogCurlCmd].
//
// NOTE: Use with care.
// - Potential to leak sensitive data from [Request] and [Response] in the debug log.
// - Beware of memory usage since the request body is reread.
// - Potential to leak sensitive data from [Request] and [Response] in the debug log
// when the debug log option is enabled.
// - Additional memory usage since the request body was reread.
// - curl body is not generated for [io.Reader] and multipart request flow.
//
// It can be overridden at the request level; see [Request.SetGenerateCurlCmd]
func (c *Client) SetGenerateCurlCmd(b bool) *Client {
c.lock.Lock()
defer c.lock.Unlock()
c.generateCurlCmd = b
return c
}

// SetDebugLogCurlCmd method enables the curl command to be logged in the debug log.
//
// It can be overridden at the request level; see [Request.SetGenerateCurlOnDebug]
func (c *Client) SetGenerateCurlOnDebug(b bool) *Client {
// It can be overridden at the request level; see [Request.SetDebugLogCurlCmd]
func (c *Client) SetDebugLogCurlCmd(b bool) *Client {
c.lock.Lock()
defer c.lock.Unlock()
c.generateCurlOnDebug = b
c.debugLogCurlCmd = b
return c
}

Expand Down
6 changes: 3 additions & 3 deletions curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ import (
)

func buildCurlCmd(req *Request) string {
// 1. Generate curl raw headers
// generate curl raw headers
var curl = "curl -X " + req.Method + " "
headers := dumpCurlHeaders(req.RawRequest)
for _, kv := range *headers {
curl += "-H " + cmdQuote(kv[0]+": "+kv[1]) + " "
}

// 2. Generate curl cookies
// generate curl cookies
if cookieJar := req.client.CookieJar(); cookieJar != nil {
if cookies := cookieJar.Cookies(req.RawRequest.URL); len(cookies) > 0 {
curl += "-H " + cmdQuote(dumpCurlCookies(cookies)) + " "
}
}

// 3. Generate curl body except for io.Reader and multipart request
// generate curl body except for io.Reader and multipart request flow
if req.RawRequest.GetBody != nil {
body, err := req.RawRequest.GetBody()
if err == nil {
Expand Down
33 changes: 18 additions & 15 deletions curl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ func TestCurlGenerateUnexecutedRequest(t *testing.T) {
).
SetMethod(MethodPost)

assertEqual(t, "", req.GenerateCurlCommand())
assertEqual(t, "", req.CurlCmd())

curlCmdUnexecuted := req.EnableGenerateCurlOnDebug().GenerateCurlCommand()
req.DisableGenerateCurlOnDebug()
curlCmdUnexecuted := req.EnableGenerateCurlCmd().CurlCmd()
req.DisableGenerateCurlCmd()

if !strings.Contains(curlCmdUnexecuted, "Cookie: count=1") ||
!strings.Contains(curlCmdUnexecuted, "curl -X POST") ||
Expand Down Expand Up @@ -60,15 +60,15 @@ func TestCurlGenerateExecutedRequest(t *testing.T) {

url := ts.URL + "/curl-cmd-post"
resp, err := req.
EnableGenerateCurlOnDebug().
EnableGenerateCurlCmd().
Post(url)
if err != nil {
t.Fatal(err)
}
curlCmdExecuted := resp.Request.GenerateCurlCommand()
curlCmdExecuted := resp.Request.CurlCmd()

c.DisableGenerateCurlOnDebug()
req.DisableGenerateCurlOnDebug()
c.DisableGenerateCurlCmd()
req.DisableGenerateCurlCmd()
if !strings.Contains(curlCmdExecuted, "Cookie: count=1") ||
!strings.Contains(curlCmdExecuted, "curl -X POST") ||
!strings.Contains(curlCmdExecuted, `-d '{"name":"Resty"}'`) ||
Expand All @@ -84,17 +84,20 @@ func TestCurlCmdDebugMode(t *testing.T) {
defer ts.Close()

c, logBuf := dcldb()
c.EnableGenerateCurlCmd().
SetDebugLogCurlCmd(true)

// Build request
req := c.EnableGenerateCurlOnDebug().R().
req := c.R().
SetBody(map[string]string{
"name": "Resty",
}).
SetCookies(
[]*http.Cookie{
{Name: "count", Value: "1"},
},
)
).
SetDebugLogCurlCmd(true)

// Execute request: set debug mode
url := ts.URL + "/curl-cmd-post"
Expand All @@ -103,8 +106,8 @@ func TestCurlCmdDebugMode(t *testing.T) {
t.Fatal(err)
}

c.DisableGenerateCurlOnDebug()
req.DisableGenerateCurlOnDebug()
c.DisableGenerateCurlCmd()
req.DisableGenerateCurlCmd()

// test logContent curl cmd
logContent := logBuf.String()
Expand Down Expand Up @@ -237,10 +240,10 @@ func TestCurlRequestGetBodyError(t *testing.T) {
).
SetMethod(MethodPost)

assertEqual(t, "", req.GenerateCurlCommand())
assertEqual(t, "", req.CurlCmd())

curlCmdUnexecuted := req.EnableGenerateCurlOnDebug().GenerateCurlCommand()
req.DisableGenerateCurlOnDebug()
curlCmdUnexecuted := req.EnableGenerateCurlCmd().CurlCmd()
req.DisableGenerateCurlCmd()

if !strings.Contains(curlCmdUnexecuted, "Cookie: count=1") ||
!strings.Contains(curlCmdUnexecuted, "curl -X POST") ||
Expand All @@ -261,7 +264,7 @@ func TestCurlRequestMiddlewaresError(t *testing.T) {
PrepareRequestMiddleware,
)

curlCmdUnexecuted := c.R().EnableGenerateCurlOnDebug().GenerateCurlCommand()
curlCmdUnexecuted := c.R().EnableGenerateCurlCmd().CurlCmd()
assertEqual(t, "", curlCmdUnexecuted)
}

Expand Down
16 changes: 3 additions & 13 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,10 @@ func PrepareRequestMiddleware(c *Client, r *Request) (err error) {
// is URL-related, and those get caught up in the `parseRequestURL`
createRawRequest(c, r)

// last one doesn't need if condition
return addCredentials(c, r)
}
addCredentials(c, r)

_ = r.generateCurlCommand()

// GenerateCurlRequestMiddleware method is used to perform CURL command
// generation during a request preparation
//
// See, [Client.SetGenerateCurlOnDebug], [Request.SetGenerateCurlOnDebug]
func GenerateCurlRequestMiddleware(c *Client, r *Request) (err error) {
if r.Debug && r.generateCurlOnDebug {
if isStringEmpty(r.resultCurlCmd) {
r.resultCurlCmd = buildCurlCmd(r)
}
}
return nil
}

Expand Down
97 changes: 61 additions & 36 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,29 +95,12 @@ type Request struct {
multipartFields []*MultipartField
retryConditions []RetryConditionFunc
resultCurlCmd string
generateCurlOnDebug bool
generateCurlCmd bool
debugLogCurlCmd bool
unescapeQueryParams bool
multipartErrChan chan error
}

// GenerateCurlCommand method generates the CURL command for the request.
func (r *Request) GenerateCurlCommand() string {
if !(r.Debug && r.generateCurlOnDebug) {
return ""
}
if len(r.resultCurlCmd) > 0 {
return r.resultCurlCmd
}
if r.RawRequest == nil {
if err := r.client.executeRequestMiddlewares(r); err != nil {
r.log.Errorf("%v", err)
return ""
}
}
r.resultCurlCmd = buildCurlCmd(r)
return r.resultCurlCmd
}

// SetMethod method used to set the HTTP verb for the request
func (r *Request) SetMethod(m string) *Request {
r.Method = m
Expand Down Expand Up @@ -1086,37 +1069,79 @@ func (r *Request) SetTrace(t bool) *Request {
return r
}

// EnableGenerateCurlOnDebug method enables the generation of CURL commands in the debug log.
// It works in conjunction with debug mode. It overrides the options set by the [Client].
// EnableGenerateCurlCmd method enables the generation of curl commands for the current request.
// It overrides the options set in the [Client].
//
// By default, Resty does not log the curl command in the debug log since it has the potential
// to leak sensitive data unless explicitly enabled via [Request.SetDebugLogCurlCmd].
//
// NOTE: Use with care.
// - Potential to leak sensitive data from [Request] and [Response] in the debug log.
// - Beware of memory usage since the request body is reread.
func (r *Request) EnableGenerateCurlOnDebug() *Request {
r.SetGenerateCurlOnDebug(true)
// - Potential to leak sensitive data from [Request] and [Response] in the debug log
// when the debug log option is enabled.
// - Additional memory usage since the request body was reread.
// - curl body is not generated for [io.Reader] and multipart request flow.
func (r *Request) EnableGenerateCurlCmd() *Request {
r.SetGenerateCurlCmd(true)
return r
}

// DisableGenerateCurlOnDebug method disables the option set by [Request.EnableGenerateCurlOnDebug].
// It overrides the options set by the [Client].
func (r *Request) DisableGenerateCurlOnDebug() *Request {
r.SetGenerateCurlOnDebug(false)
// DisableGenerateCurlCmd method disables the option set by [Request.EnableGenerateCurlCmd] or
// [Request.SetGenerateCurlCmd].
//
// It overrides the options set in the [Client].
func (r *Request) DisableGenerateCurlCmd() *Request {
r.SetGenerateCurlCmd(false)
return r
}

// SetGenerateCurlOnDebug method is used to turn on/off the generate CURL command in debug mode.
// It works in conjunction with debug mode.
// SetGenerateCurlCmd method is used to turn on/off the generate curl command for the current request.
//
// By default, Resty does not log the curl command in the debug log since it has the potential
// to leak sensitive data unless explicitly enabled via [Request.SetDebugLogCurlCmd].
//
// NOTE: Use with care.
// - Potential to leak sensitive data from [Request] and [Response] in the debug log.
// - Beware of memory usage since the request body is reread.
// - Potential to leak sensitive data from [Request] and [Response] in the debug log
// when the debug log option is enabled.
// - Additional memory usage since the request body was reread.
// - curl body is not generated for [io.Reader] and multipart request flow.
//
// It overrides the options set by the [Client.SetGenerateCurlOnDebug]
func (r *Request) SetGenerateCurlOnDebug(b bool) *Request {
r.generateCurlOnDebug = b
// It overrides the options set by the [Client.SetGenerateCurlCmd]
func (r *Request) SetGenerateCurlCmd(b bool) *Request {
r.generateCurlCmd = b
return r
}

// SetDebugLogCurlCmd method enables the curl command to be logged in the debug log
// for the current request.
//
// It can be overridden at the request level; see [Client.SetDebugLogCurlCmd]
func (r *Request) SetDebugLogCurlCmd(b bool) *Request {
r.debugLogCurlCmd = b
return r
}

// CurlCmd method generates the curl command for the request.
func (r *Request) CurlCmd() string {
return r.generateCurlCommand()
}

func (r *Request) generateCurlCommand() string {
if !r.generateCurlCmd {
return ""
}
if len(r.resultCurlCmd) > 0 {
return r.resultCurlCmd
}
if r.RawRequest == nil {
if err := r.client.executeRequestMiddlewares(r); err != nil {
r.log.Errorf("%v", err)
return ""
}
}
r.resultCurlCmd = buildCurlCmd(r)
return r.resultCurlCmd
}

// 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.
//
Expand Down
1 change: 0 additions & 1 deletion resty.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ func createClient(hc *http.Client) *Client {
// request middlewares
c.SetRequestMiddlewares(
PrepareRequestMiddleware,
GenerateCurlRequestMiddleware,
)

// response middlewares
Expand Down
2 changes: 1 addition & 1 deletion util.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func requestDebugLogger(c *Client, r *Request) {

reqLog := "\n==============================================================================\n"

if r.Debug && r.generateCurlOnDebug {
if r.generateCurlCmd && r.debugLogCurlCmd {
reqLog += "~~~ REQUEST(CURL) ~~~\n" +
fmt.Sprintf(" %v\n", r.resultCurlCmd)
}
Expand Down

0 comments on commit 9b82799

Please sign in to comment.