diff --git a/base.go b/base.go index ea243e9..a49e3ab 100644 --- a/base.go +++ b/base.go @@ -37,10 +37,8 @@ func buildBodyRequest(wf *Workflow) *http.Request { contentType = wf.Body.ContentType() } else { contentType = "" - if contentType == "" { - if wf.Method == "POST" || wf.Method == "PUT" || wf.Method == "PATCH" { - contentType = TypeURLENCODED - } + if wf.Method == "POST" || wf.Method == "PUT" || wf.Method == "PATCH" { + contentType = TypeURLENCODED } } diff --git a/go.mod b/go.mod index a87ed6a..f123b43 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module requests go 1.12 require ( - 474420502.top/eson/gjson v1.1.3 + github.com/474420502/gjson v1.1.3 + github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2 + github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 // indirect github.com/tidwall/match v1.0.1 // indirect - golang.org/x/net v0.0.0-20190902185345-cdfb69ac37fc6fa907650654115ebebb3aae2087 + golang.org/x/net v0.0.0-00010101000000-000000000000 ) replace golang.org/x/net => github.com/golang/net v0.0.0-20190902185345-cdfb69ac37fc6fa907650654115ebebb3aae2087 diff --git a/go.sum b/go.sum index 8b509c2..28490c7 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -474420502.top/eson/gjson v1.1.3 h1:SDeD1/SWm1YknuokcPww8ZmsOOguQqFAYLWnQTMMX98= -474420502.top/eson/gjson v1.1.3/go.mod h1:95mdr7XPHsGvsGZj/FeQ+iT7mggI6LKc3JT/ZnveebI= +github.com/474420502/gjson v1.1.3 h1:rQxKNSFS8bM5iVVKKb9EHY1SS2k+EhzVNUXe2xSGn8o= +github.com/474420502/gjson v1.1.3/go.mod h1:mdAOevjPYIFWOE8CpejPHwoJCz96oNnuwhjhrAVeKaY= +github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2 h1:aZtFdDNWY/yH86JPR2WX/PN63635VsE/f/nXNPAbYxY= +github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/golang/net v0.0.0-20190902185345-cdfb69ac37fc6fa907650654115ebebb3aae2087 h1:haK1T12C0CO79KUdu+ZzLL9+l9BwM9PRkd2/mQqdg8E= github.com/golang/net v0.0.0-20190902185345-cdfb69ac37fc6fa907650654115ebebb3aae2087/go.mod h1:98y8FxUyMjTdJ5eOj/8vzuiVO14/dkJ98NYhEPG8QGY= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/init_test.go b/init_test.go new file mode 100644 index 0000000..d51eec8 --- /dev/null +++ b/init_test.go @@ -0,0 +1,29 @@ +package requests + +import ( + "log" + "net/http" + "os/exec" + "time" + + "github.com/elazarl/goproxy" +) + +const ProxyAddress = "localhost:58080" + +func init() { + log.SetFlags(log.Lshortfile | log.LstdFlags) + go func() { + proxy := goproxy.NewProxyHttpServer() + proxy.Verbose = true + http.ListenAndServe(ProxyAddress, proxy) + }() + + cmd := exec.Command("/bin/bash", "-c", "docker ps | grep httpbin") + _, err := cmd.Output() + if err != nil { + log.Println("recommend 1. docker run -p 80:80 kennethreitz/httpbin \n2. echo \"127.0.0.1 httpbin.org\" >> /etc/hosts") + } + + time.Sleep(time.Millisecond * 100) +} diff --git a/multipart.go b/multipart.go index cb1b3af..5da1a64 100644 --- a/multipart.go +++ b/multipart.go @@ -35,6 +35,11 @@ func createMultipart(postParams IBody, params []interface{}) { param.FieldName = "file0" } writeFormUploadFile(mwriter, param) + case UploadFile: + if param.FieldName == "" { + param.FieldName = "file0" + } + writeFormUploadFile(mwriter, ¶m) case []*UploadFile: for i, p := range param { if p.FieldName == "" { @@ -42,6 +47,13 @@ func createMultipart(postParams IBody, params []interface{}) { } writeFormUploadFile(mwriter, p) } + case []UploadFile: + for i, p := range param { + if p.FieldName == "" { + p.FieldName = "file" + strconv.Itoa(i) + } + writeFormUploadFile(mwriter, &p) + } case string: uploadFiles, err := UploadFileFromGlob(param) if err != nil { @@ -63,7 +75,7 @@ func createMultipart(postParams IBody, params []interface{}) { } else { for ii, p := range uploadFiles { if p.FieldName == "" { - p.FieldName = "file" + strconv.Itoa(ii) + "_" + strconv.Itoa(i) + p.FieldName = "file" + strconv.Itoa(i) + "_" + strconv.Itoa(ii) } writeFormUploadFile(mwriter, p) } diff --git a/response.go b/response.go index ac7428b..fcc9363 100644 --- a/response.go +++ b/response.go @@ -16,7 +16,7 @@ type Response struct { } // FromHTTPResponse 生成Response 从标准http.Response -func FromHTTPResponse(resp *http.Response) (*Response, error) { +func FromHTTPResponse(resp *http.Response, isDecompressNoAccept bool) (*Response, error) { var err error // 复制response 返回内容 并且测试是否有解压的需求 srcbuf, err := ioutil.ReadAll(resp.Body) @@ -26,23 +26,28 @@ func FromHTTPResponse(resp *http.Response) (*Response, error) { resp.Body.Close() content := "" - srcReader := bytes.NewReader(srcbuf) - var reader io.ReadCloser - if reader, err = gzip.NewReader(srcReader); err == nil { - defer reader.Close() - buf, err := ioutil.ReadAll(reader) - if err != nil { - return nil, err + if isDecompressNoAccept { // 在某个已经遗忘的网页测试过, 为了兼容 Python requests + srcReader := bytes.NewReader(srcbuf) + var reader io.ReadCloser + if reader, err = gzip.NewReader(srcReader); err == nil { + defer reader.Close() + buf, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + content = string(buf) + } else if reader, err = zlib.NewReader(srcReader); err == nil { + defer reader.Close() + buf, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + content = string(buf) + } else { + content = string(srcbuf) } - content = string(buf) - } else if reader, err = zlib.NewReader(srcReader); err == nil { - defer reader.Close() - buf, err := ioutil.ReadAll(reader) - if err != nil { - return nil, err - } - content = string(buf) + } else { content = string(srcbuf) } @@ -59,3 +64,23 @@ func (gresp *Response) Content() string { func (gresp *Response) GetSrcResponse() *http.Response { return gresp.readResponse } + +// GetStatue 获取Statue String +func (gresp *Response) GetStatue() string { + return gresp.readResponse.Status +} + +// GetStatueCode 获取Statue int +func (gresp *Response) GetStatueCode() int { + return gresp.readResponse.StatusCode +} + +// GetHeader Header map[string][]string +func (gresp *Response) GetHeader() http.Header { + return gresp.readResponse.Header +} + +// GetContentLength 获取Content的内容长度, 如果存在 IsDecompressNoAccept 可能是压缩级别的长度, 非GetContent长度 +func (gresp *Response) GetContentLength() int64 { + return gresp.readResponse.ContentLength +} diff --git a/response_test.go b/response_test.go index 2b4511b..b2524d4 100644 --- a/response_test.go +++ b/response_test.go @@ -4,10 +4,10 @@ import ( "net/http" "testing" - "474420502.top/eson/gjson" + "github.com/474420502/gjson" ) -func TestTest(t *testing.T) { +func TestFromHTTPResponse(t *testing.T) { var gresp *http.Response var err error @@ -15,7 +15,7 @@ func TestTest(t *testing.T) { if err != nil { t.Error(err) } - resp, err := FromHTTPResponse(gresp) + resp, err := FromHTTPResponse(gresp, false) if err != nil { t.Error(err) } @@ -31,4 +31,32 @@ func TestTest(t *testing.T) { if len(resp.GetSrcResponse().Header) == 0 { t.Error("esp.GetSrcResponse().Header == nil") } + + if resp.GetStatue() != "200 OK" || resp.GetStatueCode() != 200 { + t.Error(" resp.GetStatue() != 200 OK") + } + + if len(resp.GetHeader()["Content-Length"]) != 1 { + t.Error("resp.GetHeader() is error ?") + } + + if int64(len(resp.Content())) != resp.GetContentLength() { + t.Error("content len is not equal") + } +} + +func TestResponseDeflate(t *testing.T) { + ses := NewSession() + if wf := ses.Get("http://httpbin.org/get"); wf != nil { + wf.AddHeader("accept-encoding", "deflate") + resp, err := wf.Execute() + if err != nil { + t.Error(err) + } else { + if gjson.Get(resp.Content(), "headers.Accept-Encoding").String() != "deflate" { + t.Error("Accept-Encoding != deflate ?") + } + } + } + } diff --git a/session.go b/session.go index 6fcd599..1d28ee4 100644 --- a/session.go +++ b/session.go @@ -78,7 +78,7 @@ type IBody interface { ContentType() string // AppendContent AddContentType(ct string) - // SetPrefix 设置 Prefix; 唯一前缀 + // SetPrefix 设置 Prefix; 唯一前缀; 就是ContentType的第一个, ContentType(Prefix);ContentType;ContentType SetPrefix(ct string) } @@ -90,6 +90,11 @@ type BasicAuth struct { Password string } +// IsSetting 是否设置的一些情景 +type IsSetting struct { + isDecompressNoAccept bool +} + // Session 的基本方法 type Session struct { auth *BasicAuth @@ -103,6 +108,8 @@ type Session struct { Header http.Header Query url.Values + + Is IsSetting } const ( @@ -127,7 +134,7 @@ const ( // TypeStream application/octet-stream 只能提交一个二进制流, 很少用 TypeStream = "application/octet-stream" - // TypeFormData 类型 + // TypeFormData 类型 Upload File 支持path(string) 自动转换成UploadFile TypeFormData = "multipart/form-data" // TypeMixed Mixed类型 @@ -154,7 +161,7 @@ const ( // CDialTimeout 一个Connect过程的Timeout CDialTimeout // 支持time.Duration 和 int(秒为单位) - // CKeepAlives 默认不KeepAlives, 容易被一直KeepAlives 没关闭链接 + // CKeepAlives 默认KeepAlives false, 如果默认为true容易被一直KeepAlives, 没关闭链接 CKeepAlives // CProxy 代理链接 @@ -169,8 +176,12 @@ const ( // CTLS 帐号认证 CTLS // user pwd - // CCookiejar 持久化 CookieJar - CCookiejar // true or false ; default = true + // CIsWithCookiejar 持久化 CookieJar true or false ; default = true + CIsWithCookiejar + + // CIsDecompressNoAccept 解压 当response header 不存在 Accept-Encoding + // 很多特殊情景会不返回Accept-Encoding: Gzip. 如 不按照标准的网站 + CIsDecompressNoAccept ) // NewSession 创建Session @@ -187,7 +198,7 @@ func NewSession() *Session { } client.Jar = cjar - return &Session{client: client, body: NewBody(), transport: transport, auth: nil, cookiejar: client.Jar, Header: make(http.Header)} + return &Session{client: client, body: NewBody(), transport: transport, auth: nil, cookiejar: client.Jar, Header: make(http.Header), Is: IsSetting{false}} } // SetConfig 设置配置 @@ -210,10 +221,13 @@ func (ses *Session) SetConfig(typeConfig TypeConfig, values interface{}) { panic(errors.New("error type " + reflect.TypeOf(v).String())) } case CDialTimeout: - // 没时间实现这些小细节 + // TODO: CDialTimeout CRequestTimeout 与 细节 + case CIsDecompressNoAccept: + ses.Is.isDecompressNoAccept = values.(bool) case CKeepAlives: + // println(ses.transport.DisableKeepAlives) ses.transport.DisableKeepAlives = !values.(bool) - case CCookiejar: + case CIsWithCookiejar: v := values.(bool) if v { if ses.client.Jar == nil { @@ -247,13 +261,13 @@ func (ses *Session) SetConfig(typeConfig TypeConfig, values interface{}) { switch v := values.(type) { case *BasicAuth: ses.auth.User = v.User - ses.auth.User = v.Password + ses.auth.Password = v.Password case BasicAuth: ses.auth.User = v.User - ses.auth.User = v.Password + ses.auth.Password = v.Password case []string: ses.auth.User = v[0] - ses.auth.User = v[1] + ses.auth.Password = v[1] case nil: ses.auth = nil } @@ -273,7 +287,7 @@ func (ses *Session) GetQuery() url.Values { return ses.Query } -// SetHeader 设置set Header的值 +// SetHeader 设置set Header的值, 必须符合规范 HaHa -> Haha 如果真要HaHa,只能这样 Ha-Ha func (ses *Session) SetHeader(header http.Header) { ses.Header = header } @@ -299,6 +313,7 @@ func (ses *Session) DelCookies(u *url.URL, name string) { for _, c := range cookies { if c.Name == name { c.MaxAge = -1 + break } } ses.SetCookies(u, cookies) diff --git a/session_test.go b/session_test.go index d4baa1f..440c91a 100644 --- a/session_test.go +++ b/session_test.go @@ -1,15 +1,15 @@ package requests import ( - "log" "net/http" + "net/url" "regexp" + "strings" "testing" -) + "time" -func init() { - log.SetFlags(log.Lshortfile | log.LstdFlags) -} + "github.com/474420502/gjson" +) func TestNewSession(t *testing.T) { ses := NewSession() @@ -45,7 +45,7 @@ func TestSession_Get(t *testing.T) { if err != nil { t.Error(err) } - if len(resp.Content()) <= 200 { + if len(resp.Content()) <= 150 { t.Error(resp.Content()) } }) @@ -285,7 +285,7 @@ func TestSession_SetConfig(t *testing.T) { }{ { name: "test timeout", - args: args{typeConfig: CRequestTimeout, values: 0.01}, + args: args{typeConfig: CRequestTimeout, values: 0.0001}, wantErr: true, }, @@ -297,7 +297,7 @@ func TestSession_SetConfig(t *testing.T) { { name: "test proxy", - args: args{typeConfig: CProxy, values: "http://474420502.top:7070"}, + args: args{typeConfig: CProxy, values: "http://" + ProxyAddress}, wantErr: false, }, } @@ -305,7 +305,7 @@ func TestSession_SetConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ses := NewSession() ses.SetConfig(tt.args.typeConfig, tt.args.values) - _, err := ses.Get("https://httpbin.org/get").Execute() + _, err := ses.Get("http://httpbin.org/get").Execute() if (err != nil) != tt.wantErr { t.Errorf("Metchod error = %v", err) @@ -379,3 +379,167 @@ func TestSession_Header(t *testing.T) { } }) } + +func TestSession_ConfigEx(t *testing.T) { + ses := NewSession() + ses.SetConfig(CRequestTimeout, time.Microsecond) + resp, err := ses.Get("http://httpbin.org/get").Execute() + if err == nil { + t.Error(resp) + } else { + if strings.LastIndex(err.Error(), "Client.Timeout exceeded while awaiting headers") < 0 { + t.Error(err) + } + } + + ses.SetConfig(CRequestTimeout, float32(0.0000001)) + resp, err = ses.Get("http://httpbin.org/get").Execute() + if err == nil { + t.Error(resp) + } else { + if strings.LastIndex(err.Error(), "Client.Timeout exceeded while awaiting headers") < 0 { + t.Error(err) + } + } + + ses.SetConfig(CKeepAlives, true) + ses.SetConfig(CRequestTimeout, int64(5)) + // jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + u, err := url.Parse("http://httpbin.org") + if err != nil { + t.Error(err) + } else { + // jar.SetCookies(u, []*http.Cookie{&http.Cookie{Name: "Request", Value: "Cookiejar"}}) + ses.SetConfig(CIsWithCookiejar, false) + ses.SetConfig(CIsWithCookiejar, true) + ses.SetCookies(u, []*http.Cookie{&http.Cookie{Name: "Request", Value: "Cookiejar"}, &http.Cookie{Name: "eson", Value: "bad"}}) + resp, err = ses.Get("http://httpbin.org/get").Execute() + if err != nil { + t.Error(err) + } + + if gjson.Get(resp.Content(), "headers.Cookie").String() != "Request=Cookiejar; eson=bad" { + t.Error(resp.Content()) + } + + if resp.GetSrcResponse().Header["Connection"][0] != "keep-alive" { + t.Error("CKeepAlive is error") + } + } + + ses.SetConfig(CProxy, nil) + if u, err := url.Parse("http://" + ProxyAddress); err != nil { + t.Error(err) + } else { + ses.SetConfig(CProxy, u) + } + + resp, err = ses.Get("http://httpbin.org/get").Execute() + if err != nil { + t.Error(err) + } + + ses.DelCookies(u, "eson") + resp, err = ses.Get("http://httpbin.org/cookies").Execute() + if err != nil { + t.Error(err) + } + cookies := ses.GetCookies(u) + if len(cookies) != 1 && cookies[0].String() != "Request=Cookiejar" { + t.Error("cookies del get error please check it") + } + + ses.ClearCookies() + resp, err = ses.Get("http://httpbin.org/cookies").Execute() + if err != nil { + t.Error(err) + } + if gjson.Get(resp.Content(), "cookies").String() != "{}" { + t.Error(resp.Content()) + } +} + +func TestSession_SetQuery(t *testing.T) { + ses := NewSession() + ses.SetQuery(url.Values{"query": []string{"a", "b"}}) + resp, err := ses.Get("http://httpbin.org/get").Execute() + if err != nil { + t.Error(err) + } + query := gjson.Get(resp.Content(), "args.query").Array() + for _, q := range query { + if !(q.String() == "a" || q.String() == "b") { + t.Error("query error, ", resp.Content()) + } + } +} + +func TestSession_SetHeader(t *testing.T) { + ses := NewSession() + var header http.Header + header = make(http.Header) + header["xx-xx"] = []string{"Header"} + ses.SetHeader(header) + + resp, err := ses.Get("http://httpbin.org/headers").Execute() + if err != nil { + t.Error(err) + } + + if gjson.Get(resp.Content(), "headers.Xx-Xx").String() != "Header" { + t.Error("Xx-Xx is not exists", resp.Content()) + } + + if ses.GetHeader()["xx-xx"][0] != "Header" { + t.Error("header error") + } +} + +func TestSession_SetBasicAuth(t *testing.T) { + ses := NewSession() + ses.SetConfig(CBasicAuth, &BasicAuth{User: "eson", Password: "123456"}) + resp, err := ses.Get("http://httpbin.org/basic-auth/eson/123456").Execute() + if err != nil { + t.Error(err) + } + if resp.GetSrcResponse().StatusCode != 200 { + t.Error("code != 200, code = ", resp.GetStatue()) + } + + ses.SetConfig(CBasicAuth, BasicAuth{User: "eson", Password: "12345"}) + resp, err = ses.Get("http://httpbin.org/basic-auth/eson/123456").Execute() + if err != nil { + t.Error(err) + } + + if resp.GetSrcResponse().StatusCode != 401 { + t.Error("code != 401, code = ", resp.GetStatue()) + } + + resp, err = ses.Get("http://httpbin.org/basic-auth/eson/123456").Execute() + if err != nil { + t.Error(err) + } + + if resp.GetSrcResponse().StatusCode != 401 { + t.Error("code != 401, code = ", resp.GetStatue()) + } + + ses.SetConfig(CBasicAuth, []string{"son", "123456"}) + resp, err = ses.Get("http://httpbin.org/basic-auth/eson/123456").Execute() + if err != nil { + t.Error(err) + } + if resp.GetSrcResponse().StatusCode != 401 { + t.Error("code != 401, code = ", resp.GetStatue()) + } + + ses.SetConfig(CBasicAuth, nil) + resp, err = ses.Get("http://httpbin.org/basic-auth/eson/123456").Execute() + if err != nil { + t.Error(err) + } + if resp.GetSrcResponse().StatusCode != 401 { + t.Error("code != 401, code = ", resp.GetStatue()) + } +} diff --git a/upload_file.go b/upload_file.go index e034e27..6690579 100644 --- a/upload_file.go +++ b/upload_file.go @@ -14,6 +14,51 @@ type UploadFile struct { FileReaderCloser io.ReadCloser } +// SetFileName 设置FileName属性 +func (ufile *UploadFile) SetFileName(filename string) { + ufile.FileName = filename +} + +// GetFileName 设置FileName属性 +func (ufile *UploadFile) GetFileName() string { + return ufile.FileName +} + +// SetFileReaderCloser 设置FileName属性 +func (ufile *UploadFile) SetFileReaderCloser(readerCloser io.ReadCloser) { + ufile.FileReaderCloser = readerCloser +} + +// SetFileReaderCloserFromFile 设置FileName属性 +func (ufile *UploadFile) SetFileReaderCloserFromFile(filename string) error { + fd, err := os.Open(filename) + if err != nil { + return err + } + ufile.SetFileReaderCloser(fd) + return nil +} + +// GetFileReaderCloser 设置FileName属性 +func (ufile *UploadFile) GetFileReaderCloser() io.ReadCloser { + return ufile.FileReaderCloser +} + +// SetFieldName 设置FileName属性 +func (ufile *UploadFile) SetFieldName(fieldname string) { + ufile.FieldName = fieldname +} + +// GetFieldName 设置FileName属性 +func (ufile *UploadFile) GetFieldName() string { + return ufile.FieldName +} + +// NewUploadFile 创建一个空的UploadFile, 必须设置 FileName FieldName FileReaderCloser 三个属性 +func NewUploadFile() *UploadFile { + return &UploadFile{} +} + // UploadFileFromPath 从本地文件获取上传文件 func UploadFileFromPath(fileName string) (*UploadFile, error) { fd, err := os.Open(fileName) diff --git a/upload_file_test.go b/upload_file_test.go new file mode 100644 index 0000000..860e9b0 --- /dev/null +++ b/upload_file_test.go @@ -0,0 +1,107 @@ +package requests + +import ( + "testing" + + "github.com/474420502/gjson" +) + +func TestUploadFile(t *testing.T) { + + for i := 0; i < 1; i++ { + + ses := NewSession() + wf := ses.Put("http://httpbin.org/put") + + ufile, err := UploadFileFromPath("tests/json.file") + if err != nil { + t.Error(err) + } + wf.SetBodyAuto(ufile, TypeFormData) + resp, _ := wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["file0"]; !ok { + t.Error("file error", resp.Content()) + } + + ses = NewSession() + wf = ses.Patch("http://httpbin.org/patch") + + wf.SetBodyAuto("tests/json.file", TypeFormData) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["file0"]; !ok { + t.Error("file error", resp.Content()) + } + + ses = NewSession() + wf = ses.Delete("http://httpbin.org/delete") + ufile = NewUploadFile() + ufile.SetFileName("MyFile") + ufile.SetFieldName("MyField") + ufile.SetFileReaderCloserFromFile("tests/json.file") + wf.SetBodyAuto(ufile) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["MyField"]; !ok { + t.Error("file error", resp.Content()) + } + + // ses = NewSession() + // wf = ses.Put("http://httpbin.org/put") + + ufile.SetFileReaderCloserFromFile("tests/json.file") + wf.SetBodyAuto(*ufile) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["MyField"]; !ok { + t.Error("file error", resp.Content()) + } + + // ses = NewSession() + // wf = ses.Put("http://httpbin.org/put") + + ufile = NewUploadFile() + ufile.SetFileName("MyFile") + ufile.SetFileReaderCloserFromFile("tests/json.file") + wf.SetBodyAuto(ufile) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["file0"]; !ok { + t.Error("file error", resp.Content()) + } + + ufile.SetFileReaderCloserFromFile("tests/json.file") + wf.SetBodyAuto(*ufile) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["file0"]; !ok { + t.Error("file error", resp.Content()) + } + + var ufileList []*UploadFile + ufile, err = UploadFileFromPath("tests/json.file") + if err != nil { + t.Error(err) + } + ufileList = append(ufileList, ufile) + ufile, err = UploadFileFromPath("tests/learn.js") + if err != nil { + t.Error(err) + } + ufileList = append(ufileList, ufile) + wf.SetBodyAuto(ufileList) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["file1"]; !ok { + t.Error("file error", resp.Content()) + } + + if wf.GetBody().ContentType() != "" { + t.Error("Body is not Clear") + } + + wf.SetBodyAuto([]string{"tests/learn.js", "tests/json.file"}, TypeFormData) + resp, _ = wf.Execute() + if _, ok := gjson.Get(resp.Content(), "files").Map()["file1_0"]; !ok { + t.Error("file error", resp.Content()) + } + if _, ok := gjson.Get(resp.Content(), "files").Map()["file0_0"]; !ok { + t.Error("file error", resp.Content()) + } + } + +} diff --git a/workflow.go b/workflow.go index 503bfe4..0fc0afc 100644 --- a/workflow.go +++ b/workflow.go @@ -21,7 +21,6 @@ type Workflow struct { func NewWorkflow(ses *Session, urlstr string) *Workflow { wf := &Workflow{} wf.SwitchSession(ses) - wf.SetRawURL(urlstr) wf.Body = NewBody() @@ -35,13 +34,13 @@ func (wf *Workflow) SwitchSession(ses *Session) { wf.session = ses } -// AddHeader 添加头信息 Get方法从Header参数上获取 +// AddHeader 添加头信息 Get方法从Header参数上获取 必须符合规范 HaHa -> Haha 如果真要HaHa,只能这样 Ha-Ha func (wf *Workflow) AddHeader(key, value string) *Workflow { wf.Header[key] = append(wf.Header[key], value) return wf } -// SetHeader 设置完全替换原有Header +// SetHeader 设置完全替换原有Header 必须符合规范 HaHa -> Haha 如果真要HaHa,只能这样 Ha-Ha func (wf *Workflow) SetHeader(header http.Header) *Workflow { wf.Header = make(http.Header) for k, HValues := range header { @@ -162,7 +161,7 @@ func (wf *Workflow) SetQuery(query url.Values) *Workflow { var regexGetPath = regexp.MustCompile("/[^/]*") -// GetURLPath 获取Path参数 +// GetURLPath 获取Path参数 http://localhost/anything/user/pwd return [/anything /user /pwd] func (wf *Workflow) GetURLPath() []string { return regexGetPath.FindAllString(wf.ParsedURL.Path, -1) } @@ -173,29 +172,33 @@ func (wf *Workflow) GetURLRawPath() string { } // encodePath path格式每个item都必须以/开头 -// func encodePath(path []string) string { -// rawpath := "" -// for _, p := range path { -// if p[0] != '/' { -// p = "/" + p -// } -// rawpath += p -// } -// return rawpath -// } +func encodePath(path []string) string { + rawpath := "" + for _, p := range path { + if p[0] != '/' { + p = "/" + p + } + rawpath += p + } + return rawpath +} -// // SetURLPath 设置Path参数 -// func (wf *Workflow) SetURLPath(path []string) *Workflow { -// if path == nil { -// return wf -// } -// wf.ParsedURL.Path = encodePath(path) -// return wf -// } +// SetURLPath 设置Path参数 对应 GetURLPath +func (wf *Workflow) SetURLPath(path []string) *Workflow { + if path == nil { + return wf + } + wf.ParsedURL.Path = encodePath(path) + return wf +} -// SetURLRawPath 设置Pa晚上参数 +// SetURLRawPath 设置 参数 eg. /get = http:// hostname + /get func (wf *Workflow) SetURLRawPath(path string) *Workflow { - wf.ParsedURL.Path = path + if path[0] != '/' { + wf.ParsedURL.Path = "/" + path + } else { + wf.ParsedURL.Path = path + } return wf } @@ -206,7 +209,7 @@ func (wf *Workflow) SetBody(body IBody) *Workflow { } // GetBody 参数设置 -func (wf *Workflow) GetBody(body IBody) IBody { +func (wf *Workflow) GetBody() IBody { return wf.Body } @@ -224,9 +227,10 @@ func (wf *Workflow) SetBodyAuto(params ...interface{}) *Workflow { wf.Body.SetPrefix(defaultContentType) - if defaultContentType == TypeFormData { - createMultipart(wf.Body, params) - } else { + switch defaultContentType { + case TypeFormData: + createMultipart(wf.Body, params) // 还存在 Mixed的可能 + default: var values url.Values switch param := params[0].(type) { case map[string]string: @@ -242,9 +246,26 @@ func (wf *Workflow) SetBodyAuto(params ...interface{}) *Workflow { wf.Body.SetIOBody([]byte(param)) case []byte: wf.Body.SetIOBody(param) - } + case *UploadFile: + params = append(params, TypeFormData) + wf.Body.SetPrefix(TypeFormData) + createMultipart(wf.Body, params) + case UploadFile: + params = append(params, TypeFormData) + wf.Body.SetPrefix(TypeFormData) + createMultipart(wf.Body, params) + case []*UploadFile: + params = append(params, TypeFormData) + wf.Body.SetPrefix(TypeFormData) + createMultipart(wf.Body, params) + case []UploadFile: + params = append(params, TypeFormData) + wf.Body.SetPrefix(TypeFormData) + createMultipart(wf.Body, params) + } } + } return wf } @@ -312,5 +333,6 @@ func (wf *Workflow) Execute() (*Response, error) { return nil, err } - return FromHTTPResponse(resp) + wf.Body = NewBody() + return FromHTTPResponse(resp, wf.session.Is.isDecompressNoAccept) } diff --git a/workflow_test.go b/workflow_test.go index 4ef4eb9..d42698a 100644 --- a/workflow_test.go +++ b/workflow_test.go @@ -1,10 +1,13 @@ package requests import ( + "net/http" + "net/url" "regexp" + "sort" "testing" - "474420502.top/eson/gjson" + "github.com/474420502/gjson" ) func TestWorkflow(t *testing.T) { @@ -57,4 +60,247 @@ func TestWorkflow(t *testing.T) { t.Error(resp.readContent) } }) + +} + +func TestWorkflow_SetHeader(t *testing.T) { + ses := NewSession() + wf := ses.Get("http://httpbin.org/headers") + var header http.Header + header = make(http.Header) + header["Eson"] = []string{"Bad"} + header["HaHa"] = []string{"xixi"} + wf.SetHeader(header) + + resp, err := wf.Execute() + if err == nil && gjson.Get(resp.Content(), "headers.Eson").String() != "Bad" { + t.Error("wf header error", resp.Content()) + } + + if err == nil && gjson.Get(resp.Content(), "headers.Haha").String() != "xixi" { + t.Error("wf header error", resp.Content()) + } + + // 输入不符合规范不 会自动转换 + if wf.GetHeader()["HaHa"][0] != "xixi" { + t.Error("Header 错误") + } + + if len(ses.GetHeader()) != 0 { + t.Error("session header should be zero") + } + + delete(header, "HaHa") + ses.SetHeader(header) + wf = ses.Get("http://httpbin.org/headers") + wf.AddHeader("Hello", "Hehe") + + resp, err = wf.Execute() + if err != nil || gjson.Get(resp.Content(), "headers.Eson").String() != "Bad" { + t.Error("wf header error", resp.Content()) + } + + if err != nil || gjson.Get(resp.Content(), "headers.Hello").String() != "Hehe" { + t.Error("wf header error", resp.Content()) + } + + if len(wf.GetHeader()) != 1 || wf.GetHeader()["Hello"][0] != "Hehe" { + t.Error("session header should be 1") + } + + cheader := wf.GetCombineHeader() + if len(cheader) != 2 || cheader["Eson"][0] != "Bad" { + t.Error("GetCombineHeader error") + } + + resp, err = wf.DelHeader("Hello").Execute() + if err != nil { + t.Error(err, resp.Content()) + } + + if gjson.Get(resp.Content(), "headers.Hello").Exists() { + t.Error(" wf.DelHeader error") + } +} + +func TestWorkflow_Cookies(t *testing.T) { + ses := NewSession() + u, err := url.Parse("http://httpbin.org") + if err != nil { + t.Error(err) + } + ses.SetCookies(u, []*http.Cookie{&http.Cookie{Name: "Request", Value: "Cookiejar"}}) + wf := ses.Get("http://httpbin.org/cookies") + wf.AddCookie(&http.Cookie{Name: "eson", Value: "Bad"}) + + resp, _ := wf.Execute() + if gjson.Get(resp.Content(), "cookies.Request").String() != "Cookiejar" { + t.Error(" wf.AddCookie error") + } + + if gjson.Get(resp.Content(), "cookies.eson").String() != "Bad" { + t.Error(" wf.AddCookie error") + } + + wf.DelCookie("eson") + resp, _ = wf.Execute() + if gjson.Get(resp.Content(), "cookies.Request").String() != "Cookiejar" { + t.Error(" wf.AddCookie error") + } + if gjson.Get(resp.Content(), "cookies.eson").Exists() { + t.Error(" wf.DelCookie error") + } + + wf.AddCookies([]*http.Cookie{&http.Cookie{Name: "A", Value: "AA"}, &http.Cookie{Name: "B", Value: "BB"}}) + + resp, _ = wf.Execute() + if gjson.Get(resp.Content(), "cookies.Request").String() != "Cookiejar" { + t.Error(" wf.AddCookie error") + } + if gjson.Get(resp.Content(), "cookies.A").String() != "AA" { + t.Error(" wf.AddCookies error") + } + + if gjson.Get(resp.Content(), "cookies.B").String() != "BB" { + t.Error(" wf.AddCookies error") + } + + wf.DelCookie(&http.Cookie{Name: "A", Value: "AA"}) + resp, _ = wf.Execute() + if gjson.Get(resp.Content(), "cookies.A").Exists() { + t.Error(" wf.AddCookies error") + } + + if gjson.Get(resp.Content(), "cookies.B").String() != "BB" { + t.Error(" wf.AddCookies error") + } +} + +func TestWorkflow_URL(t *testing.T) { + ses := NewSession() + wf := ses.Get("http://httpbin.org/") + u, err := url.Parse("http://httpbin.org/get") + if err != nil { + t.Error(err) + } + wf.SetParsedURL(u) + resp, _ := wf.Execute() + if gjson.Get(resp.Content(), "url").String() != "http://httpbin.org/get" { + t.Error("SetParsedURL ", resp.Content()) + } + + if wf.GetParsedURL().String() != "http://httpbin.org/get" { + t.Error("SetParsedURL ", resp.Content()) + } + + wf = ses.Get("http://httpbin.org/") + + resp, _ = wf.SetURLRawPath("/get").Execute() + if gjson.Get(resp.Content(), "url").String() != "http://httpbin.org/get" { + t.Error("SetParsedURL ", resp.Content()) + } + + if wf.GetURLRawPath() != "/get" { + t.Error("SetParsedURL ", resp.Content()) + } + + resp, _ = wf.SetURLRawPath("anything/user/password").Execute() + if gjson.Get(resp.Content(), "url").String() != "http://httpbin.org/anything/user/password" { + t.Error("SetParsedURL ", resp.Content()) + } + paths := wf.GetURLPath() + if paths[0] != "/anything" || paths[1] != "/user" || paths[2] != "/password" { + t.Error("wf.GetURLPath()", paths) + } + + wf = ses.Get("http://httpbin.org/") + wf.SetURLPath(paths) + if gjson.Get(resp.Content(), "url").String() != "http://httpbin.org/anything/user/password" { + t.Error("SetParsedURL ", resp.Content()) + } +} + +func TestWorkflow_Query(t *testing.T) { + ses := NewSession() + query := make(url.Values) + query["session"] = []string{"true"} + ses.SetQuery(query) + wf := ses.Get("http://httpbin.org/get") + wfquery := make(url.Values) + wfquery["workflow"] = []string{"do", "to"} + wf.SetQuery(wfquery) + + resp, _ := wf.Execute() + result := gjson.Get(resp.Content(), "args.workflow") + + for _, r := range result.Array() { + if !(r.String() == "to" || r.String() == "do") { + t.Error("workflow SetQuery error") + } + } + + if gjson.Get(resp.Content(), "args.session").String() != "true" { + t.Error("session SetQuery error") + } + + if v, ok := wf.GetQuery()["workflow"]; ok { + sort.Slice(v, func(i, j int) bool { + if v[i] > v[j] { + return true + } + return false + }) + if !(v[0] == "to" && v[1] == "do") && len(v) != 2 { + t.Error("workflow GetQuery", v) + } + } + + if v, ok := wf.GetQuery()["session"]; ok { + if v[0] != "true" && len(v) != 1 { + t.Error("workflow error") + } + } +} + +func TestWorkflow_Body(t *testing.T) { + ses := NewSession() + wf := ses.Post("http://httpbin.org/post") + body := NewBody() + body.SetIOBody("a=1&b=2") + wf.SetBody(body) + resp, _ := wf.Execute() + form := gjson.Get(resp.Content(), "form").Map() + if v, ok := form["a"]; ok { + if v.String() != "1" { + t.Error(v) + } + } + + if v, ok := form["b"]; ok { + if v.String() != "2" { + t.Error(v) + } + } + + body.SetPrefix(TypeJSON) + body.SetIOBody(`{"a": "1", "b": "2"}`) + wf.SetBody(body) + resp, _ = wf.Execute() + json := gjson.Get(resp.Content(), "json").Map() + if v, ok := json["a"]; ok { + if v.String() != "1" { + t.Error(v) + } + } + + if v, ok := json["b"]; ok { + if v.String() != "2" { + t.Error(v) + } + } + + // body.SetPrefix(TypeXML) + // body.SetIOBody(`12`) + // wf.SetBody(body) + // resp, _ = wf.Execute() }