package go2curl import ( "bytes" "fmt" "io/ioutil" "net" "net/http" "net/url" "strings" ) // FromRequest generates a curl command that can be used when debugging issues // encountered while executing requests. Be sure to capture your curl before // you execute the request if you want to capture the post body. func FromRequest(r *http.Request) string { ret := fmt.Sprintf("curl -v -X %s %s %s %s %s %s", r.Method, getHeaders(r.Header, r.Host), ifSet(r.UserAgent(), fmt.Sprintf("--user-agent '%s'", r.UserAgent())), ifSet(r.Referer(), fmt.Sprintf("--referrer '%s'", r.Referer())), r.URL.String(), getRequestBody(r)) return ret } // FromParams is less useful than FromRequest because the structure of the // request is likely in the http package call. Unlike a request value, we // cannot use a response value as we do not have access to the url, method, // or request body. If you used http.Post(), add a content-type header set to // the bodyType parameter. If you used http.PostForm(), your content-type is set // to "application/x-www-form-urlencoded". func FromParams(method string, urlStr string, requestBody string, headers http.Header) string { ret := fmt.Sprintf("curl -v -X %s %s %s %s", method, getHeaders(headers, extractHost(urlStr)), urlStr, ifSet(requestBody, fmt.Sprintf("-d '%s'", requestBody))) return ret } func extractHost(urlStr string) string { u, err := url.Parse(urlStr) if err != nil { return "" } host, _, _ := net.SplitHostPort(u.Host) return host } func ifSet(condition string, passThrough string) string { if len(condition) == 0 { return "" } return passThrough } func getRequestBody(r *http.Request) string { if r == nil || r.Body == nil { return "" } b, err := ioutil.ReadAll(r.Body) if err != nil { return "" } // copy and replace the reader readerCopy := ioutil.NopCloser(bytes.NewReader(b)) readerReplace := ioutil.NopCloser(bytes.NewReader(b)) r.Body = readerReplace data, err := ioutil.ReadAll(readerCopy) if err != nil { return "" } if len(data) == 0 { return "" } return fmt.Sprintf("-d '%s'", string(data)) } func getHeaders(h http.Header, Host string) string { ret := "" for header, values := range h { for _, value := range values { if strings.ToLower(header) != "host" { ret += fmt.Sprintf(" --header '%s: %v'", header, value) } } } // the request object does not allow overriding of the host header. one must say req.Host = "foo.bar" if Host != "" { ret += fmt.Sprintf(" --header '%s: %v'", "host", Host) } return ret }
Now we can generate a curl command based on an http.Request to be used for logging and debugging:
// create an http request data := []byte(`{"key":"value"}`) req, err := http.NewRequest("POST", "https://www.example.com", bytes.NewBuffer(data)) if err != nil { // handle err } req.Header.Add("X-Custom", "custom data") curl := FromRequest(req) // later, execute the request. On error, you can print curl to replicate and debug an issue
The generated curl command for this example would be:
curl -v -X POST --header 'X-Custom: custom data' https://www.example.com -d '{"key":"value"}'
With this, you can test integrations and dig deeper. I suggest placing the generated curl in every error handling case dealing with an http request.
Thanks to https://github.com/sethgrid/gencurl.