Skip to content

Commit

Permalink
feat: support post body bytes and file
Browse files Browse the repository at this point in the history
  • Loading branch information
ahuigo committed Jul 17, 2024
1 parent 611fb82 commit 7630137
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 54 deletions.
8 changes: 4 additions & 4 deletions demo/httpreq/curl_test.go → demo/httpreq/post-file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import (
"github.com/ahuigo/gohttptool/httpreq"
)

func TestCurl(t *testing.T) {
func TestPostFile(t *testing.T) {
curl, err := httpreq.R().
SetParams(map[string]string{"p": "1"}).
SetData(map[string]string{"key": "xx"}).
SetQueryParams(map[string]string{"p": "1"}).
SetFormData(map[string]string{"key": "xx"}).
SetAuthBasic("user", "pass").
SetHeader("header1", "value1").
AddCookieKV("count", "1").
AddFileHeader("file", "test.txt", []byte("hello world")).
AddFileHeader("file2", "test.txt", []byte("hello world2")).
AddFile("file2", getTestDataPath("a.txt")).
SetReq("GET", "/path").
ToCurl()
if err != nil {
Expand Down
103 changes: 103 additions & 0 deletions demo/httpreq/post_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package httpreq

import (
"strings"
"testing"

"github.com/ahuigo/gohttptool/httpreq"
)

type MapString = map[string]string

// POST: curl -X POST -d ” 'http://local/post?p=1'
func TestPostParams(t *testing.T) {
curl, err := httpreq.R().
SetQueryParams(map[string]string{"p": "1"}).
SetReq("POST", "http://local/post").
ToCurl()
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(curl, "curl ") ||
!strings.Contains(curl, "?p=1") {
t.Fatal("bad curl: ", curl)
} else {
t.Log(curl)
}
}

// POST: curl -X POST -H 'Content-Type: application/json' -d '{"name":"ahuigo"}' http://localhost/path
func TestPostJson(t *testing.T) {
curl, err := httpreq.R().
SetJson(map[string]string{"name": "ahuigo"}).
SetReq("POST", "/path").
ToCurl()
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(curl, "curl ") ||
!strings.Contains(curl, `'Content-Type: application/json`) ||
!strings.Contains(curl, `{"name":"ahuigo"}`) {
t.Fatal("bad curl: ", curl)
} else {
t.Log(curl)
}
}

// Post Data: curl -H 'Content-Type: application/x-www-form-urlencoded' http://local/post -d 'name=Alex'
func TestPostFormUrlEncode(t *testing.T) {
curl, err := httpreq.R().
SetFormData(map[string]string{"name": "Alex"}).
SetReq("POST", "http://local/post").
ToCurl()
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(curl, "curl ") ||
!strings.Contains(curl, `'Content-Type: application/x-www-form-urlencode`) ||
!strings.Contains(curl, "name=Alex") {
t.Fatal("bad curl: ", curl)
} else {
t.Log(curl)
}

}

// POST FormData: multipart/form-data; boundary=....
// curl http://local/post -F 'name=Alex'
func TestPostFormMultipart(t *testing.T) {
curl, err := httpreq.R().
SetIsMultiPart(true).
SetFormData(map[string]string{"name": "Alex"}).
SetReq("POST", "http://local/post").
ToCurl()
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(curl, "curl ") ||
!strings.Contains(curl, `'Content-Type: multipart/form-data`) ||
!strings.Contains(curl, `name="name"`) {
t.Fatal("bad curl: ", curl)
} else {
println(curl)
}
}

// POST: curl -X POST -H 'Content-Type: text/plain' -d 'hello!' http://local/post
func TestPostPlainData(t *testing.T) {
curl, err := httpreq.R().
SetContentType(httpreq.ContentTypePlain).
SetBody([]byte("hello!")).
SetReq("POST", "http://local/post").
ToCurl()
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(curl, "curl ") ||
!strings.Contains(curl, `'Content-Type: text/plain`) ||
!strings.Contains(curl, `-d 'hello!'`) {
t.Fatal("bad curl: ", curl)
} else {
println(curl)
}
}
2 changes: 2 additions & 0 deletions demo/httpreq/testdata/a.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

I'm a.txt
11 changes: 11 additions & 0 deletions demo/httpreq/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package httpreq

import (
"os"
"path/filepath"
)

func getTestDataPath(filename string) string {
pwd, _ := os.Getwd()
return filepath.Join(pwd, "./testdata", filename)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/ahuigo/gohttptool

go 1.22.1
go 1.21.1

require github.com/pkg/errors v0.9.1
23 changes: 14 additions & 9 deletions httpreq/curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
func (r *RequestBuilder) FromCurl(curl string) {

}

func (r *RequestBuilder) ToCurl() (curl string, err error) {
if httpreq, err := r.ToRequest(); err != nil {
return "", err
Expand All @@ -24,36 +25,39 @@ func (r *RequestBuilder) ToCurl() (curl string, err error) {
}
}

func buildCurlRequest(req *http.Request, httpCookiejar http.CookieJar) (curl string) {
func buildCurlRequest(req *http.Request, httpCookiejar http.CookieJar) (curlString string) {
buf := acquireBuffer()
defer releaseBuffer(buf)

// 1. Generate curl raw headers
curl = "curl -X " + req.Method + " "
buf.WriteString("curl -X " + req.Method + " ")
// req.Host + req.URL.Path + "?" + req.URL.RawQuery + " " + req.Proto + " "
headers := dumpCurlHeaders(req)
for _, kv := range *headers {
curl += `-H ` + shell.Quote(kv[0]+": "+kv[1]) + ` `
buf.WriteString(`-H ` + shell.Quote(kv[0]+": "+kv[1]) + ` `)
}

// 2. Generate curl cookies
if cookieJar, ok := httpCookiejar.(*cookiejar.Jar); ok {
cookies := cookieJar.Cookies(req.URL)
if len(cookies) > 0 {
curl += ` -H ` + shell.Quote(dumpCurlCookies(cookies)) + " "
buf.WriteString(` -H ` + shell.Quote(dumpCurlCookies(cookies)) + " ")
}
}

// 3. Generate curl body
if req.Body != nil {
buf, _ := io.ReadAll(req.Body)
req.Body = io.NopCloser(bytes.NewBuffer(buf)) // important!!
curl += `-d ` + shell.Quote(string(buf))
buf2, _ := io.ReadAll(req.Body)
req.Body = io.NopCloser(bytes.NewBuffer(buf2)) // important!!
buf.WriteString(`-d ` + shell.Quote(string(buf2)))
}

urlString := shell.Quote(req.URL.String())
if urlString == "''" {
urlString = "'http://unexecuted-request'"
}
curl += " " + urlString
return curl
buf.WriteString(" " + urlString)
return buf.String()
}

// dumpCurlCookies dumps cookies to curl format
Expand All @@ -64,6 +68,7 @@ func dumpCurlCookies(cookies []*http.Cookie) string {
sb.WriteString(cookie.Name + "=" + url.QueryEscape(cookie.Value) + "&")
}
return strings.TrimRight(sb.String(), "&")

}

// dumpCurlHeaders dumps headers to curl format
Expand Down
62 changes: 35 additions & 27 deletions httpreq/req-builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ import (
func (rb *RequestBuilder) ToRequest() (*http.Request, error) {
var dataType = ContentType(rb.rawreq.Header.Get("Content-Type"))
var origurl = rb.url
if len(rb.files) > 0 || len(rb.fileHeaders) > 0 {
if rb.isMultiPart || len(rb.files) > 0 || len(rb.fileHeaders) > 0 {
dataType = ContentTypeFormData
} else if rb.json != nil {
dataType = ContentTypeJson
} else if len(rb.formData) > 0 {
dataType = ContentTypeFormEncode
}
if dataType != "" {
rb.rawreq.Header.Set("Content-Type", string(dataType))
}

URL, err := rb.buildURLParams(origurl)
Expand All @@ -30,14 +37,16 @@ func (rb *RequestBuilder) ToRequest() (*http.Request, error) {
}

switch dataType {
case ContentTypeJson:
rb.setBodyJson()
case ContentTypeFormEncode:
if len(rb.datas) > 0 {
formEncodeValues := rb.buildFormEncode(rb.datas)
rb.setBodyFormEncode(formEncodeValues)
if len(rb.formData) > 0 {
rb.setBodyFormEncode(rb.formData)
}
case ContentTypeFormData:
// multipart/form-data
rb.buildFilesAndForms()
default:
}

if rb.rawreq.Body == nil && rb.rawreq.Method != "GET" {
Expand All @@ -49,25 +58,26 @@ func (rb *RequestBuilder) ToRequest() (*http.Request, error) {
return rb.rawreq, nil
}

// build post Form encode
func (rb *RequestBuilder) buildFormEncode(datas map[string]string) (Forms url.Values) {
Forms = url.Values{}
for key, value := range datas {
Forms.Add(key, value)
}
return Forms
}

// set form urlencode
func (rb *RequestBuilder) setBodyFormEncode(Forms url.Values) {
data := Forms.Encode()
func (rb *RequestBuilder) setBodyFormEncode(formData url.Values) {
data := formData.Encode()
rb.rawreq.Body = io.NopCloser(strings.NewReader(data))
rb.rawreq.ContentLength = int64(len(data))
}

func (rb *RequestBuilder) setBodyJson() {
bodyBuf, err := noescapeJSONMarshalIndent(&rb.json)
if err == nil {
prtBodyBytes := bodyBuf.Bytes()
plen := len(prtBodyBytes)
if plen > 0 && prtBodyBytes[plen-1] == '\n' {
prtBodyBytes = prtBodyBytes[:plen-1]
}
rb.rawreq.Body = io.NopCloser(bytes.NewReader(prtBodyBytes))
}
}

func (rb *RequestBuilder) buildURLParams(userURL string) (*url.URL, error) {
params := rb.params
paramsArray := rb.paramsList
if strings.HasPrefix(userURL, "/") {
userURL = "http://localhost" + userURL
} else if userURL == "" {
Expand All @@ -81,28 +91,26 @@ func (rb *RequestBuilder) buildURLParams(userURL string) (*url.URL, error) {

values := parsedURL.Query()

for key, value := range params {
values.Set(key, value)
}
for key, vals := range paramsArray {
for _, v := range vals {
values.Add(key, v)
}
for key, value := range rb.queryParam {
values[key] = value
// values.Set(key, value[0])
}
parsedURL.RawQuery = values.Encode()
return parsedURL, nil
}

func (rb *RequestBuilder) buildFilesAndForms() error {
files := rb.files
datas := rb.datas
formData := rb.formData
filesHeaders := rb.fileHeaders
//handle file multipart
var b bytes.Buffer
w := multipart.NewWriter(&b)

for k, v := range datas {
w.WriteField(k, v)
for k, v := range formData {
for _, vv := range v {
w.WriteField(k, vv)
}
}

for field, path := range files {
Expand Down
Loading

0 comments on commit 7630137

Please sign in to comment.