From 60cd54c6e3d1c3492be89b81fe3e58388542436b Mon Sep 17 00:00:00 2001 From: guonaihong Date: Sun, 7 Jun 2020 15:11:20 +0800 Subject: [PATCH] Urlencode (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 新增--data-urlencode选项支持 * 重命名 * 更新依赖 * 修改文件名 * 新增测试代码 * 更新文档 --- README.md | 2 + go.mod | 6 +- go.sum | 34 +++--- pcurl.go | 148 +++++++++++++++++------ pcurl_data_option_test.go | 91 ++++++++++++++ pcurl_form_test.go | 115 ++++++++++++++++++ pcurl_test.go | 180 ++-------------------------- pcurl_x_www_form_urlencoded_test.go | 105 ++++++++++++++++ 8 files changed, 449 insertions(+), 232 deletions(-) create mode 100644 pcurl_data_option_test.go create mode 100644 pcurl_form_test.go create mode 100644 pcurl_x_www_form_urlencoded_test.go diff --git a/README.md b/README.md index 016c10c..016938f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ pcurl是解析curl表达式的库 * 支持--url选项,curl中设置url,一般不会设置这个选项 * 支持--compressed选项 * 支持-k, --insecure选项 +* 支持-G, --get选项 +* 支持--data-urlencode选项 # 内容 - [json](#json) diff --git a/go.mod b/go.mod index 8145037..e18f22d 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.13 require ( github.com/gin-contrib/gzip v0.0.1 - github.com/gin-gonic/gin v1.5.0 - github.com/guonaihong/clop v0.0.7 + github.com/gin-gonic/gin v1.6.3 + github.com/guonaihong/clop v0.0.9 github.com/guonaihong/gout v0.0.12 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index 440acd5..4bdb3c8 100644 --- a/go.sum +++ b/go.sum @@ -7,9 +7,9 @@ github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NB github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= -github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -20,38 +20,37 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/guonaihong/clop v0.0.7 h1:sFZfmlu48o1wq6KUYdig0AsScEc7KHrxiC0NYp73jbk= -github.com/guonaihong/clop v0.0.7/go.mod h1:6r46yf9HnBFtFgblQrzvbxzeAbFob3Qud3PnuhphwgI= +github.com/guonaihong/clop v0.0.9 h1:L3fahWQEdfLQEcicXEqgvpGAR5sitAvn2ukIMaqDA9A= +github.com/guonaihong/clop v0.0.9/go.mod h1:6r46yf9HnBFtFgblQrzvbxzeAbFob3Qud3PnuhphwgI= github.com/guonaihong/gout v0.0.12 h1:3wMBcVnOzAaoh1l4ddShjgZtm4GecZCsgK+e8zgvj5c= github.com/guonaihong/gout v0.0.12/go.mod h1:vXvv5Kxr70eM5wrp4F0+t9lnLWmq+YPW2GByll2f/EA= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -64,18 +63,19 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pcurl.go b/pcurl.go index 7c7fa19..2e4eb8f 100644 --- a/pcurl.go +++ b/pcurl.go @@ -3,24 +3,25 @@ package pcurl import ( "net/http" "os" + "sort" "strings" "github.com/guonaihong/clop" "github.com/guonaihong/gout" - "github.com/guonaihong/gout/dataflow" ) // Curl结构体 type Curl struct { - Method string `clop:"-X; --request" usage:"Specify request command to use"` - Get bool `clop:"-G; --get" usage:"Put the post data in the URL and use GET"` - Header []string `clop:"-H; --header" usage:"Pass custom header(s) to server"` - Data string `clop:"-d; --data" usage:"HTTP POST data"` - DataRaw string `clop:"--data-raw" usage:"HTTP POST data, '@' allowed"` - Form []string `clop:"-F; --form" usage:"Specify multipart MIME data"` - URL2 string `clop:"args=url2" usage:"url2"` - URL string `clop:"--url" usage:"URL to work with"` - Location bool `clop:"-L; --location" usage:"Follow redirects"` //TODO + Method string `clop:"-X; --request" usage:"Specify request command to use"` + Get bool `clop:"-G; --get" usage:"Put the post data in the URL and use GET"` + Header []string `clop:"-H; --header" usage:"Pass custom header(s) to server"` + Data string `clop:"-d; --data" usage:"HTTP POST data"` + DataRaw string `clop:"--data-raw" usage:"HTTP POST data, '@' allowed"` + Form []string `clop:"-F; --form" usage:"Specify multipart MIME data"` + URL2 string `clop:"args=url2" usage:"url2"` + URL string `clop:"--url" usage:"URL to work with"` + Location bool `clop:"-L; --location" usage:"Follow redirects"` //TODO + DataUrlencode []string `clop:"--data-urlencode" usage:"HTTP POST data url encoded"` Compressed bool `clop:"--compressed" usage:"Request compressed response"` Insecure bool `clop:"-k; --insecure" "Allow insecure server connections when using SSL"` @@ -28,6 +29,13 @@ type Curl struct { p *clop.Clop } +const ( + bodyURLEncode = "data-urlencode" + bodyForm = "form" + bodyData = "data" + bodyDataRaw = "data-raw" +) + // 解析curl字符串形式表达式,并返回*http.Request func ParseAndRequest(curl string) (*http.Request, error) { return ParseString(curl).Request() @@ -69,6 +77,56 @@ func (c *Curl) createHeader() []string { return header } +func (c *Curl) findHighestPriority() string { + + // 获取 --data-urlencoded,-F or --form, -d or --data, --data-raw的命令行优先级别 + m := map[uint64]string{ + c.p.GetIndex(bodyURLEncode): bodyURLEncode, + c.p.GetIndex(bodyForm): bodyForm, + c.p.GetIndex(bodyData): bodyData, + c.p.GetIndex(bodyDataRaw): bodyDataRaw, + } + + index := []uint64{ + c.p.GetIndex(bodyURLEncode), + c.p.GetIndex(bodyForm), + c.p.GetIndex(bodyData), + c.p.GetIndex(bodyDataRaw), + } + + // 排序 + sort.Slice(index, func(i, j int) bool { + return index[i] < index[j] + }) + + // 取优先级最高的选项 + max := index[len(index)-1] + + return m[max] +} + +func (c *Curl) createWWWForm() ([]interface{}, error) { + if len(c.DataUrlencode) == 0 { + return nil, nil + } + + form := make([]interface{}, len(c.DataUrlencode)*2) + index := 0 + for _, v := range c.DataUrlencode { + pos := strings.IndexByte(v, '=') + if pos == -1 { + continue + } + + form[index] = v[:pos] + index++ + form[index] = v[pos+1:] + index++ + } + + return form, nil +} + func (c *Curl) createForm() ([]interface{}, error) { if len(c.Form) == 0 { return nil, nil @@ -130,7 +188,10 @@ func (c *Curl) setMethod() { func (c *Curl) Request() (req *http.Request, err error) { var ( - data interface{} + data interface{} + form []interface{} + wwwForm []interface{} + dataRaw string ) defer func() { @@ -143,21 +204,38 @@ func (c *Curl) Request() (req *http.Request, err error) { header := c.createHeader() - form, err := c.createForm() - if err != nil { - return nil, err + switch c.findHighestPriority() { + case bodyURLEncode: + if wwwForm, err = c.createWWWForm(); err != nil { + return nil, err + } + case bodyForm: + if form, err = c.createForm(); err != nil { + return nil, err + } + case bodyData: + dataRaw = c.Data + case bodyDataRaw: + dataRaw = c.DataRaw } - var dataRaw string - dataRaw = c.Data - // --data 和--data-raw同时出现的话, 取后面的选项 - // --data-raw @./a.file --data @./b.file 这里取-data @./b.file - // --data-raw @./a.file -data @./b.file 这里取-data-raw @./a.file - if c.p.GetIndex("data-raw") > c.p.GetIndex("data") { - dataRaw = c.DataRaw + var hc *http.Client + + if c.Insecure { + hc = &defaultInsecureSkipVerify } - data = dataRaw + g := gout.New(hc) + g.SetMethod(c.Method) //设置method POST or GET or DELETE + + if c.Compressed { + header = append(header, "Accept-Encoding", "deflate, gzip") + //header = append(header, "Accept-Encoding", "deflate, gzip") + } + + if len(dataRaw) > 0 { + data = dataRaw + } if len(dataRaw) > 0 && dataRaw[0] == '@' { fd, err := os.Open(dataRaw[1:]) if err != nil { @@ -169,34 +247,26 @@ func (c *Curl) Request() (req *http.Request, err error) { data = fd } - var g *dataflow.Gout - if c.Insecure { - g = gout.New(&defaultInsecureSkipVerify) - } else { - g = gout.New() + if len(header) > 0 { + g.SetHeader(header) //设置http header } - g.SetMethod(c.Method). //设置method POST or GET or DELETE - Debug(true) //打开debug模式 - - if c.Compressed { - header = append(header, "Accept-Encoding", "deflate, gzip") - //header = append(header, "Accept-Encoding", "deflate, gzip") + if len(form) > 0 { + g.SetForm(form) //设置formdata } - if header != nil { - g.SetHeader(header) //设置http header + if len(wwwForm) > 0 { + g.SetWWWForm(wwwForm) // 设置x-www-form-urlencoded格式数据 } - if len(form) > 0 { - g.SetForm(form) //设置formdata + if data != nil { + g.SetBody(data) } url := c.getURL() return g.SetURL(url). //设置url - SetBody(data). //设置http body - Request() //获取*http.Request + Request() //获取*http.Request } func parseSlice(curl []string, c *Curl) *Curl { diff --git a/pcurl_data_option_test.go b/pcurl_data_option_test.go new file mode 100644 index 0000000..802ed39 --- /dev/null +++ b/pcurl_data_option_test.go @@ -0,0 +1,91 @@ +package pcurl + +import ( + "fmt" + "testing" + + "github.com/guonaihong/gout" + "github.com/stretchr/testify/assert" +) + +// curl -d +// curl --data +// curl --data-raw +func Test_Data_Option(t *testing.T) { + + type testData struct { + need string + curlSlice []string + curlString string + path string + } + + for index, d := range []testData{ + { + need: `{"key":"val"}`, + curlSlice: []string{"curl", "-X", "POST", "-d", `{"key":"val"}`}, + curlString: `curl -X POST -d '{"key":"val"}'`, + path: "/", + }, + { //测试-k选项 + need: `{"key":"val"}`, + curlSlice: []string{"curl", "-k", "-X", "POST", "-d", `{"key":"val"}`}, + curlString: `curl -k -X POST -d '{"key":"val"}'`, + path: "/", + }, + { //测试--insecure选项 + need: `{"key":"val"}`, + curlSlice: []string{"curl", "--insecure", "-X", "POST", "-d", `{"key":"val"}`}, + curlString: `curl --insecure -X POST -d '{"key":"val"}'`, + path: "/", + }, + { + need: `{"type":"region","region":"bj","business":"asr","protocol":"private","connect":416"}`, + curlSlice: []string{"curl", "--location", "--request", "DELETE", "--header", "Content-Type: text/plain", "--data-raw", `{"type":"region","region":"bj","business":"asr","protocol":"private","connect":416"}`}, + curlString: `curl --location --request DELETE --header 'Content-Type: text/plain' --data-raw '{"type":"region","region":"bj","business":"asr","protocol":"private","connect":416"}'`, + path: "/appkey/admin/v1/delete/connect/rule?appkey=xx", + }, + { + need: `{"type":"region","list":[{"region":"sh","business":"asr","protocol":"http","connect":56},{"region":"bj","business":"asr","protocol":"websocket","connect":52},{"region":"bj","business":"asr","protocol":"private","connect":51}]}`, + curlSlice: []string{"curl", "--location", "--request", "POST", "--header", "Content-Type: text/plain", "--data-raw", `{"type":"region","list":[{"region":"sh","business":"asr","protocol":"http","connect":56},{"region":"bj","business":"asr","protocol":"websocket","connect":52},{"region":"bj","business":"asr","protocol":"private","connect":51}]}`}, + curlString: `curl --location --request POST --header 'Content-Type: text/plain' --data-raw '{"type":"region","list":[{"region":"sh","business":"asr","protocol":"http","connect":56},{"region":"bj","business":"asr","protocol":"websocket","connect":52},{"region":"bj","business":"asr","protocol":"private","connect":51}]}'`, + path: "/appkey/admin/v1/add/connect/rule?appkey=xx", + }, + } { + + // 创建测试服务 + ts := createGeneral2() + got := "" + + // 生成curl slice + url := ts.URL + if len(d.path) > 0 { + url = url + d.path + } + + // curlSlice追加url + curlSlice := append(d.curlSlice, url) + + fmt.Printf("\nindex:%d#%s\n", index, curlSlice) + + req, err := ParseSlice(curlSlice).Request() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + + err = gout.New().SetRequest(req).Debug(true).BindBody(&got).Do() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + assert.Equal(t, d.need, got) + + // 生成curl字符串 + curlString := d.curlString + " " + url + + fmt.Printf("\nindex:%d#%s\n", index, curlString) + + req, err = ParseString(curlString).Request() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + + err = gout.New().SetRequest(req).Debug(true).BindBody(&got).Do() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + assert.Equal(t, d.need, got, fmt.Sprintf("test index:%d", index)) + } + +} diff --git a/pcurl_form_test.go b/pcurl_form_test.go new file mode 100644 index 0000000..463d844 --- /dev/null +++ b/pcurl_form_test.go @@ -0,0 +1,115 @@ +package pcurl + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/guonaihong/gout" + "github.com/stretchr/testify/assert" +) + +func createGeneralForm(need H, t *testing.T) *httptest.Server { + router := func() *gin.Engine { + router := gin.New() + + router.POST("/", func(c *gin.Context) { + gotForm := make(H, 2) + err := c.Request.ParseMultipartForm(32 * 1024 * 1024) + + assert.NoError(t, err) + + for k, f := range c.Request.Form { + if len(f) == 0 { + continue + } + + gotForm[k] = f[0] + } + for k, f := range c.Request.MultipartForm.File { + if len(f) == 0 { + continue + } + fd, err := f[0].Open() + assert.NoError(t, err) + + var s strings.Builder + + io.Copy(&s, fd) + gotForm[k] = s.String() + fd.Close() + } + + c.ShouldBind(&gotForm) + + //c.ShouldBindHeader(&gotHeader2) + if assert.Equal(t, need, gotForm) { + c.JSON(200, gotForm) + return + } + + c.String(500, "") + }) + + return router + }() + + return httptest.NewServer(http.HandlerFunc(router.ServeHTTP)) +} + +// 测试formdata +func Test_Form(t *testing.T) { + type testForm struct { + curlForm []string + curlFormString string + need H + } + + for _, formData := range []testForm{ + testForm{ + curlForm: []string{"curl", "-X", "POST", "-F", "text=good", "-F", "voice=@./testdata/voice.pcm"}, + curlFormString: `curl -X POST -F text=good -F voice=@./testdata/voice.pcm`, + need: H{ + "text": "good", + "voice": "voice\n", + }, + }, + testForm{ + curlForm: []string{"curl", "--request", "POST", "--form", "text=good", "--form", "voice=@./testdata/voice.pcm"}, + curlFormString: `curl --request POST --form text=good --form voice=@./testdata/voice.pcm`, + need: H{ + "text": "good", + "voice": "voice\n", + }, + }, + } { + + code := 0 + // 创建测试服务端 + ts := createGeneralForm(formData.need, t) + + // 解析curl表达式 + req, err := ParseSlice(append(formData.curlForm, ts.URL)).Request() + assert.NoError(t, err) + + var getJSON H + //发送请求 + err = gout.New().SetRequest(req).Debug(true).Code(&code).BindJSON(&getJSON).Do() + assert.NoError(t, err) + assert.Equal(t, code, 200) + assert.Equal(t, formData.need, getJSON) + + // 测试string方式 + req, err = ParseAndRequest(formData.curlFormString + " " + ts.URL) + assert.NoError(t, err) + + //发送请求 + err = gout.New().SetRequest(req).Debug(true).Code(&code).BindJSON(&getJSON).Do() + assert.NoError(t, err) + assert.Equal(t, code, 200) + assert.Equal(t, formData.need, getJSON) + } +} diff --git a/pcurl_test.go b/pcurl_test.go index 3fdc828..caf5187 100644 --- a/pcurl_test.go +++ b/pcurl_test.go @@ -1,15 +1,14 @@ package pcurl import ( - "fmt" - "github.com/gin-gonic/gin" - "github.com/guonaihong/gout" - "github.com/stretchr/testify/assert" "io" "net/http" "net/http/httptest" - "strings" "testing" + + "github.com/gin-gonic/gin" + "github.com/guonaihong/gout" + "github.com/stretchr/testify/assert" ) type H map[string]interface{} @@ -30,177 +29,12 @@ func createGeneral(data string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(router.ServeHTTP)) } -func Test_Curl(t *testing.T) { - - type testData struct { - need string - curlSlice []string - curlString string - path string - } - - for index, d := range []testData{ - { - need: `{"key":"val"}`, - curlSlice: []string{"curl", "-X", "POST", "-d", `{"key":"val"}`}, - curlString: `curl -X POST -d '{"key":"val"}'`, - path: "/", - }, - { //测试-k选项 - need: `{"key":"val"}`, - curlSlice: []string{"curl", "-k", "-X", "POST", "-d", `{"key":"val"}`}, - curlString: `curl -k -X POST -d '{"key":"val"}'`, - path: "/", - }, - { //测试--insecure选项 - need: `{"key":"val"}`, - curlSlice: []string{"curl", "--insecure", "-X", "POST", "-d", `{"key":"val"}`}, - curlString: `curl --insecure -X POST -d '{"key":"val"}'`, - path: "/", - }, - { - need: `{"type":"region","region":"bj","business":"asr","protocol":"private","connect":416"}`, - curlSlice: []string{"curl", "--location", "--request", "DELETE", "--header", "Content-Type: text/plain", "--data-raw", `{"type":"region","region":"bj","business":"asr","protocol":"private","connect":416"}`}, - curlString: `curl --location --request DELETE --header 'Content-Type: text/plain' --data-raw '{"type":"region","region":"bj","business":"asr","protocol":"private","connect":416"}'`, - path: "/appkey/admin/v1/delete/connect/rule?appkey=xx", - }, - { - need: `{"type":"region","list":[{"region":"sh","business":"asr","protocol":"http","connect":56},{"region":"bj","business":"asr","protocol":"websocket","connect":52},{"region":"bj","business":"asr","protocol":"private","connect":51}]}'`, - curlSlice: []string{"curl", "--location", "--request", "POST", "--header", "Content-Type: text/plain", "--data-raw", `{"type":"region","list":[{"region":"sh","business":"asr","protocol":"http","connect":56},{"region":"bj","business":"asr","protocol":"websocket","connect":52},{"region":"bj","business":"asr","protocol":"private","connect":51}]}'`}, - curlString: `curl --location --request POST --header 'Content-Type: text/plain' --data-raw '{"type":"region","list":[{"region":"sh","business":"asr","protocol":"http","connect":56},{"region":"bj","business":"asr","protocol":"websocket","connect":52},{"region":"bj","business":"asr","protocol":"private","connect":51}]}'`, - path: "/appkey/admin/v1/add/connect/rule?appkey=xx", - }, - } { - - // 创建测试服务 - ts := createGeneral(d.need) - got := "" - - // 生成curl slice - url := ts.URL - if len(d.path) > 0 { - url = url + d.path - } - - // curlSlice追加url - curlSlice := append(d.curlSlice, url) - - fmt.Printf("\nindex:%d#%s\n", index, curlSlice) - - req, err := ParseSlice(curlSlice).Request() - assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) - - err = gout.New().SetRequest(req).Debug(true).BindBody(&got).Do() - assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) - assert.Equal(t, d.need, got) - - // 生成curl字符串 - curlString := d.curlString + " " + url - fmt.Printf("\nindex:%d#%s\n", index, curlString) - req, err = ParseString(curlString).Request() - assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) - - err = gout.New().SetRequest(req).Debug(true).BindBody(&got).Do() - assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) - assert.Equal(t, d.need, got) - } - -} - -// 测试formdata -func Test_Form(t *testing.T) { - type testForm struct { - curlForm []string - curlFormString string - need H - } - - for _, formData := range []testForm{ - testForm{ - curlForm: []string{"curl", "-X", "POST", "-F", "text=good", "-F", "voice=@./testdata/voice.pcm"}, - curlFormString: `curl -X POST -F text=good -F voice=@./testdata/voice.pcm`, - need: H{ - "text": "good", - "voice": "voice\n", - }, - }, - testForm{ - curlForm: []string{"curl", "--request", "POST", "--form", "text=good", "--form", "voice=@./testdata/voice.pcm"}, - curlFormString: `curl --request POST --form text=good --form voice=@./testdata/voice.pcm`, - need: H{ - "text": "good", - "voice": "voice\n", - }, - }, - } { - - code := 0 - // 创建测试服务端 - ts := createGeneralForm(formData.need, t) - - // 解析curl表达式 - req, err := ParseSlice(append(formData.curlForm, ts.URL)).Request() - assert.NoError(t, err) - - var getJSON H - //发送请求 - err = gout.New().SetRequest(req).Debug(true).Code(&code).BindJSON(&getJSON).Do() - assert.NoError(t, err) - assert.Equal(t, code, 200) - assert.Equal(t, formData.need, getJSON) - - // 测试string方式 - req, err = ParseAndRequest(formData.curlFormString + " " + ts.URL) - assert.NoError(t, err) - - //发送请求 - err = gout.New().SetRequest(req).Debug(true).Code(&code).BindJSON(&getJSON).Do() - assert.NoError(t, err) - assert.Equal(t, code, 200) - assert.Equal(t, formData.need, getJSON) - } -} - -func createGeneralForm(need H, t *testing.T) *httptest.Server { +func createGeneral2() *httptest.Server { router := func() *gin.Engine { router := gin.New() - router.POST("/", func(c *gin.Context) { - gotForm := make(H, 2) - err := c.Request.ParseMultipartForm(32 * 1024 * 1024) - - assert.NoError(t, err) - - for k, f := range c.Request.Form { - if len(f) == 0 { - continue - } - - gotForm[k] = f[0] - } - for k, f := range c.Request.MultipartForm.File { - if len(f) == 0 { - continue - } - fd, err := f[0].Open() - assert.NoError(t, err) - - var s strings.Builder - - io.Copy(&s, fd) - gotForm[k] = s.String() - fd.Close() - } - - c.ShouldBind(&gotForm) - - //c.ShouldBindHeader(&gotHeader2) - if assert.Equal(t, need, gotForm) { - c.JSON(200, gotForm) - return - } - - c.String(500, "") + router.Any("/*test", func(c *gin.Context) { + io.Copy(c.Writer, c.Request.Body) }) return router diff --git a/pcurl_x_www_form_urlencoded_test.go b/pcurl_x_www_form_urlencoded_test.go new file mode 100644 index 0000000..a9888a9 --- /dev/null +++ b/pcurl_x_www_form_urlencoded_test.go @@ -0,0 +1,105 @@ +package pcurl + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/guonaihong/gout" + "github.com/stretchr/testify/assert" +) + +type testWWWForm struct { + Hello string `form:"hello"` + Pcurl string `form:"pcurl"` +} + +func createWWWForm(t *testing.T, need testWWWForm) *httptest.Server { + r := gin.New() + + r.POST("/", func(c *gin.Context) { + wf := testWWWForm{} + + var buf bytes.Buffer + _, err := io.Copy(&buf, c.Request.Body) + assert.NoError(t, err) + + ioutil.NopCloser(&buf) + + c.Request.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes())) + err = c.ShouldBind(&wf) + + assert.NoError(t, err) + //err := c.ShouldBind(&wf) + assert.Equal(t, need, wf) + io.Copy(c.Writer, &buf) + }) + + return httptest.NewServer(http.HandlerFunc(r.ServeHTTP)) +} + +// --data-urlencode +func Test_DataURLEncode_Option(t *testing.T) { + type testData struct { + sendNeed testWWWForm + need string + curlSlice []string + curlString string + path string + } + + sendRequest := func(d testData, index int, req *http.Request) { + got := "" + err := gout.New().SetRequest(req).Debug(true).BindBody(&got).Do() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + assert.Equal(t, d.need, got, fmt.Sprintf("test index:%d", index)) + } + + for index, d := range []testData{ + { + sendNeed: testWWWForm{Hello: "world", Pcurl: "pcurl"}, + need: "hello=world&pcurl=pcurl", + curlSlice: []string{"curl", "-X", "POST", "--data-urlencode", "hello=world", "--data-urlencode", "pcurl=pcurl"}, + curlString: `curl -X POST --data-urlencode hello=world --data-urlencode pcurl=pcurl`, + path: "/", + }, + } { + + // 创建测试服务 + ts := createWWWForm(t, d.sendNeed) + + // 生成curl slice + url := ts.URL + if len(d.path) > 0 { + url = url + d.path + } + + // curlSlice追加url + curlSlice := append(d.curlSlice, url) + + fmt.Printf("\nindex:%d#%s\n", index, curlSlice) + + req, err := ParseSlice(curlSlice).Request() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + + // 发送请求 + sendRequest(d, index, req) + // =========分隔符================ + + // 生成curl字符串 + curlString := d.curlString + " " + url + + fmt.Printf("\nindex:%d#%s\n", index, curlString) + + req, err = ParseString(curlString).Request() + assert.NoError(t, err, fmt.Sprintf("test index :%d", index)) + + // 发送请求 + sendRequest(d, index, req) + } +}