From 8842d704272fad3135a31c544e281df63f8033d8 Mon Sep 17 00:00:00 2001 From: caojia Date: Thu, 6 Apr 2017 00:55:52 +0800 Subject: [PATCH 01/14] add a logger interface, and make the "logging" more flexible --- gorequest.go | 4 ++-- logger.go | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 logger.go diff --git a/gorequest.go b/gorequest.go index ea1a55c..c4ad1ab 100644 --- a/gorequest.go +++ b/gorequest.go @@ -67,7 +67,7 @@ type SuperAgent struct { BasicAuth struct{ Username, Password string } Debug bool CurlCommand bool - logger *log.Logger + logger Logger Retryable struct { RetryableStatus []int RetryerTime time.Duration @@ -124,7 +124,7 @@ func (s *SuperAgent) SetCurlCommand(enable bool) *SuperAgent { return s } -func (s *SuperAgent) SetLogger(logger *log.Logger) *SuperAgent { +func (s *SuperAgent) SetLogger(logger Logger) *SuperAgent { s.logger = logger return s } diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..cf2f90d --- /dev/null +++ b/logger.go @@ -0,0 +1,7 @@ +package gorequest + +type Logger interface { + SetPrefix(string) + Printf(format string, v ...interface{}) + Println(v ...interface{}) +} From 5da6857a230eac00732b7f2fe0e4f4c7690a61cc Mon Sep 17 00:00:00 2001 From: Jay Taylor Date: Thu, 30 Mar 2017 17:28:29 -0700 Subject: [PATCH 02/14] Fixed nil reader content regression introduced with PR #129. - Don't allow buffers with nil contents to be passed to `http.NewRequest' constructor. Fixes errors like: Get https://[SOME-WEBSITE]: stream error: stream ID 1; REFUSED_STREAM See https://github.com/parnurzeal/gorequest/pull/136 for further information. --- gorequest.go | 71 +++++++++++++++++++++++++++++++++-------------- gorequest_test.go | 1 + 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/gorequest.go b/gorequest.go index ea1a55c..4a414d9 100644 --- a/gorequest.go +++ b/gorequest.go @@ -5,6 +5,7 @@ import ( "bytes" "crypto/tls" "encoding/json" + "io" "io/ioutil" "log" "net" @@ -1088,14 +1089,28 @@ func (s *SuperAgent) getResponseBytes() (Response, []byte, []error) { func (s *SuperAgent) MakeRequest() (*http.Request, error) { var ( - req *http.Request - err error + req *http.Request + contentType string // This is only set when the request body content is non-empty. + contentReader io.Reader + err error ) if s.Method == "" { return nil, errors.New("No method specified") } + // !!! Important Note !!! + // + // Throughout this region, contentReader and contentType are only set when + // the contents will be non-empty. + // This is done avoid ever sending a non-nil request body with nil contents + // to http.NewRequest, because it contains logic which dependends on + // whether or not the body is "nil". + // + // See PR #136 for more information: + // + // https://github.com/parnurzeal/gorequest/pull/136 + // if s.TargetType == "json" { // If-case to give support to json array. we check if // 1) Map only: send it as json map from s.Data @@ -1108,12 +1123,10 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { } else if len(s.SliceData) != 0 { contentJson, _ = json.Marshal(s.SliceData) } - contentReader := bytes.NewReader(contentJson) - req, err = http.NewRequest(s.Method, s.Url, contentReader) - if err != nil { - return nil, err + if contentJson != nil { + contentReader = bytes.NewReader(contentJson) + contentType = "application/json" } - req.Header.Set("Content-Type", "application/json") } else if s.TargetType == "form" || s.TargetType == "form-data" || s.TargetType == "urlencoded" { var contentForm []byte if s.BounceToRawString || len(s.SliceData) != 0 { @@ -1122,22 +1135,25 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { formData := changeMapToURLValues(s.Data) contentForm = []byte(formData.Encode()) } - contentReader := bytes.NewReader(contentForm) - req, err = http.NewRequest(s.Method, s.Url, contentReader) - if err != nil { - return nil, err + if len(contentForm) != 0 { + contentReader = bytes.NewReader(contentForm) + contentType = "application/x-www-form-urlencoded" } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } else if s.TargetType == "text" { - req, err = http.NewRequest(s.Method, s.Url, strings.NewReader(s.RawString)) - req.Header.Set("Content-Type", "text/plain") + if len(s.RawString) != 0 { + contentReader = strings.NewReader(s.RawString) + contentType = "text/plain" + } } else if s.TargetType == "xml" { - req, err = http.NewRequest(s.Method, s.Url, strings.NewReader(s.RawString)) - req.Header.Set("Content-Type", "application/xml") + if len(s.RawString) != 0 { + contentReader = strings.NewReader(s.RawString) + contentType = "application/xml" + } } else if s.TargetType == "multipart" { - - var buf bytes.Buffer - mw := multipart.NewWriter(&buf) + var ( + buf = &bytes.Buffer{} + mw = multipart.NewWriter(buf) + ) if s.BounceToRawString { fieldName, ok := s.Header["data_fieldname"] @@ -1146,6 +1162,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { } fw, _ := mw.CreateFormField(fieldName) fw.Write([]byte(s.RawString)) + contentReader = buf } if len(s.Data) != 0 { @@ -1156,6 +1173,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { fw.Write([]byte(value)) } } + contentReader = buf } if len(s.SliceData) != 0 { @@ -1174,6 +1192,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { return nil, err } fw.Write(contentJson) + contentReader = buf } // add the files @@ -1182,18 +1201,28 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { fw, _ := mw.CreateFormFile(file.Fieldname, file.Filename) fw.Write(file.Data) } + contentReader = buf } // close before call to FormDataContentType ! otherwise its not valid multipart mw.Close() - req, err = http.NewRequest(s.Method, s.Url, &buf) - req.Header.Set("Content-Type", mw.FormDataContentType()) + if contentReader != nil { + contentType = mw.FormDataContentType() + } } else { // let's return an error instead of an nil pointer exception here return nil, errors.New("TargetType '" + s.TargetType + "' could not be determined") } + if req, err = http.NewRequest(s.Method, s.Url, contentReader); err != nil { + return nil, err + } + + if len(contentType) != 0 { + req.Header.Set("Content-Type", contentType) + } + for k, v := range s.Header { req.Header.Set(k, v) // Setting the host header is a special case, see this issue: https://github.com/golang/go/issues/7682 diff --git a/gorequest_test.go b/gorequest_test.go index 2cf5a74..0d634be 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -1063,6 +1063,7 @@ func TestMultipartRequest(t *testing.T) { Type("multipart"). Query("query1=test"). Query("query2=test"). + Send("foo"). End() New().Post(ts.URL + case5_send_struct). From 640c2a7fc0469040a9d27e2486255cd477b26a9f Mon Sep 17 00:00:00 2001 From: Ryan Smith Date: Mon, 24 Apr 2017 22:44:49 -0400 Subject: [PATCH 03/14] Update README.md correcting example's use of time.Second --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5119e8..cc8211d 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ Supposing you need retry 3 times, with 5 seconds between each attempt when gets ```go request := gorequest.New() resp, body, errs := request.Get("http://example.com/"). - Retry(3, 5 * time.seconds, http.StatusBadRequest, http.StatusInternalServerError). + Retry(3, 5 * time.Second, http.StatusBadRequest, http.StatusInternalServerError). End() ``` From f96b4cda8066d2092d7e4823ee1a750583c5b469 Mon Sep 17 00:00:00 2001 From: Roy Lou Date: Tue, 25 Apr 2017 22:43:59 +0800 Subject: [PATCH 04/14] Add DoNotClearSuperAgent flag --- gorequest.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gorequest.go b/gorequest.go index f928ca3..4f0ff6a 100644 --- a/gorequest.go +++ b/gorequest.go @@ -76,6 +76,7 @@ type SuperAgent struct { Attempt int Enable bool } + DoNotClearSuperAgent bool } var DisableTransportSwap = false @@ -125,6 +126,12 @@ func (s *SuperAgent) SetCurlCommand(enable bool) *SuperAgent { return s } +// Enable the DoNotClear mode for not clearing super agent and reuse for the next request +func (s *SuperAgent) SetDoNotClearSuperAgent(enable bool) *SuperAgent { + s.DoNotClearSuperAgent = enable + return s +} + func (s *SuperAgent) SetLogger(logger Logger) *SuperAgent { s.logger = logger return s @@ -132,6 +139,9 @@ func (s *SuperAgent) SetLogger(logger Logger) *SuperAgent { // Clear SuperAgent data for another new request. func (s *SuperAgent) ClearSuperAgent() { + if s.DoNotClearSuperAgent { + return + } s.Url = "" s.Method = "" s.Header = make(map[string]string) From 476ec3a887393ee398d6a9fe7bbae0a898c2589e Mon Sep 17 00:00:00 2001 From: Eden Tsai Date: Fri, 1 Sep 2017 01:08:00 +0800 Subject: [PATCH 05/14] Change SuperAgent.Header type to http.Header for support multiple values. Also add a function `AppendHeader()` to support mutiple values of Header. --- gorequest.go | 53 ++++++++++++++++++++++++++++++++--------------- gorequest_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/gorequest.go b/gorequest.go index f928ca3..7095f14 100644 --- a/gorequest.go +++ b/gorequest.go @@ -51,7 +51,7 @@ const ( type SuperAgent struct { Url string Method string - Header map[string]string + Header http.Header TargetType string ForceType string Data map[string]interface{} @@ -92,7 +92,7 @@ func New() *SuperAgent { s := &SuperAgent{ TargetType: "json", Data: make(map[string]interface{}), - Header: make(map[string]string), + Header: http.Header{}, RawString: "", SliceData: []interface{}{}, FormData: url.Values{}, @@ -134,7 +134,7 @@ func (s *SuperAgent) SetLogger(logger Logger) *SuperAgent { func (s *SuperAgent) ClearSuperAgent() { s.Url = "" s.Method = "" - s.Header = make(map[string]string) + s.Header = http.Header{} s.Data = make(map[string]interface{}) s.SliceData = []interface{}{} s.FormData = url.Values{} @@ -230,7 +230,8 @@ func (s *SuperAgent) Options(targetUrl string) *SuperAgent { return s } -// Set is used for setting header fields. +// Set is used for setting header fields, +// this will overwrite the existed values of Header through AppendHeader(). // Example. To set `Accept` as `application/json` // // gorequest.New(). @@ -238,7 +239,20 @@ func (s *SuperAgent) Options(targetUrl string) *SuperAgent { // Set("Accept", "application/json"). // End() func (s *SuperAgent) Set(param string, value string) *SuperAgent { - s.Header[param] = value + s.Header.Set(param, value) + return s +} + +// AppendHeader is used for setting header fileds with multiple values, +// Example. To set `Accept` as `application/json, text/plain` +// +// gorequest.New(). +// Post("/gamelist"). +// AppendHeader("Accept", "application/json"). +// AppendHeader("Accept", "text/plain"). +// End() +func (s *SuperAgent) AppendHeader(param string, value string) *SuperAgent { + s.Header.Add(param, value) return s } @@ -1016,8 +1030,9 @@ func (s *SuperAgent) getResponseBytes() (Response, []byte, []error) { // If forcetype is not set, check whether user set Content-Type header. // If yes, also bounce to the correct supported TargetType automatically. default: + contentType := s.Header.Get("Content-Type") for k, v := range Types { - if s.Header["Content-Type"] == v { + if contentType == v { s.TargetType = k } } @@ -1156,8 +1171,8 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { ) if s.BounceToRawString { - fieldName, ok := s.Header["data_fieldname"] - if !ok { + fieldName := s.Header.Get("data_fieldname") + if fieldName == "" { fieldName = "data" } fw, _ := mw.CreateFormField(fieldName) @@ -1177,8 +1192,8 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { } if len(s.SliceData) != 0 { - fieldName, ok := s.Header["json_fieldname"] - if !ok { + fieldName := s.Header.Get("json_fieldname") + if fieldName == "" { fieldName = "data" } // copied from CreateFormField() in mime/multipart/writer.go @@ -1219,17 +1234,21 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { return nil, err } + for k, vals := range s.Header { + for _, v := range vals { + req.Header.Add(k, v) + } + + // Setting the Host header is a special case, see this issue: https://github.com/golang/go/issues/7682 + if strings.EqualFold(k, "Host") { + req.Host = vals[0] + } + } + if len(contentType) != 0 { req.Header.Set("Content-Type", contentType) } - for k, v := range s.Header { - req.Header.Set(k, v) - // Setting the host header is a special case, see this issue: https://github.com/golang/go/issues/7682 - if strings.EqualFold(k, "host") { - req.Host = v - } - } // Add all querystring from Query func q := req.URL.Query() for k, v := range s.QueryData { diff --git a/gorequest_test.go b/gorequest_test.go index 0d634be..229823d 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -1734,6 +1734,59 @@ func TestPlainText(t *testing.T) { End() } +// Test for request can accept multiple types. +func TestAcceptMultipleTypes(t *testing.T) { + text := `hello world \r\n I am GoRequest` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check method is PATCH before going to check other features + if r.Method != POST { + t.Errorf("Expected method %q; got %q", POST, r.Method) + } + if r.Header == nil { + t.Error("Expected non-nil request Header") + } + if r.Header.Get("Content-Type") != "text/plain" { + t.Error("Expected Header Content-Type -> text/plain", "| but got", r.Header.Get("Content-Type")) + } + + expectedAccepts := []string{"text/plain", "application/json"} + if strings.Join(r.Header["Accept"], ", ") != strings.Join(expectedAccepts, ", ") { + t.Error("Expected Header Accept -> ", expectedAccepts, "| but got", r.Header["Accept"]) + } + + defer r.Body.Close() + body, _ := ioutil.ReadAll(r.Body) + if string(body) != text { + t.Error(`Expected text `, text, "| but got", string(body)) + } + })) + + defer ts.Close() + + New().Post(ts.URL). + AppendHeader("Accept", "text/plain"). + AppendHeader("Accept", "application/json"). + Type("text"). + Send(text). + End() + + New().Post(ts.URL). + Set("Accept", "text/plain"). + AppendHeader("Accept", "application/json"). + Set("Content-Type", "text/plain"). + Send(text). + End() + + New().Post(ts.URL). + AppendHeader("Accept", "texxt/html"). // This will be overwritten by Set("Accept") + Set("Accept", "text/plain"). + AppendHeader("Accept", "application/json"). + Type("text"). + Send(text). + End() +} + func TestAsCurlCommand(t *testing.T) { var ( endpoint = "http://github.com/parnurzeal/gorequest" From 02a07d011de893303bedd3da9511dbb03a528d23 Mon Sep 17 00:00:00 2001 From: tleb Date: Sat, 2 Sep 2017 00:13:24 +0200 Subject: [PATCH 06/14] Fix typo in doc Parenthesis are not required around the condition in a if-statement in Go --- gorequest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorequest.go b/gorequest.go index f928ca3..17d307b 100644 --- a/gorequest.go +++ b/gorequest.go @@ -906,7 +906,7 @@ func changeMapToURLValues(data map[string]interface{}) url.Values { // For example: // // resp, body, errs := gorequest.New().Get("http://www.google.com").End() -// if (errs != nil) { +// if errs != nil { // fmt.Println(errs) // } // fmt.Println(resp, body) From 798c9aa09116b8bad4c513c7f1c2f9069d94ac08 Mon Sep 17 00:00:00 2001 From: Eden Tsai Date: Fri, 1 Sep 2017 01:09:16 +0800 Subject: [PATCH 07/14] Test force type to plain text --- gorequest_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/gorequest_test.go b/gorequest_test.go index 229823d..57343bb 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -1734,6 +1734,50 @@ func TestPlainText(t *testing.T) { End() } +// Test for force type to plain text even the request has specific Content-Type header. +func TestForceTypeToPlainText(t *testing.T) { + text := `hello world \r\n I am GoRequest` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check method is PATCH before going to check other features + if r.Method != POST { + t.Errorf("Expected method %q; got %q", POST, r.Method) + } + if r.Header == nil { + t.Error("Expected non-nil request Header") + } + if r.Header.Get("Content-Type") != "text/plain" { + t.Error("Expected Header Content-Type -> text/plain", "| but got", r.Header.Get("Content-Type")) + } + + defer r.Body.Close() + body, _ := ioutil.ReadAll(r.Body) + if string(body) != text { + t.Error("Expected text ", text, "| but got", string(body)) + } + })) + + defer ts.Close() + + New().Post(ts.URL). + Set("Content-Type", "text/plain"). + Type("text"). + Send(text). + End() + + New().Post(ts.URL). + Set("Content-Type", "application/json"). + Type("text"). + Send(text). + End() + + New().Post(ts.URL). + Type("text"). + Set("Content-Type", "application/json"). + Send(text). + End() +} + // Test for request can accept multiple types. func TestAcceptMultipleTypes(t *testing.T) { text := `hello world \r\n I am GoRequest` From e352d76c6a6b4b44607e50aeb3b9c6cccbd50bfd Mon Sep 17 00:00:00 2001 From: Eden Tsai Date: Fri, 1 Sep 2017 01:09:23 +0800 Subject: [PATCH 08/14] Test force type to JSON --- gorequest_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/gorequest_test.go b/gorequest_test.go index 57343bb..dbab39a 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -1778,6 +1778,50 @@ func TestForceTypeToPlainText(t *testing.T) { End() } +// Test for force type to JSON even the request has specific Content-Type header. +func TestForceTypeToJSON(t *testing.T) { + jsonData := `{"data":"hello world \r\n I am GoRequest"}` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check method is PATCH before going to check other features + if r.Method != POST { + t.Errorf("Expected method %q; got %q", POST, r.Method) + } + if r.Header == nil { + t.Error("Expected non-nil request Header") + } + if r.Header.Get("Content-Type") != "application/json" { + t.Error("Expected Header Content-Type -> application/json", "| but got", r.Header.Get("Content-Type")) + } + + defer r.Body.Close() + body, _ := ioutil.ReadAll(r.Body) + if string(body) != jsonData { + t.Error("Expected JSON ", jsonData, "| but got", string(body)) + } + })) + + defer ts.Close() + + New().Post(ts.URL). + Set("Content-Type", "application/json"). + Type("json"). + Send(jsonData). + End() + + New().Post(ts.URL). + Set("Content-Type", "text/plain"). + Type("json"). + Send(jsonData). + End() + + New().Post(ts.URL). + Type("json"). + Set("Content-Type", "text/plain"). + Send(jsonData). + End() +} + // Test for request can accept multiple types. func TestAcceptMultipleTypes(t *testing.T) { text := `hello world \r\n I am GoRequest` From a13747ad36ad1cbca6f98a806c2b061dd577c23c Mon Sep 17 00:00:00 2001 From: Eden Tsai Date: Fri, 1 Sep 2017 01:13:46 +0800 Subject: [PATCH 09/14] Add type constants - Let editor can auto completion the types. e.g. vim-go. - Add test cases for make sure they are correct. --- gorequest.go | 49 +++++++++++++++++++++------------ gorequest_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 18 deletions(-) diff --git a/gorequest.go b/gorequest.go index f928ca3..92379eb 100644 --- a/gorequest.go +++ b/gorequest.go @@ -47,6 +47,18 @@ const ( OPTIONS = "OPTIONS" ) +// Types we support. +const ( + TypeJSON = "json" + TypeXML = "xml" + TypeUrlencoded = "urlencoded" + TypeForm = "form" + TypeFormData = "form-data" + TypeHTML = "html" + TypeText = "text" + TypeMultipart = "multipart" +) + // A SuperAgent is a object storing all request data for client. type SuperAgent struct { Url string @@ -90,7 +102,7 @@ func New() *SuperAgent { debug := os.Getenv("GOREQUEST_DEBUG") == "1" s := &SuperAgent{ - TargetType: "json", + TargetType: TypeJSON, Data: make(map[string]interface{}), Header: make(map[string]string), RawString: "", @@ -143,7 +155,7 @@ func (s *SuperAgent) ClearSuperAgent() { s.BounceToRawString = false s.RawString = "" s.ForceType = "" - s.TargetType = "json" + s.TargetType = TypeJSON s.Cookies = make([]*http.Cookie, 0) s.Errors = nil } @@ -300,14 +312,14 @@ func (s *SuperAgent) AddCookies(cookies []*http.Cookie) *SuperAgent { } var Types = map[string]string{ - "html": "text/html", - "json": "application/json", - "xml": "application/xml", - "text": "text/plain", - "urlencoded": "application/x-www-form-urlencoded", - "form": "application/x-www-form-urlencoded", - "form-data": "application/x-www-form-urlencoded", - "multipart": "multipart/form-data", + TypeJSON: "application/json", + TypeXML: "application/xml", + TypeForm: "application/x-www-form-urlencoded", + TypeFormData: "application/x-www-form-urlencoded", + TypeUrlencoded: "application/x-www-form-urlencoded", + TypeHTML: "text/html", + TypeText: "text/plain", + TypeMultipart: "multipart/form-data", } // Type is a convenience function to specify the data type to send. @@ -684,7 +696,7 @@ func (s *SuperAgent) SendString(content string) *SuperAgent { } } } - s.TargetType = "form" + s.TargetType = TypeForm } else { s.BounceToRawString = true } @@ -1011,7 +1023,7 @@ func (s *SuperAgent) getResponseBytes() (Response, []byte, []error) { } // check if there is forced type switch s.ForceType { - case "json", "form", "xml", "text", "multipart": + case TypeJSON, TypeForm, TypeXML, TypeText, TypeMultipart: s.TargetType = s.ForceType // If forcetype is not set, check whether user set Content-Type header. // If yes, also bounce to the correct supported TargetType automatically. @@ -1111,7 +1123,8 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { // // https://github.com/parnurzeal/gorequest/pull/136 // - if s.TargetType == "json" { + switch s.TargetType { + case TypeJSON: // If-case to give support to json array. we check if // 1) Map only: send it as json map from s.Data // 2) Array or Mix of map & array or others: send it as rawstring from s.RawString @@ -1127,7 +1140,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { contentReader = bytes.NewReader(contentJson) contentType = "application/json" } - } else if s.TargetType == "form" || s.TargetType == "form-data" || s.TargetType == "urlencoded" { + case TypeForm, TypeFormData, TypeUrlencoded: var contentForm []byte if s.BounceToRawString || len(s.SliceData) != 0 { contentForm = []byte(s.RawString) @@ -1139,17 +1152,17 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { contentReader = bytes.NewReader(contentForm) contentType = "application/x-www-form-urlencoded" } - } else if s.TargetType == "text" { + case TypeText: if len(s.RawString) != 0 { contentReader = strings.NewReader(s.RawString) contentType = "text/plain" } - } else if s.TargetType == "xml" { + case TypeXML: if len(s.RawString) != 0 { contentReader = strings.NewReader(s.RawString) contentType = "application/xml" } - } else if s.TargetType == "multipart" { + case TypeMultipart: var ( buf = &bytes.Buffer{} mw = multipart.NewWriter(buf) @@ -1210,7 +1223,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { if contentReader != nil { contentType = mw.FormDataContentType() } - } else { + default: // let's return an error instead of an nil pointer exception here return nil, errors.New("TargetType '" + s.TargetType + "' could not be determined") } diff --git a/gorequest_test.go b/gorequest_test.go index 0d634be..be45f60 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -40,6 +40,76 @@ type ( } ) +// Test for type constants. +func TestTypeConstants(t *testing.T) { + if TypeJSON != "json" { + t.Errorf("Expected TypeJSON -> json | but got %s", TypeJSON) + } + + if TypeXML != "xml" { + t.Errorf("Expected TypeXML -> xml | but got %s", TypeXML) + } + + if TypeForm != "form" { + t.Errorf("Expected TypeForm -> form | but got %s", TypeForm) + } + + if TypeFormData != "form-data" { + t.Errorf("Expected TypeFormData -> form-data | but got %s", TypeFormData) + } + + if TypeUrlencoded != "urlencoded" { + t.Errorf("Expected TypeUrlencoded -> urlencoded | but got %s", TypeUrlencoded) + } + + if TypeHTML != "html" { + t.Errorf("Expected TypeHTML -> html | but got %s", TypeHTML) + } + + if TypeText != "text" { + t.Errorf("Expected TypeText -> text | but got %s", TypeText) + } + + if TypeMultipart != "multipart" { + t.Errorf("Expected TypeMultipart -> multipart | but got %s", TypeMultipart) + } +} + +// Test for Types map. +func TestTypesMap(t *testing.T) { + if Types[TypeJSON] != "application/json" { + t.Errorf(`Expected Types["json"] -> "application/json" | but got %s`, Types[TypeJSON]) + } + + if Types[TypeXML] != "application/xml" { + t.Errorf(`Expected Types["xml"] -> "applicaion/xml" | but got %s`, Types[TypeXML]) + } + + if Types[TypeForm] != "application/x-www-form-urlencoded" { + t.Errorf(`Expected Types["form"] -> "application/x-www-form-urlencoded" | but got %s`, Types[TypeForm]) + } + + if Types[TypeFormData] != "application/x-www-form-urlencoded" { + t.Errorf(`Expected Types["form-data"] -> "application/x-www-form-urlencoded" | but got %s`, Types[TypeFormData]) + } + + if Types[TypeUrlencoded] != "application/x-www-form-urlencoded" { + t.Errorf(`Expected Types["urlencoded"] -> "application/x-www-form-urlencoded" | but got %s`, Types[TypeUrlencoded]) + } + + if Types[TypeHTML] != "text/html" { + t.Errorf(`Expected Types["html"] -> "text/html" | but got %s`, Types[TypeHTML]) + } + + if Types[TypeText] != "text/plain" { + t.Errorf(`Expected Types["text"] -> "text/plain" | but got %s`, Types[TypeText]) + } + + if Types[TypeMultipart] != "multipart/form-data" { + t.Errorf(`Expected Types["multipart"] -> "multipart/form-data" | but got %s`, Types[TypeMultipart]) + } +} + // Test for changeMapToURLValues func TestChangeMapToURLValues(t *testing.T) { From 290a1fe2bd376b5f0162fc5c7c75e2cd2197192c Mon Sep 17 00:00:00 2001 From: Empijei Date: Sun, 1 Oct 2017 22:40:36 +0200 Subject: [PATCH 10/14] Added doc for DoNotClearSuperAgent --- gorequest.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gorequest.go b/gorequest.go index a0167e6..3072d11 100644 --- a/gorequest.go +++ b/gorequest.go @@ -88,6 +88,7 @@ type SuperAgent struct { Attempt int Enable bool } + //If true prevents clearing Superagent data and makes it possible to reuse it for the next requests DoNotClearSuperAgent bool } From 95984de35860e94540768e4b6c9282a344643bf7 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Mon, 9 Oct 2017 01:05:39 -0700 Subject: [PATCH 11/14] Don't infer header when set explicitly Amends https://github.com/parnurzeal/gorequest/pull/154 --- gorequest.go | 3 +- gorequest_test.go | 133 ++++++++++++++++------------------------------ 2 files changed, 48 insertions(+), 88 deletions(-) diff --git a/gorequest.go b/gorequest.go index 3072d11..9efca23 100644 --- a/gorequest.go +++ b/gorequest.go @@ -1268,8 +1268,7 @@ func (s *SuperAgent) MakeRequest() (*http.Request, error) { req.Host = vals[0] } } - - if len(contentType) != 0 { + if len(contentType) != 0 && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", contentType) } diff --git a/gorequest_test.go b/gorequest_test.go index 2be9ed8..7596244 100644 --- a/gorequest_test.go +++ b/gorequest_test.go @@ -1804,92 +1804,53 @@ func TestPlainText(t *testing.T) { End() } -// Test for force type to plain text even the request has specific Content-Type header. -func TestForceTypeToPlainText(t *testing.T) { - text := `hello world \r\n I am GoRequest` - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check method is PATCH before going to check other features - if r.Method != POST { - t.Errorf("Expected method %q; got %q", POST, r.Method) - } - if r.Header == nil { - t.Error("Expected non-nil request Header") - } - if r.Header.Get("Content-Type") != "text/plain" { - t.Error("Expected Header Content-Type -> text/plain", "| but got", r.Header.Get("Content-Type")) - } - - defer r.Body.Close() - body, _ := ioutil.ReadAll(r.Body) - if string(body) != text { - t.Error("Expected text ", text, "| but got", string(body)) - } - })) - - defer ts.Close() - - New().Post(ts.URL). - Set("Content-Type", "text/plain"). - Type("text"). - Send(text). - End() - - New().Post(ts.URL). - Set("Content-Type", "application/json"). - Type("text"). - Send(text). - End() - - New().Post(ts.URL). - Type("text"). - Set("Content-Type", "application/json"). - Send(text). - End() -} - -// Test for force type to JSON even the request has specific Content-Type header. -func TestForceTypeToJSON(t *testing.T) { - jsonData := `{"data":"hello world \r\n I am GoRequest"}` - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check method is PATCH before going to check other features - if r.Method != POST { - t.Errorf("Expected method %q; got %q", POST, r.Method) - } - if r.Header == nil { - t.Error("Expected non-nil request Header") - } - if r.Header.Get("Content-Type") != "application/json" { - t.Error("Expected Header Content-Type -> application/json", "| but got", r.Header.Get("Content-Type")) - } - - defer r.Body.Close() - body, _ := ioutil.ReadAll(r.Body) - if string(body) != jsonData { - t.Error("Expected JSON ", jsonData, "| but got", string(body)) - } - })) - - defer ts.Close() - - New().Post(ts.URL). - Set("Content-Type", "application/json"). - Type("json"). - Send(jsonData). - End() - - New().Post(ts.URL). - Set("Content-Type", "text/plain"). - Type("json"). - Send(jsonData). - End() - - New().Post(ts.URL). - Type("json"). - Set("Content-Type", "text/plain"). - Send(jsonData). - End() +func TestCustomHeaderTakePriorityOnTypeInference(t *testing.T) { + var tests = []struct { + name string + customHeader string + Type string + expectedHeader string + body string + }{ + {"same", "application/json", "json", "application/json", "{}"}, + {"emptybody", "", "json", "", ""}, + {"normal emptyjson", "", "json", "", "{}"}, + {"json_override", "text/json", "json", "text/json", "{}"}, + {"xml_override", "text/xml", "json", "text/xml", ""}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check method is PATCH before going to check other features + if r.Method != POST { + t.Errorf("Expected method %q; got %q", POST, r.Method) + } + if r.Header == nil { + t.Error("Expected non-nil request Header") + } + if r.Header.Get("Content-Type") != test.expectedHeader { + t.Errorf("Expected Header Content-Type -> %q | but got %q", test.expectedHeader, r.Header.Get("Content-Type")) + } + })) + defer ts.Close() + + New().Post(ts.URL). + Set("Content-Type", test.customHeader). + Type(test.Type). + Send(test.body). + End() + New().Post(ts.URL). + Set("cOnTent-tYpE", test.customHeader). + Type(test.Type). + Send(test.body). + End() + New().Post(ts.URL). + AppendHeader("Content-Type", test.customHeader). + Type(test.Type). + Send(test.body). + End() + }) + } } // Test for request can accept multiple types. From faf2e78ca3352c95e42ab2a299bcd024dc234b58 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Mon, 9 Oct 2017 16:23:39 -0700 Subject: [PATCH 12/14] Remove minitests as they don't work with "}, + {"application/json", "json", "application/json", "{}"}, + {"", "json", "", ""}, + {"", "json", "", "{}"}, + {"text/json", "json", "text/json", "{}"}, + {"text/xml", "json", "text/xml", ""}, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check method is PATCH before going to check other features - if r.Method != POST { - t.Errorf("Expected method %q; got %q", POST, r.Method) - } - if r.Header == nil { - t.Error("Expected non-nil request Header") - } - if r.Header.Get("Content-Type") != test.expectedHeader { - t.Errorf("Expected Header Content-Type -> %q | but got %q", test.expectedHeader, r.Header.Get("Content-Type")) - } - })) - defer ts.Close() - - New().Post(ts.URL). - Set("Content-Type", test.customHeader). - Type(test.Type). - Send(test.body). - End() - New().Post(ts.URL). - Set("cOnTent-tYpE", test.customHeader). - Type(test.Type). - Send(test.body). - End() - New().Post(ts.URL). - AppendHeader("Content-Type", test.customHeader). - Type(test.Type). - Send(test.body). - End() - }) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // check method is PATCH before going to check other features + if r.Method != POST { + t.Errorf("Expected method %q; got %q", POST, r.Method) + } + if r.Header == nil { + t.Error("Expected non-nil request Header") + } + if r.Header.Get("Content-Type") != test.expectedHeader { + t.Errorf("Expected Header Content-Type -> %q | but got %q", test.expectedHeader, r.Header.Get("Content-Type")) + } + })) + + New().Post(ts.URL). + Set("Content-Type", test.customContentType). + Type(test.Type). + Send(test.body). + End() + New().Post(ts.URL). + Set("cOnTent-tYpE", test.customContentType). + Type(test.Type). + Send(test.body). + End() + New().Post(ts.URL). + AppendHeader("Content-Type", test.customContentType). + Type(test.Type). + Send(test.body). + End() + ts.Close() } } From 2221d4204bcf968d3f70363085b6f916ed4b6198 Mon Sep 17 00:00:00 2001 From: "Daniel T. Morgan" Date: Thu, 12 Oct 2017 18:35:42 +0100 Subject: [PATCH 13/14] updating some of the English --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cc8211d..17fe733 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ GoRequest -- Simplified HTTP client ( inspired by famous SuperAgent lib in Node. #### "Shooting Requests like a Machine Gun" - Gopher -Sending request would never been fun and easier than this. It comes with lots of feature: +Sending request has never been as fun nor easier than this. It comes with lots of features: * Get/Post/Put/Head/Delete/Patch/Options * Set - simple header setting @@ -59,7 +59,7 @@ Or below if you don't want to reuse it for other requests. resp, body, errs := gorequest.New().Get("http://example.com/").End() ``` -How about getting control over HTTP client headers, redirect policy, and etc. Things is getting more complicated in golang. You need to create a Client, setting header in different command, ... to do just only one __GET__ +How about getting control over HTTP client headers, redirect policy, and etc. Things can quickly get more complicated in golang. You need to create a Client, set headers in a different command, ... just to do only one __GET__ ```go client := &http.Client{ @@ -72,7 +72,7 @@ req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req) ``` -Why making things ugly while you can just do as follows: +Why make things ugly while you can just do it as follows: ```go request := gorequest.New() @@ -82,7 +82,7 @@ resp, body, errs := request.Get("http://example.com"). End() ``` -__DELETE__, __HEAD__, __POST__, __PUT__, __PATCH__ are now supported and can be used the same way as __GET__: +__DELETE__, __HEAD__, __POST__, __PUT__, __PATCH__ are now supported and can be used in the same way as __GET__: ```go request := gorequest.New() @@ -95,7 +95,7 @@ resp, body, errs := request.Post("http://example.com").End() ### JSON -For a __JSON POST__ with standard libraries, you might need to marshal map data structure to json format, setting header to 'application/json' (and other headers if you need to) and declare http.Client. So, you code become longer and hard to maintain: +For a __JSON POST__ with standard libraries, you might need to marshal map data structure to json format, set headers to 'application/json' (and other headers if you need to) and declare http.Client. So, your code becomes longer and harder to maintain: ```go m := map[string]interface{}{ From 26d39af87c8b8528d2953537f20132c330d8e5a1 Mon Sep 17 00:00:00 2001 From: Anthony Hartanto Date: Tue, 7 Aug 2018 18:01:36 +0700 Subject: [PATCH 14/14] include timeout on retry --- gorequest.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gorequest.go b/gorequest.go index 8edb0fc..575082b 100644 --- a/gorequest.go +++ b/gorequest.go @@ -987,7 +987,7 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e if errs != nil { return nil, nil, errs } - if s.isRetryableRequest(resp) { + if s.isRetryableRequest(resp, errs) { resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt)) break } @@ -1000,8 +1000,20 @@ func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, e return resp, body, nil } -func (s *SuperAgent) isRetryableRequest(resp Response) bool { - if s.Retryable.Enable && s.Retryable.Attempt < s.Retryable.RetryerCount && contains(resp.StatusCode, s.Retryable.RetryableStatus) { +// isTimeout checks is it a net timeout error. +// Applicable only for go version >1.6 +func isTimeout(errs []error) bool { + for _, v := range errs { + if err, ok := v.(net.Error); ok && err.Timeout() { + return false + } + } + + return false +} + +func (s *SuperAgent) isRetryableRequest(resp Response, errs []error) bool { + if s.Retryable.Enable && s.Retryable.Attempt < s.Retryable.RetryerCount && (contains(resp.StatusCode, s.Retryable.RetryableStatus) || isTimeout(errs)) { time.Sleep(s.Retryable.RetryerTime) s.Retryable.Attempt++ return false