commit dc1baf12c187267d7bbe0b7a9b393dd010659936 Author: huangsimin Date: Wed Oct 17 14:25:17 2018 +0800 改版前 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/base.go b/base.go new file mode 100644 index 0000000..2c915a7 --- /dev/null +++ b/base.go @@ -0,0 +1,42 @@ +package requests + +import ( + "bytes" + "errors" + "net/http" + "reflect" +) + +func buildBodyRequest(ver, url string, params *Params) *http.Request { + var req *http.Request + var err error + + if params.Body == nil { + req, err = http.NewRequest(ver, url, nil) + } else { + var body *bytes.Buffer + switch params.Body.(type) { + case []byte: + body = bytes.NewBuffer(params.Body.([]byte)) + case *bytes.Buffer: + body = bytes.NewBuffer(params.Body.(*bytes.Buffer).Bytes()) + default: + panic(errors.New("the type is not exist, type is" + reflect.TypeOf(params.Body).String())) + } + + req, err = http.NewRequest(ver, url, body) + } + + if err != nil { + panic(err) + } + + if params.ContentType == "" { + req.Header.Set("Content-Type", TypeURLENCODED) + } else { + req.Header.Set("Content-Type", params.ContentType) + } + + return req + +} diff --git a/httpclient_method.go b/httpclient_method.go new file mode 100644 index 0000000..1ffddfd --- /dev/null +++ b/httpclient_method.go @@ -0,0 +1,5 @@ +package requests + +func Get(url string) { + +} diff --git a/httpclient_method_test.go b/httpclient_method_test.go new file mode 100644 index 0000000..806e8f5 --- /dev/null +++ b/httpclient_method_test.go @@ -0,0 +1,9 @@ +package requests + +import ( + "testing" +) + +func TestGet(t *testing.T) { + +} diff --git a/multipart.go b/multipart.go new file mode 100644 index 0000000..dbdd939 --- /dev/null +++ b/multipart.go @@ -0,0 +1,91 @@ +package requests + +import ( + "bytes" + "io" + "log" + "mime/multipart" + "net/url" + "strconv" +) + +func writeFormUploadFile(mwriter *multipart.Writer, ufile *UploadFile) { + part, err := mwriter.CreateFormFile(ufile.FieldName, ufile.FileName) + if err != nil { + log.Panic(err) + } + io.Copy(part, ufile.FileReaderCloser) +} + +func createMultipart(postParams *Params, params []interface{}) { + plen := len(params) + + body := &bytes.Buffer{} + mwriter := multipart.NewWriter(body) + + defer mwriter.Close() + + for _, iparam := range params[0 : plen-1] { + switch param := iparam.(type) { + case *UploadFile: + if param.FieldName == "" { + param.FieldName = "file0" + } + writeFormUploadFile(mwriter, param) + case []*UploadFile: + for i, p := range param { + if p.FieldName == "" { + p.FieldName = "file" + strconv.Itoa(i) + } + writeFormUploadFile(mwriter, p) + } + case string: + log.Println(param) + uploadFiles, err := UploadFileFromGlob(param) + if err != nil { + log.Println(err) + } else { + for i, p := range uploadFiles { + if p.FieldName == "" { + p.FieldName = "file" + strconv.Itoa(i) + } + writeFormUploadFile(mwriter, p) + } + } + + case []string: + for i, glob := range param { + uploadFiles, err := UploadFileFromGlob(glob) + if err != nil { + log.Println(err) + } else { + for ii, p := range uploadFiles { + if p.FieldName == "" { + p.FieldName = "file" + strconv.Itoa(ii) + "_" + strconv.Itoa(i) + } + writeFormUploadFile(mwriter, p) + } + } + } + case map[string]string: + for k, v := range param { + mwriter.WriteField(k, v) + } + case map[string][]string: + for k, vs := range param { + for _, v := range vs { + mwriter.WriteField(k, v) + } + } + case url.Values: + for k, vs := range param { + for _, v := range vs { + mwriter.WriteField(k, v) + } + } + } + } + + postParams.ContentType = mwriter.FormDataContentType() + postParams.Body = body +} diff --git a/requests_method.go b/requests_method.go new file mode 100644 index 0000000..ebc8dc6 --- /dev/null +++ b/requests_method.go @@ -0,0 +1,149 @@ +package requests + +import ( + "net/http" + "net/url" +) + +// Params 相关参数结构 +type Params struct { + // Query map[string][]string + Body interface{} + // Files []UploadFile + ContentType string +} + +// Session 的基本方法 +type Session struct { + client *http.Client + params *Params +} + +// TypeContent post类型参数 +type TypeContent int + +const ( + _ TypeContent = iota + // TypeJSON 类型 + TypeJSON = "application/json" + // TypeXML 类型 + TypeXML = "text/xml" + // TypeURLENCODED 类型 + TypeURLENCODED = "application/x-www-form-urlencoded" + // TypeFormData 类型 + TypeFormData = "multipart/form-data" +) + +// NewSession 创建Session +func NewSession() *Session { + return &Session{client: &http.Client{}, params: &Params{}} +} + +// Get 请求 +func (ses *Session) Get(url string) (*Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + panic(err) + } + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} + +// SetParams 设置Post参数 +func (ses *Session) SetParams(params ...interface{}) { + plen := len(params) + defaultContentType := TypeURLENCODED + + if plen >= 2 { + t := params[plen-1] + defaultContentType = t.(string) + ses.params.ContentType = defaultContentType + } else { + ses.params.ContentType = defaultContentType + } + + if defaultContentType == TypeFormData { + // TODO: form-data + createMultipart(ses.params, params) + } else { + var values url.Values + switch param := params[0].(type) { + case map[string]string: + values := make(url.Values) + for k, v := range param { + values.Set(k, v) + } + ses.params.Body = []byte(values.Encode()) + case map[string][]string: + values = param + ses.params.Body = []byte(values.Encode()) + case string: + ses.params.Body = []byte(param) + case []byte: + ses.params.Body = param + } + } +} + +// Post 请求 +func (ses *Session) Post(url string) (*Response, error) { + req := buildBodyRequest("POST", url, ses.params) + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} + +// Put 请求 +func (ses *Session) Put(url string) (*Response, error) { + req := buildBodyRequest("PUT", url, ses.params) + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} + +// Patch 请求 +func (ses *Session) Patch(url string) (*Response, error) { + req := buildBodyRequest("PATCH", url, ses.params) + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} + +// Delete 请求 +func (ses *Session) Delete(url string) (*Response, error) { + req := buildBodyRequest("DELETE", url, ses.params) + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} + +// Head 请求 +func (ses *Session) Head(url string) (*Response, error) { + req := buildBodyRequest("HEAD", url, ses.params) + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} + +// Options 请求 +func (ses *Session) Options(url string) (*Response, error) { + req := buildBodyRequest("OPTIONS", url, ses.params) + resp, err := ses.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +} diff --git a/requests_method_test.go b/requests_method_test.go new file mode 100644 index 0000000..655b831 --- /dev/null +++ b/requests_method_test.go @@ -0,0 +1,285 @@ +package requests + +import ( + "net/http" + "regexp" + "testing" +) + +func TestNewSession(t *testing.T) { + ses := NewSession() + if ses == nil { + t.Error("session create fail, value is nil") + } +} + +func TestSession_Get(t *testing.T) { + type fields struct { + client *http.Client + } + type args struct { + url string + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + { + name: "Get test", + fields: fields{client: &http.Client{}}, + args: args{url: "http://httpbin.org/get"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ses := &Session{ + client: tt.fields.client, + } + resp, err := ses.Get(tt.args.url) + if err != nil { + t.Error(err) + } + if len(resp.Content()) <= 200 { + t.Error(resp.Content()) + } + }) + } +} + +func TestSession_Post(t *testing.T) { + type fields struct { + client *http.Client + params *Params + } + type args struct { + url string + } + tests := []struct { + name string + fields fields + args args + want *regexp.Regexp + wantErr bool + }{ + // TODO: Add test cases. + { + name: "Post test", + fields: fields{client: &http.Client{}, params: &Params{}}, + args: args{url: "http://httpbin.org/post"}, + want: regexp.MustCompile(`"form": \{\}`), + }, + { + name: "Post data", + fields: fields{client: &http.Client{}, params: &Params{Body: []byte("a=1&b=2")}}, + args: args{url: "http://httpbin.org/post"}, + want: regexp.MustCompile(`"form": \{[^"]+"a": "1"[^"]+"b": "2"[^\}]+\}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ses := &Session{ + client: tt.fields.client, + params: tt.fields.params, + } + got, err := ses.Post(tt.args.url) + if (err != nil) != tt.wantErr { + t.Errorf("Session.Post() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.want.MatchString(got.DContent) == false { + t.Errorf("Session.Post() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSession_Setparams(t *testing.T) { + type fields struct { + client *http.Client + params *Params + } + type args struct { + params []interface{} + } + tests := []struct { + name string + fields fields + args args + want *regexp.Regexp + wantErr bool + }{ + // TODO: Add test cases. + { + name: "test Setparams", + fields: fields{client: &http.Client{}, params: &Params{}}, + args: args{params: []interface{}{map[string]string{"a": "1", "b": "2"}}}, + want: regexp.MustCompile(`"form": \{[^"]+"a": "1"[^"]+"b": "2"[^\}]+\}`), + }, + { + name: "test json", + fields: fields{client: &http.Client{}, params: &Params{}}, + args: args{params: []interface{}{`{"a":"1","b":"2"}`, TypeJSON}}, + want: regexp.MustCompile(`"json": \{[^"]+"a": "1"[^"]+"b": "2"[^\}]+\}`), + }, + { + name: "test xml", + fields: fields{client: &http.Client{}, params: &Params{}}, + args: args{params: []interface{}{`test`, TypeXML}}, + want: regexp.MustCompile(`"data": "test"`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ses := NewSession() + ses.SetParams(tt.args.params...) + got, err := ses.Post("http://httpbin.org/post") + + if (err != nil) != tt.wantErr { + t.Errorf("Session.Post() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.want.MatchString(got.DContent) == false { + t.Errorf("Session.Post() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSession_PostUploadFile(t *testing.T) { + type args struct { + params interface{} + } + + tests := []struct { + name string + args args + want *regexp.Regexp + }{ + { + name: "test post uploadfile glob", + args: args{params: "tests/*.js"}, + want: regexp.MustCompile(`"file0": "data:application/octet-stream;base64`), + }, + { + name: "test post uploadfile only one file", + args: args{params: "tests/json.file"}, + want: regexp.MustCompile(`"file0": "json.file.+jsonjsonjsonjson"`), + }, + { + name: "test post uploadfile key values", + args: args{params: map[string]string{"a": "32"}}, + want: regexp.MustCompile(`"a": "32"`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ses := NewSession() + ses.SetParams(tt.args.params, TypeFormData) + got, err := ses.Post("http://httpbin.org/post") + + if err != nil { + t.Errorf("Session.Post() error = %v", err) + return + } + + if tt.want.MatchString(got.DContent) == false { + t.Errorf("Session.Post() = %v, want %v", got, tt.want) + } + + }) + } +} + +func TestSession_Put(t *testing.T) { + type args struct { + params interface{} + } + + tests := []struct { + name string + args args + want *regexp.Regexp + }{ + { + name: "test post uploadfile glob", + args: args{params: "tests/*.js"}, + want: regexp.MustCompile(`"file0": "data:application/octet-stream;base64`), + }, + { + name: "test post uploadfile only one file", + args: args{params: "tests/json.file"}, + want: regexp.MustCompile(`"file0": "json.file.+jsonjsonjsonjson"`), + }, + { + name: "test post uploadfile key values", + args: args{params: map[string]string{"a": "32"}}, + want: regexp.MustCompile(`"a": "32"`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ses := NewSession() + ses.SetParams(tt.args.params, TypeFormData) + got, err := ses.Put("http://httpbin.org/put") + + if err != nil { + t.Errorf("Session.Post() error = %v", err) + return + } + + if tt.want.MatchString(got.DContent) == false { + t.Errorf("Session.Post() = %v, want %v", got, tt.want) + } + + }) + } +} + +func TestSession_Patch(t *testing.T) { + type args struct { + params interface{} + } + + tests := []struct { + name string + args args + want *regexp.Regexp + }{ + { + name: "test post uploadfile glob", + args: args{params: "tests/*.js"}, + want: regexp.MustCompile(`"file0": "data:application/octet-stream;base64`), + }, + { + name: "test post uploadfile only one file", + args: args{params: "tests/json.file"}, + want: regexp.MustCompile(`"file0": "json.file.+jsonjsonjsonjson"`), + }, + { + name: "test post uploadfile key values", + args: args{params: map[string]string{"a": "32"}}, + want: regexp.MustCompile(`"a": "32"`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ses := NewSession() + ses.SetParams(tt.args.params, TypeFormData) + got, err := ses.Patch("http://httpbin.org/patch") + + if err != nil { + t.Errorf("Session.Post() error = %v", err) + return + } + + if tt.want.MatchString(got.DContent) == false { + t.Errorf("Session.Post() = %v, want %v", got, tt.want) + } + + }) + } +} diff --git a/response.go b/response.go new file mode 100644 index 0000000..3176aeb --- /dev/null +++ b/response.go @@ -0,0 +1,60 @@ +package requests + +import ( + "bytes" + "compress/gzip" + "compress/zlib" + "io" + "io/ioutil" + "net/http" +) + +// Response 响应内容包含http.Response +type Response struct { + DContent string + GResponse *http.Response +} + +// FromHTTPResponse 生成Response 从标准http.Response +func FromHTTPResponse(resp *http.Response) (*Response, error) { + + // 复制response 返回内容 并且测试是否有解压的需求 + srcbuf, err := ioutil.ReadAll(resp.Body) + if err != nil { + panic(err) + } + resp.Body.Close() + + cbuf := bytes.NewBuffer([]byte{}) + _, err = io.Copy(cbuf, bytes.NewReader(srcbuf)) + + if err != nil { + panic(err) + } + + resp.Body = ioutil.NopCloser(cbuf) + + content := string(srcbuf) + srcReader := bytes.NewReader(srcbuf) + + if r, err := gzip.NewReader(srcReader); err == nil { + buf, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + content = string(buf) + } else if r, err := zlib.NewReader(srcReader); err == nil { + buf, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + content = string(buf) + } + + return &Response{DContent: content, GResponse: resp}, nil +} + +// Content 返回解压后的内容 +func (gresp *Response) Content() string { + return gresp.DContent +} diff --git a/response_test.go b/response_test.go new file mode 100644 index 0000000..523a338 --- /dev/null +++ b/response_test.go @@ -0,0 +1 @@ +package requests diff --git a/tests/file.js b/tests/file.js new file mode 100644 index 0000000..eb18604 Binary files /dev/null and b/tests/file.js differ diff --git a/tests/json.file b/tests/json.file new file mode 100644 index 0000000..fa254a1 --- /dev/null +++ b/tests/json.file @@ -0,0 +1,2 @@ +json.file +fdsfsdavxvxwewe32323412jsonjsonjsonjson \ No newline at end of file diff --git a/tests/learn.js b/tests/learn.js new file mode 100644 index 0000000..53f60ed --- /dev/null +++ b/tests/learn.js @@ -0,0 +1,2 @@ +learn.js +fdsfsdavxlearnlearnlearnlearn \ No newline at end of file diff --git a/upload_file.go b/upload_file.go new file mode 100644 index 0000000..e9d07dd --- /dev/null +++ b/upload_file.go @@ -0,0 +1,58 @@ +package requests + +import ( + "io" + "log" + "os" + "path/filepath" +) + +// UploadFile 上传文件的结构 +type UploadFile struct { + FileName string + FieldName string + FileReaderCloser io.ReadCloser +} + +// UploadFileFromPath 从本地文件获取上传文件 +func UploadFileFromPath(fileName string) (*UploadFile, error) { + fd, err := os.Open(fileName) + + if err != nil { + return nil, err + } + + return &UploadFile{FileReaderCloser: fd, FileName: fileName}, nil + +} + +// UploadFileFromGlob 根据Glob从本地文件获取上传文件 +func UploadFileFromGlob(glob string) ([]*UploadFile, error) { + files, err := filepath.Glob(glob) + + if err != nil { + return nil, err + } + + if len(files) == 0 { + log.Println("UploadFileFromGlob: len(files) == 0") + } + + var ufiles []*UploadFile + + for _, f := range files { + if s, err := os.Stat(f); err != nil || s.IsDir() { + continue + } + + fd, err := os.Open(f) + if err != nil { + log.Println(fd.Name(), err) + } else { + ufiles = append(ufiles, &UploadFile{FileReaderCloser: fd, FileName: filepath.Base(fd.Name())}) + } + } + + return ufiles, nil + +} diff --git a/workflow.go b/workflow.go new file mode 100644 index 0000000..1e3d2d1 --- /dev/null +++ b/workflow.go @@ -0,0 +1,71 @@ +package requests + +import "net/url" + +// Workflow 工作流 +type Workflow struct { + session *Session + URL string + Method string + Body *Params + Query map[string][]string +} + +// NewWorkflow new and init workflow +func NewWorkflow(ses *Session) *Workflow { + wf := &Workflow{} + wf.SwitchSession(ses) + return wf +} + +// SwitchSession 替换Session +func (wf *Workflow) SwitchSession(ses *Session) { + wf.session = ses +} + +// SetParams 参数设置 +func (wf *Workflow) SetParams(params ...interface{}) *Workflow { + plen := len(params) + defaultContentType := TypeURLENCODED + + if plen >= 2 { + t := params[plen-1] + defaultContentType = t.(string) + wf.Body.ContentType = defaultContentType + } else { + wf.Body.ContentType = defaultContentType + } + + if defaultContentType == TypeFormData { + // TODO: form-data + createMultipart(wf.Body, params) + } else { + var values url.Values + switch param := params[0].(type) { + case map[string]string: + values := make(url.Values) + for k, v := range param { + values.Set(k, v) + } + wf.Body.Body = []byte(values.Encode()) + case map[string][]string: + values = param + wf.Body.Body = []byte(values.Encode()) + case string: + wf.Body.Body = []byte(param) + case []byte: + wf.Body.Body = param + } + } + return wf +} + +// Execute 执行 +func (wf *Workflow) Execute() (*Response, error) { + req := buildBodyRequest(wf.Method, wf.URL, wf.Body) + resp, err := wf.session.client.Do(req) + if err != nil { + return nil, err + } + return FromHTTPResponse(resp) +}