From db2857768ce46b677de5754466b65d0ebbc0db54 Mon Sep 17 00:00:00 2001 From: imroc Date: Sun, 4 Jun 2017 16:56:08 +0800 Subject: [PATCH] refactor internally --- dump.go | 8 +- dump_test.go | 1 + option.go | 74 -------- option_test.go | 95 ---------- req.go | 482 +++++++++++++++++++++++++++++++++---------------- req_test.go | 238 ++++++++++++++++++++++++ resp.go | 9 +- resp_test.go | 1 + 8 files changed, 577 insertions(+), 331 deletions(-) create mode 100644 dump_test.go delete mode 100644 option.go delete mode 100644 option_test.go create mode 100644 req_test.go create mode 100644 resp_test.go diff --git a/dump.go b/dump.go index 482e2e95..20082d2d 100644 --- a/dump.go +++ b/dump.go @@ -111,8 +111,12 @@ func (r *Resp) dumpRequest(dump *dumpBuffer) { if head { r.dumpReqHead(dump) } - if body && len(r.reqBody) > 0 { - dump.Write(r.reqBody) + if body { + if r.multipartHelper != nil { + dump.Write(r.multipartHelper.Dump()) + } else if len(r.reqBody) > 0 { + dump.Write(r.reqBody) + } } } diff --git a/dump_test.go b/dump_test.go new file mode 100644 index 00000000..ea6c1e25 --- /dev/null +++ b/dump_test.go @@ -0,0 +1 @@ +package req diff --git a/option.go b/option.go deleted file mode 100644 index b3afb70d..00000000 --- a/option.go +++ /dev/null @@ -1,74 +0,0 @@ -package req - -import ( - "errors" - "io" - "os" - "path/filepath" -) - -// http request header param -type Header map[string]string - -// http request param -type Param map[string]string - -// used to force append http request param to the uri -type QueryParam map[string]string - -// used for set request's Host -type Host string - -// represents a file to upload -type FileUpload struct { - // filename in multipart form. - FileName string - // form field name - FieldName string - // file to uplaod, required - File io.ReadCloser -} - -type bodyJson struct { - v interface{} -} - -// BodyJSON make the object be encoded in json format and set it to the request body -func BodyJSON(v interface{}) *bodyJson { - return &bodyJson{v: v} -} - -type bodyXml struct { - v interface{} -} - -// BodyXML make the object be encoded in xml format and set it to the request body -func BodyXML(v interface{}) *bodyXml { - return &bodyXml{v: v} -} - -// File upload files matching the name pattern such as -// /usr/*/bin/go* (assuming the Separator is '/') -func File(patterns ...string) interface{} { - matches := []string{} - for _, pattern := range patterns { - m, err := filepath.Glob(pattern) - if err != nil { - return err - } - matches = append(matches, m...) - } - if len(matches) == 0 { - return errors.New("req: No file have been matched") - } - uploads := []FileUpload{} - for _, match := range matches { - if s, e := os.Stat(match); e != nil || s.IsDir() { - continue - } - file, _ := os.Open(match) - uploads = append(uploads, FileUpload{File: file, FileName: filepath.Base(match)}) - } - - return uploads -} diff --git a/option_test.go b/option_test.go deleted file mode 100644 index 55d04a6e..00000000 --- a/option_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package req - -import ( - "encoding/json" - "encoding/xml" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" -) - -func TestBodyJSON(t *testing.T) { - type content struct { - Code int `json:"code"` - Msg string `json:"msg"` - } - c := content{ - Code: 1, - Msg: "ok", - } - checkData := func(data []byte) { - var cc content - err := json.Unmarshal(data, &cc) - if err != nil { - t.Fatalf("error unmarshal request body in BodyJSON, json.Unmarshal: %v", err) - } - if cc != c { - t.Errorf("request body = %+v; want = %+v", cc, c) - } - } - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - data, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Fatalf("error read request body in BodyJSON, ReadAll: %v", err) - } - checkData(data) - }) - ts := httptest.NewServer(handler) - - resp, err := Post(ts.URL, BodyJSON(&c)) - if err != nil { - t.Fatalf("error initiate request in BodyJSON, Post: %v", err) - } - checkData(resp.reqBody) - - SetJSONEscapeHTML(false) - SetJSONIndent("", "\t") - resp, err = Put(ts.URL, BodyJSON(&c)) - if err != nil { - t.Fatalf("error initiate request in BodyJSON, Put: %v", err) - } - checkData(resp.reqBody) -} - -func TestBodyXML(t *testing.T) { - type content struct { - Code int `xml:"code"` - Msg string `xml:"msg"` - } - c := content{ - Code: 1, - Msg: "ok", - } - checkData := func(data []byte) { - var cc content - err := xml.Unmarshal(data, &cc) - if err != nil { - t.Fatalf("error unmarshal request body in BodyXML, xml.Unmarshal: %v", err) - } - if cc != c { - t.Errorf("request body = %+v; want = %+v", cc, c) - } - } - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - data, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Fatalf("error read request body in BodyXML, ReadAll: %v", err) - } - checkData(data) - }) - ts := httptest.NewServer(handler) - - resp, err := Post(ts.URL, BodyXML(&c)) - if err != nil { - t.Fatalf("error initiate request in BodyXML, Post: %v", err) - } - checkData(resp.reqBody) - - SetXMLIndent("", " ") - resp, err = Put(ts.URL, BodyXML(&c)) - if err != nil { - t.Fatalf("error initiate request in BodyXML, Put: %v", err) - } - checkData(resp.reqBody) -} diff --git a/req.go b/req.go index 0a5e488f..a5a1f382 100644 --- a/req.go +++ b/req.go @@ -13,6 +13,8 @@ import ( "net/http" "net/textproto" "net/url" + "os" + "path/filepath" "regexp" "strconv" "strings" @@ -32,6 +34,76 @@ const ( LstdFlags = LreqHead | LreqBody | LrespHead | LrespBody ) +// Header represents http request header +type Header map[string]string + +// Param represents http request param +type Param map[string]string + +// QueryParam is used to force append http request param to the uri +type QueryParam map[string]string + +// Host is used for set request's Host +type Host string + +// FileUpload represents a file to upload +type FileUpload struct { + // filename in multipart form. + FileName string + // form field name + FieldName string + // file to uplaod, required + File io.ReadCloser +} + +// File upload files matching the name pattern such as +// /usr/*/bin/go* (assuming the Separator is '/') +func File(patterns ...string) interface{} { + matches := []string{} + for _, pattern := range patterns { + m, err := filepath.Glob(pattern) + if err != nil { + return err + } + matches = append(matches, m...) + } + if len(matches) == 0 { + return errors.New("req: no file have been matched") + } + uploads := []FileUpload{} + for _, match := range matches { + if s, e := os.Stat(match); e != nil || s.IsDir() { + continue + } + file, _ := os.Open(match) + uploads = append(uploads, FileUpload{ + File: file, + FileName: filepath.Base(match), + FieldName: "media", + }) + } + + return uploads +} + +type bodyJson struct { + v interface{} +} + +type bodyXml struct { + v interface{} +} + +// BodyJSON make the object be encoded in json format and set it to the request body +func BodyJSON(v interface{}) *bodyJson { + return &bodyJson{v: v} +} + +// BodyXML make the object be encoded in xml format and set it to the request body +func BodyXML(v interface{}) *bodyXml { + return &bodyXml{v: v} +} + // Req is a convenient client for initiating requests type Req struct { client *http.Client @@ -45,11 +117,47 @@ func New() *Req { return &Req{flag: LstdFlags} } +type param struct { + url.Values +} + +func (p *param) getValues() url.Values { + if p.Values == nil { + p.Values = make(url.Values) + } + return p.Values +} + +func (p *param) Copy(pp param) { + if pp.Values == nil { + return + } + vs := p.getValues() + for key, values := range pp.Values { + for _, value := range values { + vs.Add(key, value) + } + } +} +func (p *param) Adds(m map[string]string) { + if len(m) == 0 { + return + } + vs := p.getValues() + for k, v := range m { + vs.Add(k, v) + } +} + +func (p *param) Empty() bool { + return p.Values == nil +} + var regTextContentType = regexp.MustCompile("text|xml|json|javascript|charset|java") // Do execute a http request with sepecify method and url, // and it can also have some optional params, depending on your needs. -func (r *Req) Do(method, rawurl string, v ...interface{}) (resp *Resp, err error) { +func (r *Req) Do(method, rawurl string, vs ...interface{}) (resp *Resp, err error) { if rawurl == "" { return nil, errors.New("req: url not specified") } @@ -61,139 +169,84 @@ func (r *Req) Do(method, rawurl string, v ...interface{}) (resp *Resp, err error ProtoMinor: 1, } resp = &Resp{req: req, r: r} - handleBody := func(data []byte, contentType string) { - resp.reqBody = data - req.Body = resp.getReqBody() - req.ContentLength = int64(len(resp.reqBody)) - if contentType != "" && req.Header.Get("Content-Type") == "" { - req.Header.Set("Content-Type", contentType) - } - } - var formParam []Param - var queryParam []QueryParam - var file []FileUpload - for _, p := range v { - switch t := p.(type) { + var queryParam param + var formParam param + var uploads []FileUpload + var delayedFunc []func() + var lastFunc []func() + + for _, v := range vs { + switch vv := v.(type) { case Header: - for key, value := range t { + for key, value := range vv { req.Header.Add(key, value) } case http.Header: - req.Header = t - case io.Reader: - var rc io.ReadCloser - if trc, ok := t.(io.ReadCloser); ok { - rc = trc - } else { - rc = ioutil.NopCloser(t) - } - req.Body = bodyWrapper{ - ReadCloser: rc, - limit: 102400, + for key, values := range vv { + for _, value := range values { + req.Header.Add(key, value) + } } - bs, err := ioutil.ReadAll(t) + case *bodyJson: + fn, err := setBodyJson(req, resp, r.jsonEncOpts, vv.v) if err != nil { return nil, err } - handleBody(bs, "") - if rc, ok := t.(io.ReadCloser); ok { - rc.Close() - } - case *bodyJson: - var data []byte - if r.jsonEncOpts != nil { - opts := r.jsonEncOpts - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetIndent(opts.indentPrefix, opts.indentValue) - enc.SetEscapeHTML(opts.escapeHTML) - err = enc.Encode(t.v) - if err != nil { - return nil, err - } - data = buf.Bytes() - } else { - data, err = json.Marshal(t.v) - if err != nil { - return nil, err - } - } - handleBody(data, "application/json; charset=UTF-8") + delayedFunc = append(delayedFunc, fn) case *bodyXml: - var data []byte - if r.xmlEncOpts != nil { - opts := r.xmlEncOpts - var buf bytes.Buffer - enc := xml.NewEncoder(&buf) - enc.Indent(opts.prefix, opts.indent) - err = enc.Encode(t.v) - if err != nil { - return nil, err - } - data = buf.Bytes() - } else { - data, err = xml.Marshal(t.v) - if err != nil { - return nil, err - } + fn, err := setBodyXml(req, resp, r.xmlEncOpts, vv.v) + if err != nil { + return nil, err } - handleBody(data, "application/xml; charset=UTF-8") + delayedFunc = append(delayedFunc, fn) case Param: - if method == "GET" { - queryParam = append(queryParam, QueryParam(t)) + if method == "GET" || method == "HEAD" { + queryParam.Adds(vv) } else { - formParam = append(formParam, t) + formParam.Adds(vv) } case QueryParam: - queryParam = append(queryParam, t) + queryParam.Adds(vv) case string: - handleBody([]byte(t), "") + setBodyBytes(req, resp, []byte(vv)) case []byte: - handleBody(t, "") + setBodyBytes(req, resp, vv) + case bytes.Buffer: + setBodyBytes(req, resp, vv.Bytes()) case *http.Client: - resp.client = t + resp.client = vv case FileUpload: - file = append(file, t) + uploads = append(uploads, vv) case []FileUpload: - if file == nil { - file = make([]FileUpload, 0) - } - file = append(file, t...) + uploads = append(uploads, vv...) case *http.Cookie: - req.AddCookie(t) + req.AddCookie(vv) case Host: - req.Host = string(t) + req.Host = string(vv) + case io.Reader: + fn := setBodyReader(req, resp, vv) + lastFunc = append(lastFunc, fn) case error: - err = t - return + return nil, vv } } - if len(file) > 0 && (req.Method == "POST" || req.Method == "PUT") { - r.upload(resp, file, formParam) - } else if len(formParam) > 0 { + if len(uploads) > 0 && (req.Method == "POST" || req.Method == "PUT") { // multipart + multipartHelper := &multipartHelper{form: formParam.Values, uploads: uploads} + multipartHelper.Upload(req) + resp.multipartHelper = multipartHelper + } else if !formParam.Empty() { if req.Body != nil { - return nil, errors.New("req: can not set both body and params") - } - params := make(url.Values) - for _, p := range formParam { - for key, value := range p { - params.Add(key, value) - } + queryParam.Copy(formParam) + } else { + setBodyBytes(req, resp, []byte(formParam.Encode())) + setContentType(req, "application/x-www-form-urlencoded; charset=UTF-8") } - paramStr := params.Encode() - handleBody([]byte(paramStr), "application/x-www-form-urlencoded; charset=UTF-8") } - if len(queryParam) > 0 { - params := make(url.Values) - for _, p := range queryParam { - for key, value := range p { - params.Add(key, value) - } - } - paramStr := params.Encode() + if !queryParam.Empty() { + paramStr := queryParam.Encode() if strings.IndexByte(rawurl, '?') == -1 { rawurl = rawurl + "?" + paramStr } else { @@ -207,6 +260,10 @@ func (r *Req) Do(method, rawurl string, v ...interface{}) (resp *Resp, err error } req.URL = u + for _, fn := range delayedFunc { + fn() + } + if resp.client == nil { resp.client = r.Client() } @@ -215,7 +272,11 @@ func (r *Req) Do(method, rawurl string, v ...interface{}) (resp *Resp, err error response, err := resp.client.Do(req) resp.cost = time.Since(now) if err != nil { - return + return nil, err + } + + for _, fn := range lastFunc { + fn() } resp.resp = response @@ -243,13 +304,112 @@ func (r *Req) Do(method, rawurl string, v ...interface{}) (resp *Resp, err error return } +func setBodyBytes(req *http.Request, resp *Resp, data []byte) { + resp.reqBody = data + req.Body = ioutil.NopCloser(bytes.NewReader(data)) + req.ContentLength = int64(len(data)) +} + +func setBodyJson(req *http.Request, resp *Resp, opts *jsonEncOpts, v interface{}) (func(), error) { + var data []byte + switch vv := v.(type) { + case string: + data = []byte(vv) + case []byte: + data = vv + case *bytes.Buffer: + data = vv.Bytes() + default: + if opts != nil { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetIndent(opts.indentPrefix, opts.indentValue) + enc.SetEscapeHTML(opts.escapeHTML) + err := enc.Encode(v) + if err != nil { + return nil, err + } + data = buf.Bytes() + } else { + var err error + data, err = json.Marshal(v) + if err != nil { + return nil, err + } + } + } + setBodyBytes(req, resp, data) + delayedFunc := func() { + setContentType(req, "application/json; charset=UTF-8") + } + return delayedFunc, nil +} + +func setBodyXml(req *http.Request, resp *Resp, opts *xmlEncOpts, v interface{}) (func(), error) { + var data []byte + switch vv := v.(type) { + case string: + data = []byte(vv) + case []byte: + data = vv + case *bytes.Buffer: + data = vv.Bytes() + default: + if opts != nil { + var buf bytes.Buffer + enc := xml.NewEncoder(&buf) + enc.Indent(opts.prefix, opts.indent) + err := enc.Encode(v) + if err != nil { + return nil, err + } + data = buf.Bytes() + } else { + var err error + data, err = xml.Marshal(v) + if err != nil { + return nil, err + } + } + } + setBodyBytes(req, resp, data) + delayedFunc := func() { + setContentType(req, "application/xml; charset=UTF-8") + } + return delayedFunc, nil +} + +func setContentType(req *http.Request, contentType string) { + if req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", contentType) + } +} + +func setBodyReader(req *http.Request, resp *Resp, rd io.Reader) func() { + var rc io.ReadCloser + if trc, ok := rd.(io.ReadCloser); ok { + rc = trc + } else { + rc = ioutil.NopCloser(rd) + } + bw := &bodyWrapper{ + ReadCloser: rc, + limit: 102400, + } + req.Body = bw + lastFunc := func() { + resp.reqBody = bw.buf.Bytes() + } + return lastFunc +} + type bodyWrapper struct { io.ReadCloser buf bytes.Buffer limit int } -func (b bodyWrapper) Read(p []byte) (n int, err error) { +func (b *bodyWrapper) Read(p []byte) (n int, err error) { n, err = b.ReadCloser.Read(p) if left := b.limit - b.buf.Len(); left > 0 && n > 0 { if n <= left { @@ -261,16 +421,69 @@ func (b bodyWrapper) Read(p []byte) (n int, err error) { return } -type dummyMultipart struct { - buf bytes.Buffer - w *multipart.Writer +type multipartHelper struct { + form url.Values + uploads []FileUpload + dump []byte } -func (d *dummyMultipart) WriteField(fieldname, value string) error { +func (m *multipartHelper) Upload(req *http.Request) { + pr, pw := io.Pipe() + bodyWriter := multipart.NewWriter(pw) + go func() { + for key, values := range m.form { + for _, value := range values { + bodyWriter.WriteField(key, value) + } + } + i := 0 + for _, up := range m.uploads { + if up.FieldName == "" { + i++ + up.FieldName = "file" + strconv.Itoa(i) + } + fileWriter, err := bodyWriter.CreateFormFile(up.FieldName, up.FileName) + if err != nil { + continue + } + //iocopy + _, err = io.Copy(fileWriter, up.File) + if err != nil { + continue + } + up.File.Close() + } + bodyWriter.Close() + pw.Close() + }() + req.Header.Set("Content-Type", bodyWriter.FormDataContentType()) + req.Body = ioutil.NopCloser(pr) +} + +func (m *multipartHelper) Dump() []byte { + if m.dump != nil { + return m.dump + } + var buf bytes.Buffer + bodyWriter := multipart.NewWriter(&buf) + for key, values := range m.form { + for _, value := range values { + m.writeField(bodyWriter, key, value) + } + } + for _, up := range m.uploads { + m.writeFile(bodyWriter, up.FieldName, up.FileName) + } + bodyWriter.Close() + m.dump = buf.Bytes() + return m.dump +} + +func (m *multipartHelper) writeField(w *multipart.Writer, fieldname, value string) error { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, fieldname)) - p, err := d.w.CreatePart(h) + p, err := w.CreatePart(h) if err != nil { return err } @@ -278,13 +491,13 @@ func (d *dummyMultipart) WriteField(fieldname, value string) error { return err } -func (d *dummyMultipart) WriteFile(fieldname, filename string) error { +func (m *multipartHelper) writeFile(w *multipart.Writer, fieldname, filename string) error { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldname, filename)) h.Set("Content-Type", "application/octet-stream") - p, err := d.w.CreatePart(h) + p, err := w.CreatePart(h) if err != nil { return err } @@ -292,49 +505,6 @@ func (d *dummyMultipart) WriteFile(fieldname, filename string) error { return err } -func newDummyMultipart() *dummyMultipart { - d := new(dummyMultipart) - d.w = multipart.NewWriter(&d.buf) - return d -} - -func (r *Req) upload(resp *Resp, file []FileUpload, param []Param) { - pr, pw := io.Pipe() - bodyWriter := multipart.NewWriter(pw) - d := newDummyMultipart() - go func() { - for _, p := range param { - for key, value := range p { - bodyWriter.WriteField(key, value) - d.WriteField(key, value) - } - } - i := 0 - for _, f := range file { - if f.FieldName == "" { - i++ - f.FieldName = "file" + strconv.Itoa(i) - } - fileWriter, err := bodyWriter.CreateFormFile(f.FieldName, f.FileName) - if err != nil { - return - } - //iocopy - _, err = io.Copy(fileWriter, f.File) - if err != nil { - return - } - f.File.Close() - d.WriteFile(f.FieldName, f.FileName) - } - bodyWriter.Close() - pw.Close() - resp.reqBody = d.buf.Bytes() - }() - resp.req.Header.Set("Content-Type", bodyWriter.FormDataContentType()) - resp.req.Body = ioutil.NopCloser(pr) -} - // Get execute a http GET request func (r *Req) Get(url string, v ...interface{}) (*Resp, error) { return r.Do("GET", url, v...) diff --git a/req_test.go b/req_test.go new file mode 100644 index 00000000..1ddeeb3e --- /dev/null +++ b/req_test.go @@ -0,0 +1,238 @@ +package req + +import ( + "encoding/json" + "encoding/xml" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestUrlParam(t *testing.T) { + m := map[string]string{ + "access_token": "123abc", + "name": "roc", + "enc": "中文", + } + queryHandler := func(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + for key, value := range m { + if v := query.Get(key); value != v { + t.Errorf("query param %s = %s; want = %s", key, v, value) + } + } + } + ts := httptest.NewServer(http.HandlerFunc(queryHandler)) + _, err := Get(ts.URL, QueryParam(m)) + if err != nil { + t.Fatal(err) + } + _, err = Head(ts.URL, Param(m)) + if err != nil { + t.Fatal(err) + } + _, err = Put(ts.URL, QueryParam(m)) + if err != nil { + t.Fatal(err) + } +} + +func TestFormParam(t *testing.T) { + formParam := Param{ + "access_token": "123abc", + "name": "roc", + "enc": "中文", + } + formHandler := func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + for key, value := range formParam { + if v := r.FormValue(key); value != v { + t.Errorf("form param %s = %s; want = %s", key, v, value) + } + } + } + ts := httptest.NewServer(http.HandlerFunc(formHandler)) + url := ts.URL + _, err := Post(url, formParam) + if err != nil { + t.Fatal(err) + } +} + +func TestParamBoth(t *testing.T) { + urlParam := QueryParam{ + "access_token": "123abc", + "enc": "中文", + } + formParam := Param{ + "name": "roc", + "job": "软件工程师", + } + handler := func(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + for key, value := range urlParam { + if v := query.Get(key); value != v { + t.Errorf("query param %s = %s; want = %s", key, v, value) + } + } + r.ParseForm() + for key, value := range formParam { + if v := r.FormValue(key); value != v { + t.Errorf("form param %s = %s; want = %s", key, v, value) + } + } + } + ts := httptest.NewServer(http.HandlerFunc(handler)) + url := ts.URL + _, err := Patch(url, urlParam, formParam) + if err != nil { + t.Fatal(err) + } + +} + +func TestBodyJSON(t *testing.T) { + type content struct { + Code int `json:"code"` + Msg string `json:"msg"` + } + c := content{ + Code: 1, + Msg: "ok", + } + checkData := func(data []byte) { + var cc content + err := json.Unmarshal(data, &cc) + if err != nil { + t.Fatal(err) + } + if cc != c { + t.Errorf("request body = %+v; want = %+v", cc, c) + } + } + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + checkData(data) + }) + + ts := httptest.NewServer(handler) + resp, err := Post(ts.URL, BodyJSON(&c)) + if err != nil { + t.Fatal(err) + } + checkData(resp.reqBody) + + SetJSONEscapeHTML(false) + SetJSONIndent("", "\t") + resp, err = Put(ts.URL, BodyJSON(&c)) + if err != nil { + t.Fatal(err) + } + checkData(resp.reqBody) +} + +func TestBodyXML(t *testing.T) { + type content struct { + Code int `xml:"code"` + Msg string `xml:"msg"` + } + c := content{ + Code: 1, + Msg: "ok", + } + checkData := func(data []byte) { + var cc content + err := xml.Unmarshal(data, &cc) + if err != nil { + t.Fatal(err) + } + if cc != c { + t.Errorf("request body = %+v; want = %+v", cc, c) + } + } + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + checkData(data) + }) + + ts := httptest.NewServer(handler) + resp, err := Post(ts.URL, BodyXML(&c)) + if err != nil { + t.Fatal(err) + } + checkData(resp.reqBody) + + SetXMLIndent("", " ") + resp, err = Put(ts.URL, BodyXML(&c)) + if err != nil { + t.Fatal(err) + } + checkData(resp.reqBody) +} + +func TestHeader(t *testing.T) { + header := Header{ + "User-Agent": "V1.0.0", + "Authorization": "roc", + } + handler := func(w http.ResponseWriter, r *http.Request) { + for key, value := range header { + if v := r.Header.Get(key); value != v { + t.Errorf("header %q = %s; want = %s", key, v, value) + } + } + } + ts := httptest.NewServer(http.HandlerFunc(handler)) + _, err := Head(ts.URL, header) + if err != nil { + t.Fatal(err) + } +} + +func TestUpload(t *testing.T) { + str := "hello req" + file := ioutil.NopCloser(strings.NewReader(str)) + upload := FileUpload{ + File: file, + FieldName: "media", + FileName: "hello.txt", + } + handler := func(w http.ResponseWriter, r *http.Request) { + mr, err := r.MultipartReader() + if err != nil { + t.Fatal(err) + } + for { + p, err := mr.NextPart() + if err != nil { + break + } + if p.FileName() != upload.FileName { + t.Errorf("filename = %s; want = %s", p.FileName(), upload.FileName) + } + if p.FormName() != upload.FieldName { + t.Errorf("formname = %s; want = %s", p.FileName(), upload.FileName) + } + data, err := ioutil.ReadAll(p) + if err != nil { + t.Fatal(err) + } + if string(data) != str { + t.Errorf("file content = %s; want = %s", data, str) + } + } + } + ts := httptest.NewServer(http.HandlerFunc(handler)) + _, err := Post(ts.URL, upload) + if err != nil { + t.Fatal(err) + } +} diff --git a/resp.go b/resp.go index 246f56e3..71d4bc0c 100644 --- a/resp.go +++ b/resp.go @@ -15,10 +15,11 @@ import ( // Resp represents a request with it's response type Resp struct { - r *Req - req *http.Request - resp *http.Response - client *http.Client + r *Req + req *http.Request + resp *http.Response + client *http.Client + *multipartHelper reqBody []byte respBody []byte cost time.Duration diff --git a/resp_test.go b/resp_test.go new file mode 100644 index 00000000..ea6c1e25 --- /dev/null +++ b/resp_test.go @@ -0,0 +1 @@ +package req