pax_global_header00006660000000000000000000000064130526225710014515gustar00rootroot0000000000000052 comment=a578a48e8d6ca8b01a3b18314c43c6716bb5f5a3 parnurzeal-gorequest-a578a48/000077500000000000000000000000001305262257100162575ustar00rootroot00000000000000parnurzeal-gorequest-a578a48/.gitignore000066400000000000000000000004071305262257100202500ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test .idea # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe tags parnurzeal-gorequest-a578a48/.travis.yml000066400000000000000000000003141305262257100203660ustar00rootroot00000000000000language: go go: - 1.7 - 1.6 - 1.5 - 1.4 - 1.3 - 1.2 install: - go get -t -v ./... notifications: email: recipients: parnurzeal@gmail.com on_success: change on_failure: always parnurzeal-gorequest-a578a48/CHANGELOG000066400000000000000000000062661305262257100175030ustar00rootroot00000000000000Changelog ========= v0.2.15 (2016-08-30) Features * Allow float and boolean in Query()'s queryStruct @davyzhang * Support Map in Query() @yangmls * Support Map in Send() @longlongh4 * Document RedirectPolicy @codegoalie * Enable Debug mode by ENV variable @heytitle * Add Retry() @xild Bug/Fixes * Allow bodies with all methods @pkopac * Change std "errors" pkg to "github.com/pkg/errors" @pkopac v0.2.14 (2016-08-30) Features * Support multipart @fraenky8 * Support OPTIONS request @coderhaoxin * Add EndStruct method @franciscocpg * Add AsCurlCommand @jaytaylor * Add CustomMethod @WaveCutz * Add MakeRequest @quangbuule Bug/Fixes * Disable keep alive by default v0.2.13 (2015-11-21) Features * Add DisableTransportSwap to stop gorequest from modifying Transport settings. Note that this will effect many functions that modify gorequest's tranport. (So, use with caution.) (Bug #47, PR #59 by @piotrmiskiewicz) v0.2.12 (2015-11-21) Features * Add SetCurlCommand for printing comparable CURL command of the request (PR #60 by @QuentinPerez) v0.2.11 (2015-10-24) Bug/Fixes * Add support to Slice data structure (Bug #40, #42) * Refactor sendStruct to be public function SendStruct v0.2.10 (2015-10-24) Bug/Fixes * Fix incorrect text output in tests (PR #52 by @QuentinPerez) * Fix Panic and runtime error properly (PR #53 by @QuentinPerez) * Add support for "text/plain" and "application/xml" (PR #51 by @smallnest) * Content-Type header is also equivalent with Type function to identify supported Gorequest's Target Type v0.2.9 (2015-08-16) Bug/Fixes * ParseQuery accepts ; as a synonym for &. thus Gorequest Query won't accept ; as in a query string. We add additional Param to solve this (PR #43 by @6david9) * AddCookies for bulk adding cookies (PR #46 by @pencil001) v0.2.8 (2015-08-10) Bug/Fixes * Added SetDebug and SetLogger for debug mode (PR #28 by @dafang) * Ensure the response Body is reusable (PR #37 by alaingilbert) v0.2.7 (2015-07-11) Bug/Fixes * Incorrectly reset "Authentication" header (Hot fix by @na-ga PR #38 & Issue #39) v0.2.6 (2015-07-10) Features * Added EndBytes (PR #30 by @jaytaylor) v0.2.5 (2015-07-01) Features * Added Basic Auth support (pull request #24 by @dickeyxxx) Bug/Fixes * Fix #31 incorrect number conversion (PR #34 by @killix) v0.2.4 (2015-04-13) Features * Query() now supports Struct as same as Send() (pull request #25 by @figlief) v0.2.3 (2015-02-08) Features * Added Patch HTTP Method Bug/Fixes * Refactored testing code v0.2.2 (2015-01-03) Features * Added TLSClientConfig for better control over tls * Added AddCookie func to set "Cookie" field in request (pull request #17 by @austinov) - Issue #7 * Added CookieJar (pull request #15 by @kemadz) v0.2.1 (2014-07-06) Features * Implemented timeout test Bugs/Fixes * Improved timeout feature by control over both dial + read/write timeout compared to previously controlling only dial connection timeout. v0.2.0 (2014-06-13) - Send is now supporting Struct type as a parameter v0.1.0 (2014-04-14) - Finished release with enough rich functions to do get, post, json and redirectpolicy parnurzeal-gorequest-a578a48/CONTRIBUTING.md000066400000000000000000000053631305262257100205170ustar00rootroot00000000000000# Contributing to GoRequest Thanks for taking the time to contribute!! GoRequest welcomes any kind of contributions including documentation, bug reports, issues, feature requests, feature implementations, pull requests, helping to manage and answer issues, etc. ### Code Guidelines To make the contribution process as seamless as possible, we ask for the following: * Go ahead and fork the project and make your changes. We encourage pull requests to allow for review and discussion of code changes. * When you’re ready to create a pull request, be sure to: * Have test cases for the new code. * Follow [GoDoc](https://blog.golang.org/godoc-documenting-go-code) guideline and always add documentation for new function/variable definitions. * Run `go fmt`. * Additonally, add documentation to README.md if you are adding new features or changing functionality. * Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`. * Make sure `go test ./...` passes, and `go build` completes. * Follow the **Git Commit Message Guidelines** below. ### Writing Commit Message Follow this [blog article](http://chris.beams.io/posts/git-commit/). It is a good resource for learning how to write good commit messages, the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period: *"Return error when sending incorrect JSON format"*, **NOT** *"returning some error."* Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*. Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*. ### Sending a Pull Request Due to the way Go handles package imports, the best approach for working on a fork is to use Git Remotes. You can follow the instructions below: 1. Get the latest sources: ``` go get -u -t github.com/parnurzeal/gorequest/... ``` 1. Change to the GoRequest source directory: ``` cd $GOPATH/src/github.com/parnurzeal/gorequest ``` 1. Create a new branch for your changes (the branch name is arbitrary): ``` git checkout -b issue_1234 ``` 1. After making your changes, commit them to your new branch: ``` git commit -a -v ``` 1. Fork GoRequest in Github. 1. Add your fork as a new remote (the remote name, "fork" in this example, is arbitrary): ``` git remote add fork git://github.com/USERNAME/gorequest.git ``` 1. Push the changes to your new remote: ``` git push --set-upstream fork issue_1234 ``` 1. You're now ready to submit a PR based upon the new branch in your forked repository. parnurzeal-gorequest-a578a48/LICENSE000066400000000000000000000021021305262257100172570ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Theeraphol Wattanavekin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. parnurzeal-gorequest-a578a48/README.md000066400000000000000000000246041305262257100175440ustar00rootroot00000000000000GoRequest ========= GoRequest -- Simplified HTTP client ( inspired by famous SuperAgent lib in Node.js ) ![GopherGoRequest](https://raw.githubusercontent.com/parnurzeal/gorequest/gh-pages/images/Gopher_GoRequest_400x300.jpg) #### "Shooting Requests like a Machine Gun" - Gopher Sending request would never been fun and easier than this. It comes with lots of feature: * Get/Post/Put/Head/Delete/Patch/Options * Set - simple header setting * JSON - made it simple with JSON string as a parameter * Multipart-Support - send data and files as multipart request * Proxy - sending request via proxy * Timeout - setting timeout for a request * TLSClientConfig - taking control over tls where at least you can disable security check for https * RedirectPolicy * Cookie - setting cookies for your request * CookieJar - automatic in-memory cookiejar * BasicAuth - setting basic authentication header * more to come.. ## Installation ```bash $ go get github.com/parnurzeal/gorequest ``` ## Documentation See [Go Doc](http://godoc.org/github.com/parnurzeal/gorequest) or [Go Walker](http://gowalker.org/github.com/parnurzeal/gorequest) for usage and details. ## Status [![Drone Build Status](https://drone.io/github.com/jmcvetta/restclient/status.png)](https://drone.io/github.com/parnurzeal/gorequest/latest) [![Travis Build Status](https://travis-ci.org/parnurzeal/gorequest.svg?branch=master)](https://travis-ci.org/parnurzeal/gorequest) ## Why should you use GoRequest? GoRequest makes thing much more simple for you, making http client more awesome and fun like SuperAgent + golang style usage. This is what you normally do for a simple GET without GoRequest: ```go resp, err := http.Get("http://example.com/") ``` With GoRequest: ```go request := gorequest.New() resp, body, errs := request.Get("http://example.com/").End() ``` Or below if you don't want to reuse it for other requests. ```go 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__ ```go client := &http.Client{ CheckRedirect: redirectPolicyFunc, } req, err := http.NewRequest("GET", "http://example.com", nil) req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req) ``` Why making things ugly while you can just do as follows: ```go request := gorequest.New() resp, body, errs := request.Get("http://example.com"). RedirectPolicy(redirectPolicyFunc). Set("If-None-Match", `W/"wyzzy"`). End() ``` __DELETE__, __HEAD__, __POST__, __PUT__, __PATCH__ are now supported and can be used the same way as __GET__: ```go request := gorequest.New() resp, body, errs := request.Post("http://example.com").End() // PUT -> request.Put("http://example.com").End() // DELETE -> request.Delete("http://example.com").End() // HEAD -> request.Head("http://example.com").End() // ANYTHING -> request.CustomMethod("TRACE", "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: ```go m := map[string]interface{}{ "name": "backy", "species": "dog", } mJson, _ := json.Marshal(m) contentReader := bytes.NewReader(mJson) req, _ := http.NewRequest("POST", "http://example.com", contentReader) req.Header.Set("Content-Type", "application/json") req.Header.Set("Notes","GoRequest is coming!") client := &http.Client{} resp, _ := client.Do(req) ``` Compared to our GoRequest version, JSON is for sure a default. So, it turns out to be just one simple line!: ```go request := gorequest.New() resp, body, errs := request.Post("http://example.com"). Set("Notes","gorequst is coming!"). Send(`{"name":"backy", "species":"dog"}`). End() ``` Moreover, it also supports struct type. So, you can have a fun __Mix & Match__ sending the different data types for your request: ```go type BrowserVersionSupport struct { Chrome string Firefox string } ver := BrowserVersionSupport{ Chrome: "37.0.2041.6", Firefox: "30.0" } request := gorequest.New() resp, body, errs := request.Post("http://version.com/update"). Send(ver). Send(`{"Safari":"5.1.10"}`). End() ``` Not only for Send() but Query() is also supported. Just give it a try! :) ## Callback Moreover, GoRequest also supports callback function. This gives you much more flexibility on using it. You can use it any way to match your own style! Let's see a bit of callback example: ```go func printStatus(resp gorequest.Response, body string, errs []error){ fmt.Println(resp.Status) } gorequest.New().Get("http://example.com").End(printStatus) ``` ## Multipart/Form-Data You can specify the content-type of the request to type `multipart` to send all data as `multipart/form-data`. This feature also allows you to send (multiple) files! Check the examples below! ```go gorequest.New().Post("http://example.com/"). Type("multipart"). Send(`{"query1":"test"}`). End() ``` The `SendFile` function accepts `strings` as path to a file, `[]byte` slice or even a `os.File`! You can also combine them to send multiple files with either custom name and/or custom fieldname: ```go f, _ := filepath.Abs("./file2.txt") bytesOfFile, _ := ioutil.ReadFile(f) gorequest.New().Post("http://example.com/"). Type("multipart"). SendFile("./file1.txt"). SendFile(bytesOfFile, "file2.txt", "my_file_fieldname"). End() ``` Check the docs for `SendFile` to get more information about the types of arguments. ## Proxy In the case when you are behind proxy, GoRequest can handle it easily with Proxy func: ```go request := gorequest.New().Proxy("http://proxy:999") resp, body, errs := request.Get("http://example-proxy.com").End() // To reuse same client with no_proxy, use empty string: resp, body, errs = request.Proxy("").Get("http://example-no-proxy.com").End() ``` ## Basic Authentication To add a basic authentication header: ```go request := gorequest.New().SetBasicAuth("username", "password") resp, body, errs := request.Get("http://example-proxy.com").End() ``` ## Timeout Timeout can be set in any time duration using time package: ```go request := gorequest.New().Timeout(2*time.Millisecond) resp, body, errs:= request.Get("http://example.com").End() ``` Timeout func defines both dial + read/write timeout to the specified time parameter. ## EndBytes Thanks to @jaytaylor, we now have EndBytes to use when you want the body as bytes. The callbacks work the same way as with `End`, except that a byte array is used instead of a string. ```go resp, bodyBytes, errs := gorequest.New().Get("http://example.com/").EndBytes() ``` ## EndStruct We now have EndStruct to use when you want the body as struct. The callbacks work the same way as with `End`, except that a struct is used instead of a string. Supposing the URL **http://example.com/** returns the body `{"hey":"you"}` ```go heyYou struct { Hey string `json:"hey"` } var heyYou heyYou resp, _, errs := gorequest.New().Get("http://example.com/").EndStruct(&heyYou) ``` ## Retry Supposing you need retry 3 times, with 5 seconds between each attempt when gets a BadRequest or a InternalServerError ```go request := gorequest.New() resp, body, errs := request.Get("http://example.com/"). Retry(3, 5 * time.seconds, http.StatusBadRequest, http.StatusInternalServerError). End() ``` ## Handling Redirects Redirects can be handled with RedirectPolicy which behaves similarly to net/http Client's [CheckRedirect function](https://golang.org/pkg/net/http#Client). Simply specify a function which takes the Request about to be made and a slice of previous Requests in order of oldest first. When this function returns an error, the Request is not made. For example to redirect only to https endpoints: ```go request := gorequest.New() resp, body, errs := request.Get("http://example.com/"). RedirectPolicy(func(req Request, via []*Request) error { if req.URL.Scheme != "https" { return http.ErrUseLastResponse } }). End() ``` ## Debug For debugging, GoRequest leverages `httputil` to dump details of every request/response. (Thanks to @dafang) You can just use `SetDebug` or environment variable `GOREQUEST_DEBUG=0|1` to enable/disable debug mode and `SetLogger` to set your own choice of logger. Thanks to @QuentinPerez, we can see even how gorequest is compared to CURL by using `SetCurlCommand`. ## Noted As the underlying gorequest is based on http.Client in most use cases, gorequest.New() should be called once and reuse gorequest as much as possible. ## Contributing to GoRequest: If you find any improvement or issue you want to fix, feel free to send me a pull request with testing. Thanks to all contributors thus far: | Contributors | |---------------------------------------| | https://github.com/alaingilbert | | https://github.com/austinov | | https://github.com/coderhaoxin | | https://github.com/codegoalie | | https://github.com/dafang | | https://github.com/davyzhang | | https://github.com/dickeyxxx | | https://github.com/figlief | | https://github.com/fraenky8 | | https://github.com/franciscocpg | | https://github.com/heytitle | | https://github.com/hownowstephen | | https://github.com/kemadz | | https://github.com/killix | | https://github.com/jaytaylor | | https://github.com/na-ga | | https://github.com/piotrmiskiewicz | | https://github.com/pencil001 | | https://github.com/pkopac | | https://github.com/quangbuule | | https://github.com/QuentinPerez | | https://github.com/smallnest | | https://github.com/WaveCutz | | https://github.com/xild | | https://github.com/yangmls | | https://github.com/6david9 | Also, co-maintainer is needed here. If anyone is interested, please email me (parnurzeal at gmail.com) ## Credits * Renee French - the creator of Gopher mascot * [Wisi Mongkhonsrisawat](https://www.facebook.com/puairw) for providing an awesome GoRequest's Gopher image :) ## License GoRequest is MIT License. parnurzeal-gorequest-a578a48/gorequest.go000066400000000000000000001057641305262257100206410ustar00rootroot00000000000000// Package gorequest inspired by Nodejs SuperAgent provides easy-way to write http client package gorequest import ( "bytes" "crypto/tls" "encoding/json" "io/ioutil" "log" "net" "net/http" "net/http/cookiejar" "net/http/httputil" "net/url" "os" "reflect" "strconv" "strings" "time" "github.com/pkg/errors" "mime/multipart" "net/textproto" "fmt" "path/filepath" "github.com/moul/http2curl" "golang.org/x/net/publicsuffix" ) type Request *http.Request type Response *http.Response // HTTP methods we support const ( POST = "POST" GET = "GET" HEAD = "HEAD" PUT = "PUT" DELETE = "DELETE" PATCH = "PATCH" OPTIONS = "OPTIONS" ) // A SuperAgent is a object storing all request data for client. type SuperAgent struct { Url string Method string Header map[string]string TargetType string ForceType string Data map[string]interface{} SliceData []interface{} FormData url.Values QueryData url.Values FileData []File BounceToRawString bool RawString string Client *http.Client Transport *http.Transport Cookies []*http.Cookie Errors []error BasicAuth struct{ Username, Password string } Debug bool CurlCommand bool logger *log.Logger Retryable struct { RetryableStatus []int RetryerTime time.Duration RetryerCount int Attempt int Enable bool } } var DisableTransportSwap = false // Used to create a new SuperAgent object. func New() *SuperAgent { cookiejarOptions := cookiejar.Options{ PublicSuffixList: publicsuffix.List, } jar, _ := cookiejar.New(&cookiejarOptions) debug := os.Getenv("GOREQUEST_DEBUG") == "1" s := &SuperAgent{ TargetType: "json", Data: make(map[string]interface{}), Header: make(map[string]string), RawString: "", SliceData: []interface{}{}, FormData: url.Values{}, QueryData: url.Values{}, FileData: make([]File, 0), BounceToRawString: false, Client: &http.Client{Jar: jar}, Transport: &http.Transport{}, Cookies: make([]*http.Cookie, 0), Errors: nil, BasicAuth: struct{ Username, Password string }{}, Debug: debug, CurlCommand: false, logger: log.New(os.Stderr, "[gorequest]", log.LstdFlags), } // disable keep alives by default, see this issue https://github.com/parnurzeal/gorequest/issues/75 s.Transport.DisableKeepAlives = true return s } // Enable the debug mode which logs request/response detail func (s *SuperAgent) SetDebug(enable bool) *SuperAgent { s.Debug = enable return s } // Enable the curlcommand mode which display a CURL command line func (s *SuperAgent) SetCurlCommand(enable bool) *SuperAgent { s.CurlCommand = enable return s } func (s *SuperAgent) SetLogger(logger *log.Logger) *SuperAgent { s.logger = logger return s } // Clear SuperAgent data for another new request. func (s *SuperAgent) ClearSuperAgent() { s.Url = "" s.Method = "" s.Header = make(map[string]string) s.Data = make(map[string]interface{}) s.SliceData = []interface{}{} s.FormData = url.Values{} s.QueryData = url.Values{} s.FileData = make([]File, 0) s.BounceToRawString = false s.RawString = "" s.ForceType = "" s.TargetType = "json" s.Cookies = make([]*http.Cookie, 0) s.Errors = nil } // Just a wrapper to initialize SuperAgent instance by method string func (s *SuperAgent) CustomMethod(method, targetUrl string) *SuperAgent { switch method { case POST: return s.Post(targetUrl) case GET: return s.Get(targetUrl) case HEAD: return s.Head(targetUrl) case PUT: return s.Put(targetUrl) case DELETE: return s.Delete(targetUrl) case PATCH: return s.Patch(targetUrl) case OPTIONS: return s.Options(targetUrl) default: s.ClearSuperAgent() s.Method = method s.Url = targetUrl s.Errors = nil return s } } func (s *SuperAgent) Get(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = GET s.Url = targetUrl s.Errors = nil return s } func (s *SuperAgent) Post(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = POST s.Url = targetUrl s.Errors = nil return s } func (s *SuperAgent) Head(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = HEAD s.Url = targetUrl s.Errors = nil return s } func (s *SuperAgent) Put(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = PUT s.Url = targetUrl s.Errors = nil return s } func (s *SuperAgent) Delete(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = DELETE s.Url = targetUrl s.Errors = nil return s } func (s *SuperAgent) Patch(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = PATCH s.Url = targetUrl s.Errors = nil return s } func (s *SuperAgent) Options(targetUrl string) *SuperAgent { s.ClearSuperAgent() s.Method = OPTIONS s.Url = targetUrl s.Errors = nil return s } // Set is used for setting header fields. // Example. To set `Accept` as `application/json` // // gorequest.New(). // Post("/gamelist"). // Set("Accept", "application/json"). // End() func (s *SuperAgent) Set(param string, value string) *SuperAgent { s.Header[param] = value return s } // Retryable is used for setting a Retryer policy // Example. To set Retryer policy with 5 seconds between each attempt. // 3 max attempt. // And StatusBadRequest and StatusInternalServerError as RetryableStatus // gorequest.New(). // Post("/gamelist"). // Retry(3, 5 * time.seconds, http.StatusBadRequest, http.StatusInternalServerError). // End() func (s *SuperAgent) Retry(retryerCount int, retryerTime time.Duration, statusCode ...int) *SuperAgent { for _, code := range statusCode { statusText := http.StatusText(code) if len(statusText) == 0 { s.Errors = append(s.Errors, errors.New("StatusCode '"+strconv.Itoa(code)+"' doesn't exist in http package")) } } s.Retryable = struct { RetryableStatus []int RetryerTime time.Duration RetryerCount int Attempt int Enable bool }{ statusCode, retryerTime, retryerCount, 0, true, } return s } // SetBasicAuth sets the basic authentication header // Example. To set the header for username "myuser" and password "mypass" // // gorequest.New() // Post("/gamelist"). // SetBasicAuth("myuser", "mypass"). // End() func (s *SuperAgent) SetBasicAuth(username string, password string) *SuperAgent { s.BasicAuth = struct{ Username, Password string }{username, password} return s } // AddCookie adds a cookie to the request. The behavior is the same as AddCookie on Request from net/http func (s *SuperAgent) AddCookie(c *http.Cookie) *SuperAgent { s.Cookies = append(s.Cookies, c) return s } // AddCookies is a convenient method to add multiple cookies func (s *SuperAgent) AddCookies(cookies []*http.Cookie) *SuperAgent { s.Cookies = append(s.Cookies, cookies...) return s } 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", } // Type is a convenience function to specify the data type to send. // For example, to send data as `application/x-www-form-urlencoded` : // // gorequest.New(). // Post("/recipe"). // Type("form"). // Send(`{ "name": "egg benedict", "category": "brunch" }`). // End() // // This will POST the body "name=egg benedict&category=brunch" to url /recipe // // GoRequest supports // // "text/html" uses "html" // "application/json" uses "json" // "application/xml" uses "xml" // "text/plain" uses "text" // "application/x-www-form-urlencoded" uses "urlencoded", "form" or "form-data" // func (s *SuperAgent) Type(typeStr string) *SuperAgent { if _, ok := Types[typeStr]; ok { s.ForceType = typeStr } else { s.Errors = append(s.Errors, errors.New("Type func: incorrect type \""+typeStr+"\"")) } return s } // Query function accepts either json string or strings which will form a query-string in url of GET method or body of POST method. // For example, making "/search?query=bicycle&size=50x50&weight=20kg" using GET method: // // gorequest.New(). // Get("/search"). // Query(`{ query: 'bicycle' }`). // Query(`{ size: '50x50' }`). // Query(`{ weight: '20kg' }`). // End() // // Or you can put multiple json values: // // gorequest.New(). // Get("/search"). // Query(`{ query: 'bicycle', size: '50x50', weight: '20kg' }`). // End() // // Strings are also acceptable: // // gorequest.New(). // Get("/search"). // Query("query=bicycle&size=50x50"). // Query("weight=20kg"). // End() // // Or even Mixed! :) // // gorequest.New(). // Get("/search"). // Query("query=bicycle"). // Query(`{ size: '50x50', weight:'20kg' }`). // End() // func (s *SuperAgent) Query(content interface{}) *SuperAgent { switch v := reflect.ValueOf(content); v.Kind() { case reflect.String: s.queryString(v.String()) case reflect.Struct: s.queryStruct(v.Interface()) case reflect.Map: s.queryMap(v.Interface()) default: } return s } func (s *SuperAgent) queryStruct(content interface{}) *SuperAgent { if marshalContent, err := json.Marshal(content); err != nil { s.Errors = append(s.Errors, err) } else { var val map[string]interface{} if err := json.Unmarshal(marshalContent, &val); err != nil { s.Errors = append(s.Errors, err) } else { for k, v := range val { k = strings.ToLower(k) var queryVal string switch t := v.(type) { case string: queryVal = t case float64: queryVal = strconv.FormatFloat(t, 'f', -1, 64) case time.Time: queryVal = t.Format(time.RFC3339) default: j, err := json.Marshal(v) if err != nil { continue } queryVal = string(j) } s.QueryData.Add(k, queryVal) } } } return s } func (s *SuperAgent) queryString(content string) *SuperAgent { var val map[string]string if err := json.Unmarshal([]byte(content), &val); err == nil { for k, v := range val { s.QueryData.Add(k, v) } } else { if queryData, err := url.ParseQuery(content); err == nil { for k, queryValues := range queryData { for _, queryValue := range queryValues { s.QueryData.Add(k, string(queryValue)) } } } else { s.Errors = append(s.Errors, err) } // TODO: need to check correct format of 'field=val&field=val&...' } return s } func (s *SuperAgent) queryMap(content interface{}) *SuperAgent { return s.queryStruct(content) } // As Go conventions accepts ; as a synonym for &. (https://github.com/golang/go/issues/2210) // Thus, Query won't accept ; in a querystring if we provide something like fields=f1;f2;f3 // This Param is then created as an alternative method to solve this. func (s *SuperAgent) Param(key string, value string) *SuperAgent { s.QueryData.Add(key, value) return s } func (s *SuperAgent) Timeout(timeout time.Duration) *SuperAgent { s.Transport.Dial = func(network, addr string) (net.Conn, error) { conn, err := net.DialTimeout(network, addr, timeout) if err != nil { s.Errors = append(s.Errors, err) return nil, err } conn.SetDeadline(time.Now().Add(timeout)) return conn, nil } return s } // Set TLSClientConfig for underling Transport. // One example is you can use it to disable security check (https): // // gorequest.New().TLSClientConfig(&tls.Config{ InsecureSkipVerify: true}). // Get("https://disable-security-check.com"). // End() // func (s *SuperAgent) TLSClientConfig(config *tls.Config) *SuperAgent { s.Transport.TLSClientConfig = config return s } // Proxy function accepts a proxy url string to setup proxy url for any request. // It provides a convenience way to setup proxy which have advantages over usual old ways. // One example is you might try to set `http_proxy` environment. This means you are setting proxy up for all the requests. // You will not be able to send different request with different proxy unless you change your `http_proxy` environment again. // Another example is using Golang proxy setting. This is normal prefer way to do but too verbase compared to GoRequest's Proxy: // // gorequest.New().Proxy("http://myproxy:9999"). // Post("http://www.google.com"). // End() // // To set no_proxy, just put empty string to Proxy func: // // gorequest.New().Proxy(""). // Post("http://www.google.com"). // End() // func (s *SuperAgent) Proxy(proxyUrl string) *SuperAgent { parsedProxyUrl, err := url.Parse(proxyUrl) if err != nil { s.Errors = append(s.Errors, err) } else if proxyUrl == "" { s.Transport.Proxy = nil } else { s.Transport.Proxy = http.ProxyURL(parsedProxyUrl) } return s } // RedirectPolicy accepts a function to define how to handle redirects. If the // policy function returns an error, the next Request is not made and the previous // request is returned. // // The policy function's arguments are the Request about to be made and the // past requests in order of oldest first. func (s *SuperAgent) RedirectPolicy(policy func(req Request, via []Request) error) *SuperAgent { s.Client.CheckRedirect = func(r *http.Request, v []*http.Request) error { vv := make([]Request, len(v)) for i, r := range v { vv[i] = Request(r) } return policy(Request(r), vv) } return s } // Send function accepts either json string or query strings which is usually used to assign data to POST or PUT method. // Without specifying any type, if you give Send with json data, you are doing requesting in json format: // // gorequest.New(). // Post("/search"). // Send(`{ query: 'sushi' }`). // End() // // While if you use at least one of querystring, GoRequest understands and automatically set the Content-Type to `application/x-www-form-urlencoded` // // gorequest.New(). // Post("/search"). // Send("query=tonkatsu"). // End() // // So, if you want to strictly send json format, you need to use Type func to set it as `json` (Please see more details in Type function). // You can also do multiple chain of Send: // // gorequest.New(). // Post("/search"). // Send("query=bicycle&size=50x50"). // Send(`{ wheel: '4'}`). // End() // // From v0.2.0, Send function provide another convenience way to work with Struct type. You can mix and match it with json and query string: // // type BrowserVersionSupport struct { // Chrome string // Firefox string // } // ver := BrowserVersionSupport{ Chrome: "37.0.2041.6", Firefox: "30.0" } // gorequest.New(). // Post("/update_version"). // Send(ver). // Send(`{"Safari":"5.1.10"}`). // End() // // If you have set Type to text or Content-Type to text/plain, content will be sent as raw string in body instead of form // // gorequest.New(). // Post("/greet"). // Type("text"). // Send("hello world"). // End() // func (s *SuperAgent) Send(content interface{}) *SuperAgent { // TODO: add normal text mode or other mode to Send func switch v := reflect.ValueOf(content); v.Kind() { case reflect.String: s.SendString(v.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // includes rune s.SendString(strconv.FormatInt(v.Int(), 10)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // includes byte s.SendString(strconv.FormatUint(v.Uint(), 10)) case reflect.Float64: s.SendString(strconv.FormatFloat(v.Float(), 'f', -1, 64)) case reflect.Float32: s.SendString(strconv.FormatFloat(v.Float(), 'f', -1, 32)) case reflect.Bool: s.SendString(strconv.FormatBool(v.Bool())) case reflect.Struct: s.SendStruct(v.Interface()) case reflect.Slice: s.SendSlice(makeSliceOfReflectValue(v)) case reflect.Array: s.SendSlice(makeSliceOfReflectValue(v)) case reflect.Ptr: s.Send(v.Elem().Interface()) case reflect.Map: s.SendMap(v.Interface()) default: // TODO: leave default for handling other types in the future, such as complex numbers, (nested) maps, etc return s } return s } func makeSliceOfReflectValue(v reflect.Value) (slice []interface{}) { kind := v.Kind() if kind != reflect.Slice && kind != reflect.Array { return slice } slice = make([]interface{}, v.Len()) for i := 0; i < v.Len(); i++ { slice[i] = v.Index(i).Interface() } return slice } // SendSlice (similar to SendString) returns SuperAgent's itself for any next chain and takes content []interface{} as a parameter. // Its duty is to append slice of interface{} into s.SliceData ([]interface{}) which later changes into json array in the End() func. func (s *SuperAgent) SendSlice(content []interface{}) *SuperAgent { s.SliceData = append(s.SliceData, content...) return s } func (s *SuperAgent) SendMap(content interface{}) *SuperAgent { return s.SendStruct(content) } // SendStruct (similar to SendString) returns SuperAgent's itself for any next chain and takes content interface{} as a parameter. // Its duty is to transfrom interface{} (implicitly always a struct) into s.Data (map[string]interface{}) which later changes into appropriate format such as json, form, text, etc. in the End() func. func (s *SuperAgent) SendStruct(content interface{}) *SuperAgent { if marshalContent, err := json.Marshal(content); err != nil { s.Errors = append(s.Errors, err) } else { var val map[string]interface{} d := json.NewDecoder(bytes.NewBuffer(marshalContent)) d.UseNumber() if err := d.Decode(&val); err != nil { s.Errors = append(s.Errors, err) } else { for k, v := range val { s.Data[k] = v } } } return s } // SendString returns SuperAgent's itself for any next chain and takes content string as a parameter. // Its duty is to transform String into s.Data (map[string]interface{}) which later changes into appropriate format such as json, form, text, etc. in the End func. // Send implicitly uses SendString and you should use Send instead of this. func (s *SuperAgent) SendString(content string) *SuperAgent { if !s.BounceToRawString { var val interface{} d := json.NewDecoder(strings.NewReader(content)) d.UseNumber() if err := d.Decode(&val); err == nil { switch v := reflect.ValueOf(val); v.Kind() { case reflect.Map: for k, v := range val.(map[string]interface{}) { s.Data[k] = v } // add to SliceData case reflect.Slice: s.SendSlice(val.([]interface{})) // bounce to rawstring if it is arrayjson, or others default: s.BounceToRawString = true } } else if formData, err := url.ParseQuery(content); err == nil { for k, formValues := range formData { for _, formValue := range formValues { // make it array if already have key if val, ok := s.Data[k]; ok { var strArray []string strArray = append(strArray, string(formValue)) // check if previous data is one string or array switch oldValue := val.(type) { case []string: strArray = append(strArray, oldValue...) case string: strArray = append(strArray, oldValue) } s.Data[k] = strArray } else { // make it just string if does not already have same key s.Data[k] = formValue } } } s.TargetType = "form" } else { s.BounceToRawString = true } } // Dump all contents to RawString in case in the end user doesn't want json or form. s.RawString += content return s } type File struct { Filename string Fieldname string Data []byte } // SendFile function works only with type "multipart". The function accepts one mandatory and up to two optional arguments. The mandatory (first) argument is the file. // The function accepts a path to a file as string: // // gorequest.New(). // Post("http://example.com"). // Type("multipart"). // SendFile("./example_file.ext"). // End() // // File can also be a []byte slice of a already file read by eg. ioutil.ReadFile: // // b, _ := ioutil.ReadFile("./example_file.ext") // gorequest.New(). // Post("http://example.com"). // Type("multipart"). // SendFile(b). // End() // // Furthermore file can also be a os.File: // // f, _ := os.Open("./example_file.ext") // gorequest.New(). // Post("http://example.com"). // Type("multipart"). // SendFile(f). // End() // // The first optional argument (second argument overall) is the filename, which will be automatically determined when file is a string (path) or a os.File. // When file is a []byte slice, filename defaults to "filename". In all cases the automatically determined filename can be overwritten: // // b, _ := ioutil.ReadFile("./example_file.ext") // gorequest.New(). // Post("http://example.com"). // Type("multipart"). // SendFile(b, "my_custom_filename"). // End() // // The second optional argument (third argument overall) is the fieldname in the multipart/form-data request. It defaults to fileNUMBER (eg. file1), where number is ascending and starts counting at 1. // So if you send multiple files, the fieldnames will be file1, file2, ... unless it is overwritten. If fieldname is set to "file" it will be automatically set to fileNUMBER, where number is the greatest exsiting number+1. // // b, _ := ioutil.ReadFile("./example_file.ext") // gorequest.New(). // Post("http://example.com"). // Type("multipart"). // SendFile(b, "", "my_custom_fieldname"). // filename left blank, will become "example_file.ext" // End() // func (s *SuperAgent) SendFile(file interface{}, args ...string) *SuperAgent { filename := "" fieldname := "file" if len(args) >= 1 && len(args[0]) > 0 { filename = strings.TrimSpace(args[0]) } if len(args) >= 2 && len(args[1]) > 0 { fieldname = strings.TrimSpace(args[1]) } if fieldname == "file" || fieldname == "" { fieldname = "file" + strconv.Itoa(len(s.FileData)+1) } switch v := reflect.ValueOf(file); v.Kind() { case reflect.String: pathToFile, err := filepath.Abs(v.String()) if err != nil { s.Errors = append(s.Errors, err) return s } if filename == "" { filename = filepath.Base(pathToFile) } data, err := ioutil.ReadFile(v.String()) if err != nil { s.Errors = append(s.Errors, err) return s } s.FileData = append(s.FileData, File{ Filename: filename, Fieldname: fieldname, Data: data, }) case reflect.Slice: slice := makeSliceOfReflectValue(v) if filename == "" { filename = "filename" } f := File{ Filename: filename, Fieldname: fieldname, Data: make([]byte, len(slice)), } for i := range slice { f.Data[i] = slice[i].(byte) } s.FileData = append(s.FileData, f) case reflect.Ptr: if len(args) == 1 { return s.SendFile(v.Elem().Interface(), args[0]) } if len(args) >= 2 { return s.SendFile(v.Elem().Interface(), args[0], args[1]) } return s.SendFile(v.Elem().Interface()) default: if v.Type() == reflect.TypeOf(os.File{}) { osfile := v.Interface().(os.File) if filename == "" { filename = filepath.Base(osfile.Name()) } data, err := ioutil.ReadFile(osfile.Name()) if err != nil { s.Errors = append(s.Errors, err) return s } s.FileData = append(s.FileData, File{ Filename: filename, Fieldname: fieldname, Data: data, }) return s } s.Errors = append(s.Errors, errors.New("SendFile currently only supports either a string (path/to/file), a slice of bytes (file content itself), or a os.File!")) } return s } func changeMapToURLValues(data map[string]interface{}) url.Values { var newUrlValues = url.Values{} for k, v := range data { switch val := v.(type) { case string: newUrlValues.Add(k, val) case bool: newUrlValues.Add(k, strconv.FormatBool(val)) // if a number, change to string // json.Number used to protect against a wrong (for GoRequest) default conversion // which always converts number to float64. // This type is caused by using Decoder.UseNumber() case json.Number: newUrlValues.Add(k, string(val)) case int: newUrlValues.Add(k, strconv.FormatInt(int64(val), 10)) // TODO add all other int-Types (int8, int16, ...) case float64: newUrlValues.Add(k, strconv.FormatFloat(float64(val), 'f', -1, 64)) case float32: newUrlValues.Add(k, strconv.FormatFloat(float64(val), 'f', -1, 64)) // following slices are mostly needed for tests case []string: for _, element := range val { newUrlValues.Add(k, element) } case []int: for _, element := range val { newUrlValues.Add(k, strconv.FormatInt(int64(element), 10)) } case []bool: for _, element := range val { newUrlValues.Add(k, strconv.FormatBool(element)) } case []float64: for _, element := range val { newUrlValues.Add(k, strconv.FormatFloat(float64(element), 'f', -1, 64)) } case []float32: for _, element := range val { newUrlValues.Add(k, strconv.FormatFloat(float64(element), 'f', -1, 64)) } // these slices are used in practice like sending a struct case []interface{}: if len(val) <= 0 { continue } switch val[0].(type) { case string: for _, element := range val { newUrlValues.Add(k, element.(string)) } case bool: for _, element := range val { newUrlValues.Add(k, strconv.FormatBool(element.(bool))) } case json.Number: for _, element := range val { newUrlValues.Add(k, string(element.(json.Number))) } } default: // TODO add ptr, arrays, ... } } return newUrlValues } // End is the most important function that you need to call when ending the chain. The request won't proceed without calling it. // End function returns Response which matchs the structure of Response type in Golang's http package (but without Body data). The body data itself returns as a string in a 2nd return value. // Lastly but worth noticing, error array (NOTE: not just single error value) is returned as a 3rd value and nil otherwise. // // For example: // // resp, body, errs := gorequest.New().Get("http://www.google.com").End() // if (errs != nil) { // fmt.Println(errs) // } // fmt.Println(resp, body) // // Moreover, End function also supports callback which you can put as a parameter. // This extends the flexibility and makes GoRequest fun and clean! You can use GoRequest in whatever style you love! // // For example: // // func printBody(resp gorequest.Response, body string, errs []error){ // fmt.Println(resp.Status) // } // gorequest.New().Get("http://www..google.com").End(printBody) // func (s *SuperAgent) End(callback ...func(response Response, body string, errs []error)) (Response, string, []error) { var bytesCallback []func(response Response, body []byte, errs []error) if len(callback) > 0 { bytesCallback = []func(response Response, body []byte, errs []error){ func(response Response, body []byte, errs []error) { callback[0](response, string(body), errs) }, } } resp, body, errs := s.EndBytes(bytesCallback...) bodyString := string(body) return resp, bodyString, errs } // EndBytes should be used when you want the body as bytes. The callbacks work the same way as with `End`, except that a byte array is used instead of a string. func (s *SuperAgent) EndBytes(callback ...func(response Response, body []byte, errs []error)) (Response, []byte, []error) { var ( errs []error resp Response body []byte ) for { resp, body, errs = s.getResponseBytes() if errs != nil { return nil, nil, errs } if s.isRetryableRequest(resp) { resp.Header.Set("Retry-Count", strconv.Itoa(s.Retryable.Attempt)) break } } respCallback := *resp if len(callback) != 0 { callback[0](&respCallback, body, s.Errors) } 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) { time.Sleep(s.Retryable.RetryerTime) s.Retryable.Attempt++ return false } return true } func contains(respStatus int, statuses []int) bool { for _, status := range statuses { if status == respStatus { return true } } return false } // EndStruct should be used when you want the body as a struct. The callbacks work the same way as with `End`, except that a struct is used instead of a string. func (s *SuperAgent) EndStruct(v interface{}, callback ...func(response Response, v interface{}, body []byte, errs []error)) (Response, []byte, []error) { resp, body, errs := s.EndBytes() if errs != nil { return nil, body, errs } err := json.Unmarshal(body, &v) if err != nil { s.Errors = append(s.Errors, err) return resp, body, s.Errors } respCallback := *resp if len(callback) != 0 { callback[0](&respCallback, v, body, s.Errors) } return resp, body, nil } func (s *SuperAgent) getResponseBytes() (Response, []byte, []error) { var ( req *http.Request err error resp Response ) // check whether there is an error. if yes, return all errors if len(s.Errors) != 0 { return nil, nil, s.Errors } // check if there is forced type switch s.ForceType { case "json", "form", "xml", "text", "multipart": 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. default: for k, v := range Types { if s.Header["Content-Type"] == v { s.TargetType = k } } } // if slice and map get mixed, let's bounce to rawstring if len(s.Data) != 0 && len(s.SliceData) != 0 { s.BounceToRawString = true } // Make Request req, err = s.MakeRequest() if err != nil { s.Errors = append(s.Errors, err) return nil, nil, s.Errors } // Set Transport if !DisableTransportSwap { s.Client.Transport = s.Transport } // Log details of this request if s.Debug { dump, err := httputil.DumpRequest(req, true) s.logger.SetPrefix("[http] ") if err != nil { s.logger.Println("Error:", err) } else { s.logger.Printf("HTTP Request: %s", string(dump)) } } // Display CURL command line if s.CurlCommand { curl, err := http2curl.GetCurlCommand(req) s.logger.SetPrefix("[curl] ") if err != nil { s.logger.Println("Error:", err) } else { s.logger.Printf("CURL command line: %s", curl) } } // Send request resp, err = s.Client.Do(req) if err != nil { s.Errors = append(s.Errors, err) return nil, nil, s.Errors } defer resp.Body.Close() // Log details of this response if s.Debug { dump, err := httputil.DumpResponse(resp, true) if nil != err { s.logger.Println("Error:", err) } else { s.logger.Printf("HTTP Response: %s", string(dump)) } } body, _ := ioutil.ReadAll(resp.Body) // Reset resp.Body so it can be use again resp.Body = ioutil.NopCloser(bytes.NewBuffer(body)) return resp, body, nil } func (s *SuperAgent) MakeRequest() (*http.Request, error) { var ( req *http.Request err error ) if s.Method == "" { return nil, errors.New("No method specified") } 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 // 2) Array or Mix of map & array or others: send it as rawstring from s.RawString var contentJson []byte if s.BounceToRawString { contentJson = []byte(s.RawString) } else if len(s.Data) != 0 { contentJson, _ = json.Marshal(s.Data) } 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 } 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 { contentForm = []byte(s.RawString) } else { 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 } 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") } else if s.TargetType == "xml" { req, err = http.NewRequest(s.Method, s.Url, strings.NewReader(s.RawString)) req.Header.Set("Content-Type", "application/xml") } else if s.TargetType == "multipart" { var buf bytes.Buffer mw := multipart.NewWriter(&buf) if s.BounceToRawString { fieldName, ok := s.Header["data_fieldname"] if !ok { fieldName = "data" } fw, _ := mw.CreateFormField(fieldName) fw.Write([]byte(s.RawString)) } if len(s.Data) != 0 { formData := changeMapToURLValues(s.Data) for key, values := range formData { for _, value := range values { fw, _ := mw.CreateFormField(key) fw.Write([]byte(value)) } } } if len(s.SliceData) != 0 { fieldName, ok := s.Header["json_fieldname"] if !ok { fieldName = "data" } // copied from CreateFormField() in mime/multipart/writer.go h := make(textproto.MIMEHeader) fieldName = strings.Replace(strings.Replace(fieldName, "\\", "\\\\", -1), `"`, "\\\"", -1) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, fieldName)) h.Set("Content-Type", "application/json") fw, _ := mw.CreatePart(h) contentJson, err := json.Marshal(s.SliceData) if err != nil { return nil, err } fw.Write(contentJson) } // add the files if len(s.FileData) != 0 { for _, file := range s.FileData { fw, _ := mw.CreateFormFile(file.Fieldname, file.Filename) fw.Write(file.Data) } } // 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()) } else { // let's return an error instead of an nil pointer exception here return nil, errors.New("TargetType '" + s.TargetType + "' could not be determined") } 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 { for _, vv := range v { q.Add(k, vv) } } req.URL.RawQuery = q.Encode() // Add basic auth if s.BasicAuth != struct{ Username, Password string }{} { req.SetBasicAuth(s.BasicAuth.Username, s.BasicAuth.Password) } // Add cookies for _, cookie := range s.Cookies { req.AddCookie(cookie) } return req, nil } // AsCurlCommand returns a string representing the runnable `curl' command // version of the request. func (s *SuperAgent) AsCurlCommand() (string, error) { req, err := s.MakeRequest() if err != nil { return "", err } cmd, err := http2curl.GetCurlCommand(req) if err != nil { return "", err } return cmd.String(), nil } parnurzeal-gorequest-a578a48/gorequest_test.go000066400000000000000000001646401305262257100216760ustar00rootroot00000000000000package gorequest import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "net/http/httptest" "net/url" "reflect" "strconv" "strings" "testing" "time" "mime/multipart" "os" "github.com/elazarl/goproxy" ) type ( heyYou struct { Hey string `json:"hey"` } testStruct struct { String string Int int Btrue bool Bfalse bool Float float64 StringArray []string IntArray []int BoolArray []bool FloatArray []float64 } ) // Test for changeMapToURLValues func TestChangeMapToURLValues(t *testing.T) { data := map[string]interface{}{ "s": "a string", "i": 42, "bt": true, "bf": false, "f": 12.345, "sa": []string{"s1", "s2"}, "ia": []int{47, 73}, "fa": []float64{1.23, 4.56}, "ba": []bool{true, false}, } urlValues := changeMapToURLValues(data) var ( s string sd string ) if s := urlValues.Get("s"); s != data["s"] { t.Errorf("Expected string %v, got %v", data["s"], s) } s = urlValues.Get("i") sd = strconv.Itoa(data["i"].(int)) if s != sd { t.Errorf("Expected int %v, got %v", sd, s) } s = urlValues.Get("bt") sd = strconv.FormatBool(data["bt"].(bool)) if s != sd { t.Errorf("Expected boolean %v, got %v", sd, s) } s = urlValues.Get("bf") sd = strconv.FormatBool(data["bf"].(bool)) if s != sd { t.Errorf("Expected boolean %v, got %v", sd, s) } s = urlValues.Get("f") sd = strconv.FormatFloat(data["f"].(float64), 'f', -1, 64) if s != sd { t.Errorf("Expected float %v, got %v", data["f"], s) } // array cases // "To access multiple values, use the map directly." if size := len(urlValues["sa"]); size != 2 { t.Fatalf("Expected length %v, got %v", 2, size) } if urlValues["sa"][0] != "s1" { t.Errorf("Expected string %v, got %v", "s1", urlValues["sa"][0]) } if urlValues["sa"][1] != "s2" { t.Errorf("Expected string %v, got %v", "s2", urlValues["sa"][1]) } if size := len(urlValues["ia"]); size != 2 { t.Fatalf("Expected length %v, got %v", 2, size) } if urlValues["ia"][0] != "47" { t.Errorf("Expected string %v, got %v", "47", urlValues["ia"][0]) } if urlValues["ia"][1] != "73" { t.Errorf("Expected string %v, got %v", "73", urlValues["ia"][1]) } if size := len(urlValues["ba"]); size != 2 { t.Fatalf("Expected length %v, got %v", 2, size) } if urlValues["ba"][0] != "true" { t.Errorf("Expected string %v, got %v", "true", urlValues["ba"][0]) } if urlValues["ba"][1] != "false" { t.Errorf("Expected string %v, got %v", "false", urlValues["ba"][1]) } if size := len(urlValues["fa"]); size != 2 { t.Fatalf("Expected length %v, got %v", 2, size) } if urlValues["fa"][0] != "1.23" { t.Errorf("Expected string %v, got %v", "true", urlValues["fa"][0]) } if urlValues["fa"][1] != "4.56" { t.Errorf("Expected string %v, got %v", "false", urlValues["fa"][1]) } } // Test for Make request func TestMakeRequest(t *testing.T) { var err error var cases = []struct { m string s *SuperAgent }{ {POST, New().Post("/")}, {GET, New().Get("/")}, {HEAD, New().Head("/")}, {PUT, New().Put("/")}, {PATCH, New().Patch("/")}, {DELETE, New().Delete("/")}, {OPTIONS, New().Options("/")}, {"TRACE", New().CustomMethod("TRACE", "/")}, // valid HTTP 1.1 method, see W3C RFC 2616 } for _, c := range cases { _, err = c.s.MakeRequest() if err != nil { t.Errorf("Expected nil error for method %q; got %q", c.m, err.Error()) } } // empty method should fail _, err = New().CustomMethod("", "/").MakeRequest() if err == nil { t.Errorf("Expected non-nil error for empty method; got %q", err.Error()) } } // testing for Get method func TestGet(t *testing.T) { const case1_empty = "/" const case2_set_header = "/set_header" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check method is GET before going to check other features if r.Method != GET { t.Errorf("Expected method %q; got %q", GET, r.Method) } if r.Header == nil { t.Error("Expected non-nil request Header") } switch r.URL.Path { default: t.Errorf("No testing for this case yet : %q", r.URL.Path) case case1_empty: t.Logf("case %v ", case1_empty) case case2_set_header: t.Logf("case %v ", case2_set_header) if r.Header.Get("API-Key") != "fookey" { t.Errorf("Expected 'API-Key' == %q; got %q", "fookey", r.Header.Get("API-Key")) } } })) defer ts.Close() New().Get(ts.URL + case1_empty). End() New().Get(ts.URL+case2_set_header). Set("API-Key", "fookey"). End() } // testing for Get method with retry option func TestRetryGet(t *testing.T) { const ( case1_empty = "/" case24_after_3_attempt_return_valid = "/retry_3_attempt_then_valid" retry_count_expected = "3" ) var attempt int ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check method is GET before going to check other features if r.Method != GET { t.Errorf("Expected method %q; got %q", GET, r.Method) } //set return status if r.Header == nil { t.Error("Expected non-nil request Header") } switch r.URL.Path { default: t.Errorf("No testing for this case yet : %q", r.URL.Path) case case1_empty: w.WriteHeader(400) t.Logf("case %v ", case1_empty) case case24_after_3_attempt_return_valid: if attempt == 3 { w.WriteHeader(200) } else { w.WriteHeader(400) t.Logf("case %v ", case24_after_3_attempt_return_valid) } attempt++ } })) defer ts.Close() resp, _, errs := New().Get(ts.URL+case1_empty). Retry(3, 1*time.Nanosecond, http.StatusBadRequest). End() if errs != nil { t.Errorf("No testing for this case yet : %q", errs) } retryCountReturn := resp.Header.Get("Retry-Count") if retryCountReturn != retry_count_expected { t.Errorf("Expected [%s] retry but was [%s]", retry_count_expected, retryCountReturn) } resp, _, errs = New().Get(ts.URL+case24_after_3_attempt_return_valid). Retry(4, 1*time.Nanosecond, http.StatusBadRequest). End() if errs != nil { t.Errorf("No testing for this case yet : %q", errs) } retryCountReturn = resp.Header.Get("Retry-Count") if retryCountReturn != retry_count_expected { t.Errorf("Expected [%s] retry but was [%s]", retry_count_expected, retryCountReturn) } } // testing for Options method func TestOptions(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check method is OPTIONS before going to check other features if r.Method != OPTIONS { t.Errorf("Expected method %q; got %q", OPTIONS, r.Method) } t.Log("test Options") w.Header().Set("Allow", "HEAD, GET") w.WriteHeader(204) })) defer ts.Close() New().Options(ts.URL). End() } // testing that resp.Body is reusable func TestResetBody(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Just some text")) })) defer ts.Close() resp, _, _ := New().Get(ts.URL).End() bodyBytes, _ := ioutil.ReadAll(resp.Body) if string(bodyBytes) != "Just some text" { t.Error("Expected to be able to reuse the response body") } } // testing for Param method func TestParam(t *testing.T) { paramCode := "123456" paramFields := "f1;f2;f3" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Form.Get("code") != paramCode { t.Errorf("Expected 'code' == %s; got %v", paramCode, r.Form.Get("code")) } if r.Form.Get("fields") != paramFields { t.Errorf("Expected 'fields' == %s; got %v", paramFields, r.Form.Get("fields")) } })) defer ts.Close() New().Get(ts.URL). Param("code", paramCode). Param("fields", paramFields) } // testing for POST method func TestPost(t *testing.T) { const case1_empty = "/" const case2_set_header = "/set_header" const case3_send_json = "/send_json" const case4_send_string = "/send_string" const case5_integration_send_json_string = "/integration_send_json_string" const case6_set_query = "/set_query" const case7_integration_send_json_struct = "/integration_send_json_struct" // Check that the number conversion should be converted as string not float64 const case8_send_json_with_long_id_number = "/send_json_with_long_id_number" const case9_send_json_string_with_long_id_number_as_form_result = "/send_json_string_with_long_id_number_as_form_result" const case10_send_struct_pointer = "/send_struct_pointer" const case11_send_string_pointer = "/send_string_pointer" const case12_send_slice_string = "/send_slice_string" const case13_send_slice_string_pointer = "/send_slice_string_pointer" const case14_send_int_pointer = "/send_int_pointer" const case15_send_float_pointer = "/send_float_pointer" const case16_send_bool_pointer = "/send_bool_pointer" const case17_send_string_array = "/send_string_array" const case18_send_string_array_pointer = "/send_string_array_pointer" const case19_send_struct = "/send_struct" const case20_send_byte_char = "/send_byte_char" const case21_send_byte_char_pointer = "/send_byte_char_pointer" const case22_send_byte_int = "/send_byte_int" const case22_send_byte_int_pointer = "/send_byte_int_pointer" const case23_send_duplicate_query_params = "/send_duplicate_query_params" const case24_send_query_and_request_body = "/send_query_and_request_body" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check method is POST 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") } switch r.URL.Path { default: t.Errorf("No testing for this case yet : %q", r.URL.Path) case case1_empty: t.Logf("case %v ", case1_empty) case case2_set_header: t.Logf("case %v ", case2_set_header) if r.Header.Get("API-Key") != "fookey" { t.Errorf("Expected 'API-Key' == %q; got %q", "fookey", r.Header.Get("API-Key")) } case case3_send_json: t.Logf("case %v ", case3_send_json) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != `{"query1":"test","query2":"test"}` { t.Error(`Expected Body with {"query1":"test","query2":"test"}`, "| but got", string(body)) } case case4_send_string, case11_send_string_pointer: t.Logf("case %v ", r.URL.Path) if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { t.Error("Expected Header Content-Type -> application/x-www-form-urlencoded", "| but got", r.Header.Get("Content-Type")) } defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != "query1=test&query2=test" { t.Error("Expected Body with \"query1=test&query2=test\"", "| but got", string(body)) } case case5_integration_send_json_string: t.Logf("case %v ", case5_integration_send_json_string) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != "query1=test&query2=test" { t.Error("Expected Body with \"query1=test&query2=test\"", "| but got", string(body)) } case case6_set_query: t.Logf("case %v ", case6_set_query) v := r.URL.Query() if v["query1"][0] != "test" { t.Error("Expected query1:test", "| but got", v["query1"][0]) } if v["query2"][0] != "test" { t.Error("Expected query2:test", "| but got", v["query2"][0]) } case case7_integration_send_json_struct: t.Logf("case %v ", case7_integration_send_json_struct) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) comparedBody := []byte(`{"Lower":{"Color":"green","Size":1.7},"Upper":{"Color":"red","Size":0},"a":"a","name":"Cindy"}`) if !bytes.Equal(body, comparedBody) { t.Errorf(`Expected correct json but got ` + string(body)) } case case8_send_json_with_long_id_number: t.Logf("case %v ", case8_send_json_with_long_id_number) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != `{"id":123456789,"name":"nemo"}` { t.Error(`Expected Body with {"id":123456789,"name":"nemo"}`, "| but got", string(body)) } case case9_send_json_string_with_long_id_number_as_form_result: t.Logf("case %v ", case9_send_json_string_with_long_id_number_as_form_result) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != `id=123456789&name=nemo` { t.Error(`Expected Body with "id=123456789&name=nemo"`, `| but got`, string(body)) } case case19_send_struct, case10_send_struct_pointer: t.Logf("case %v ", r.URL.Path) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) comparedBody := []byte(`{"Bfalse":false,"BoolArray":[true,false],"Btrue":true,"Float":12.345,"FloatArray":[1.23,4.56,7.89],"Int":42,"IntArray":[1,2],"String":"a string","StringArray":["string1","string2"]}`) if !bytes.Equal(body, comparedBody) { t.Errorf(`Expected correct json but got ` + string(body)) } case case12_send_slice_string, case13_send_slice_string_pointer, case17_send_string_array, case18_send_string_array_pointer: t.Logf("case %v ", r.URL.Path) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) comparedBody := []byte(`["string1","string2"]`) if !bytes.Equal(body, comparedBody) { t.Errorf(`Expected correct json but got ` + string(body)) } case case14_send_int_pointer: t.Logf("case %v ", case14_send_int_pointer) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != "42" { t.Error("Expected Body with \"42\"", "| but got", string(body)) } case case15_send_float_pointer: t.Logf("case %v ", case15_send_float_pointer) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != "12.345" { t.Error("Expected Body with \"12.345\"", "| but got", string(body)) } case case16_send_bool_pointer: t.Logf("case %v ", case16_send_bool_pointer) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != "true" { t.Error("Expected Body with \"true\"", "| but got", string(body)) } case case20_send_byte_char, case21_send_byte_char_pointer, case22_send_byte_int, case22_send_byte_int_pointer: t.Logf("case %v ", r.URL.Path) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != "71" { t.Error("Expected Body with \"71\"", "| but got", string(body)) } case case23_send_duplicate_query_params: t.Logf("case %v ", case23_send_duplicate_query_params) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) sbody := string(body) if sbody != "param=4¶m=3¶m=2¶m=1" { t.Error("Expected Body \"param=4¶m=3¶m=2¶m=1\"", "| but got", sbody) } values, _ := url.ParseQuery(sbody) if len(values["param"]) != 4 { t.Error("Expected Body with 4 params", "| but got", sbody) } if values["param"][0] != "4" || values["param"][1] != "3" || values["param"][2] != "2" || values["param"][3] != "1" { t.Error("Expected Body with 4 params and values", "| but got", sbody) } case case24_send_query_and_request_body: t.Logf("case %v ", case24_send_query_and_request_body) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) sbody := string(body) if sbody != `{"name":"jkbbwr"}` { t.Error(`Expected Body "{"name":"jkbbwr"}"`, "| but got", sbody) } v := r.URL.Query() if v["test"][0] != "true" { t.Error("Expected test:true", "| but got", v["test"][0]) } } })) defer ts.Close() New().Post(ts.URL + case1_empty). End() New().Post(ts.URL+case2_set_header). Set("API-Key", "fookey"). End() New().Post(ts.URL + case3_send_json). Send(`{"query1":"test"}`). Send(`{"query2":"test"}`). End() New().Post(ts.URL + case4_send_string). Send("query1=test"). Send("query2=test"). End() New().Post(ts.URL + case5_integration_send_json_string). Send("query1=test"). Send(`{"query2":"test"}`). End() /* TODO: More testing post for application/x-www-form-urlencoded post.query(json), post.query(string), post.send(json), post.send(string), post.query(both).send(both) */ New().Post(ts.URL + case6_set_query). Query("query1=test"). Query("query2=test"). End() // TODO: // 1. test 2nd layer nested struct // 2. test lowercase won't be export to json // 3. test field tag change to json field name type Upper struct { Color string Size int note string } type Lower struct { Color string Size float64 note string } type Style struct { Upper Upper Lower Lower Name string `json:"name"` } myStyle := Style{Upper: Upper{Color: "red"}, Name: "Cindy", Lower: Lower{Color: "green", Size: 1.7}} New().Post(ts.URL + case7_integration_send_json_struct). Send(`{"a":"a"}`). Send(myStyle). End() New().Post(ts.URL + case8_send_json_with_long_id_number). Send(`{"id":123456789, "name":"nemo"}`). End() New().Post(ts.URL + case9_send_json_string_with_long_id_number_as_form_result). Type("form"). Send(`{"id":123456789, "name":"nemo"}`). End() payload := testStruct{ String: "a string", Int: 42, Btrue: true, Bfalse: false, Float: 12.345, StringArray: []string{"string1", "string2"}, IntArray: []int{1, 2}, BoolArray: []bool{true, false}, FloatArray: []float64{1.23, 4.56, 7.89}, } New().Post(ts.URL + case10_send_struct_pointer). Send(&payload). End() New().Post(ts.URL + case19_send_struct). Send(payload). End() s1 := "query1=test" s2 := "query2=test" New().Post(ts.URL + case11_send_string_pointer). Send(&s1). Send(&s2). End() New().Post(ts.URL + case12_send_slice_string). Send([]string{"string1", "string2"}). End() New().Post(ts.URL + case13_send_slice_string_pointer). Send(&[]string{"string1", "string2"}). End() i := 42 New().Post(ts.URL + case14_send_int_pointer). Send(&i). End() f := 12.345 New().Post(ts.URL + case15_send_float_pointer). Send(&f). End() b := true New().Post(ts.URL + case16_send_bool_pointer). Send(&b). End() var a [2]string a[0] = "string1" a[1] = "string2" New().Post(ts.URL + case17_send_string_array). Send(a). End() New().Post(ts.URL + case18_send_string_array_pointer). Send(&a). End() aByte := byte('G') // = 71 dec New().Post(ts.URL + case20_send_byte_char). Send(aByte). End() New().Post(ts.URL + case21_send_byte_char_pointer). Send(&aByte). End() iByte := byte(71) // = 'G' New().Post(ts.URL + case22_send_byte_int). Send(iByte). End() New().Post(ts.URL + case22_send_byte_int_pointer). Send(&iByte). End() New().Post(ts.URL + case23_send_duplicate_query_params). Send("param=1"). Send("param=2"). Send("param=3¶m=4"). End() data24 := struct { Name string `json:"name"` }{"jkbbwr"} New().Post(ts.URL + case24_send_query_and_request_body). Query("test=true"). Send(data24). End() } func checkFile(t *testing.T, fileheader *multipart.FileHeader) { infile, err := fileheader.Open() if err != nil { t.Error(err) } defer infile.Close() b, err := ioutil.ReadAll(infile) if err != nil { t.Error(err) } if len(b) == 0 { t.Error("Expected file-content > 0", "| but got", len(b), string(b)) } } // testing for POST-Request of Type multipart func TestMultipartRequest(t *testing.T) { const case0_send_not_supported_filetype = "/send_not_supported_filetype" const case1_send_string = "/send_string" const case2_send_json = "/send_json" const case3_integration_send_json_string = "/integration_send_json_string" const case4_set_query = "/set_query" const case5_send_struct = "/send_struct" const case6_send_slice_string = "/send_slice_string" const case6_send_slice_string_with_custom_fieldname = "/send_slice_string_with_custom_fieldname" const case7_send_array = "/send_array" const case8_integration_send_json_struct = "/integration_send_json_struct" const case9_send_duplicate_query_params = "/send_duplicate_query_params" const case10_send_file_by_path = "/send_file_by_path" const case10a_send_file_by_path_with_name = "/send_file_by_path_with_name" const case10b_send_file_by_path_pointer = "/send_file_by_path_pointer" const case11_send_file_by_path_without_name = "/send_file_by_path_without_name" const case12_send_file_by_path_without_name_but_with_fieldname = "/send_file_by_path_without_name_but_with_fieldname" const case13_send_file_by_content_without_name = "/send_file_by_content_without_name" const case13a_send_file_by_content_without_name_pointer = "/send_file_by_content_without_name_pointer" const case14_send_file_by_content_with_name = "/send_file_by_content_with_name" const case15_send_file_by_content_without_name_but_with_fieldname = "/send_file_by_content_without_name_but_with_fieldname" const case16_send_file_by_content_with_name_and_with_fieldname = "/send_file_by_content_with_name_and_with_fieldname" const case17_send_file_multiple_by_path_and_content_without_name = "/send_file_multiple_by_path_and_content_without_name" const case18_send_file_multiple_by_path_and_content_with_name = "/send_file_multiple_by_path_and_content_with_name" const case19_integration_send_file_and_data = "/integration_send_file_and_data" const case20_send_file_as_osfile = "/send_file_as_osfile" const case21_send_file_as_osfile_with_name = "/send_file_as_osfile_with_name" const case22_send_file_as_osfile_with_name_and_with_fieldname = "/send_file_as_osfile_with_name_and_with_fieldname" const case23_send_file_with_file_as_fieldname = "/send_file_with_file_as_fieldname" const case24_send_file_with_name_with_spaces = "/send_file_with_name_with_spaces" const case25_send_file_with_name_with_spaces_only = "/send_file_with_name_with_spaces_only" const case26_send_file_with_fieldname_with_spaces = "/send_file_with_fieldname_with_spaces" const case27_send_file_with_fieldname_with_spaces_only = "/send_file_with_fieldname_with_spaces_only" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check method is POST before going to check other features if r.Method != POST { t.Errorf("Expected method %q; got %q", POST, r.Method) } if !strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") { t.Error("Expected Header Content-Type -> multipart/form-data", "| but got", r.Header.Get("Content-Type")) } const _24K = (1 << 20) * 24 err := r.ParseMultipartForm(_24K) if err != nil { t.Errorf("Error: %v", err) } t.Logf("case %v ", r.URL.Path) switch r.URL.Path { default: t.Errorf("No testing for this case yet : %q", r.URL.Path) case case0_send_not_supported_filetype: // will be handled at place case case1_send_string, case2_send_json, case3_integration_send_json_string: if len(r.MultipartForm.Value["query1"]) != 1 { t.Error("Expected length of query1:test == 1", "| but got", len(r.MultipartForm.Value["query1"])) } if r.MultipartForm.Value["query1"][0] != "test" { t.Error("Expected query1:test", "| but got", r.MultipartForm.Value["query1"][0]) } if len(r.MultipartForm.Value["query2"]) != 1 { t.Error("Expected length of query2:test == 1", "| but got", len(r.MultipartForm.Value["query2"])) } if r.MultipartForm.Value["query2"][0] != "test" { t.Error("Expected query2:test", "| but got", r.MultipartForm.Value["query2"][0]) } case case4_set_query: v := r.URL.Query() if v["query1"][0] != "test" { t.Error("Expected query1:test", "| but got", v["query1"][0]) } if v["query2"][0] != "test" { t.Error("Expected query2:test", "| but got", v["query2"][0]) } if val, ok := r.MultipartForm.Value["query1"]; ok { t.Error("Expected no value", "| but got", val) } if val, ok := r.MultipartForm.Value["query2"]; ok { t.Error("Expected no value", "| but got", val) } case case5_send_struct: if r.MultipartForm.Value["String"][0] != "a string" { t.Error("Expected String:'a string'", "| but got", r.MultipartForm.Value["String"][0]) } if r.MultipartForm.Value["Int"][0] != "42" { t.Error("Expected Int:42", "| but got", r.MultipartForm.Value["Int"][0]) } if r.MultipartForm.Value["Btrue"][0] != "true" { t.Error("Expected Btrue:true", "| but got", r.MultipartForm.Value["Btrue"][0]) } if r.MultipartForm.Value["Bfalse"][0] != "false" { t.Error("Expected Btrue:false", "| but got", r.MultipartForm.Value["Bfalse"][0]) } if r.MultipartForm.Value["Float"][0] != "12.345" { t.Error("Expected Float:12.345", "| but got", r.MultipartForm.Value["Float"][0]) } if len(r.MultipartForm.Value["StringArray"]) != 2 { t.Error("Expected length of StringArray:2", "| but got", len(r.MultipartForm.Value["StringArray"])) } if r.MultipartForm.Value["StringArray"][0] != "string1" { t.Error("Expected StringArray:string1", "| but got", r.MultipartForm.Value["StringArray"][0]) } if r.MultipartForm.Value["StringArray"][1] != "string2" { t.Error("Expected StringArray:string2", "| but got", r.MultipartForm.Value["StringArray"][1]) } if len(r.MultipartForm.Value["IntArray"]) != 2 { t.Error("Expected length of IntArray:2", "| but got", len(r.MultipartForm.Value["IntArray"])) } if r.MultipartForm.Value["IntArray"][0] != "1" { t.Error("Expected IntArray:1", "| but got", r.MultipartForm.Value["IntArray"][0]) } if r.MultipartForm.Value["IntArray"][1] != "2" { t.Error("Expected IntArray:2", "| but got", r.MultipartForm.Value["IntArray"][1]) } if len(r.MultipartForm.Value["BoolArray"]) != 2 { t.Error("Expected length of BoolArray:2", "| but got", len(r.MultipartForm.Value["BoolArray"])) } if r.MultipartForm.Value["BoolArray"][0] != "true" { t.Error("Expected BoolArray:true", "| but got", r.MultipartForm.Value["BoolArray"][0]) } if r.MultipartForm.Value["BoolArray"][1] != "false" { t.Error("Expected BoolArray:false", "| but got", r.MultipartForm.Value["BoolArray"][1]) } if len(r.MultipartForm.Value["FloatArray"]) != 3 { t.Error("Expected length of FloatArray:3", "| but got", len(r.MultipartForm.Value["FloatArray"])) } if r.MultipartForm.Value["FloatArray"][0] != "1.23" { t.Error("Expected FloatArray:1.23", "| but got", r.MultipartForm.Value["FloatArray"][0]) } if r.MultipartForm.Value["FloatArray"][1] != "4.56" { t.Error("Expected FloatArray:4.56", "| but got", r.MultipartForm.Value["FloatArray"][1]) } if r.MultipartForm.Value["FloatArray"][2] != "7.89" { t.Error("Expected FloatArray:7.89", "| but got", r.MultipartForm.Value["FloatArray"][2]) } case case6_send_slice_string, case7_send_array: if len(r.MultipartForm.Value["data"]) != 1 { t.Error("Expected length of data:JSON == 1", "| but got", len(r.MultipartForm.Value["data"])) } if r.MultipartForm.Value["data"][0] != `["string1","string2"]` { t.Error(`Expected 'data' with ["string1","string2"]`, "| but got", r.MultipartForm.Value["data"][0]) } case case6_send_slice_string_with_custom_fieldname: if len(r.MultipartForm.Value["my_custom_data"]) != 1 { t.Error("Expected length of my_custom_data:JSON == 1", "| but got", len(r.MultipartForm.Value["my_custom_data"])) } if r.MultipartForm.Value["my_custom_data"][0] != `["string1","string2"]` { t.Error(`Expected 'my_custom_data' with ["string1","string2"]`, "| but got", r.MultipartForm.Value["my_custom_data"][0]) } case case8_integration_send_json_struct: if len(r.MultipartForm.Value["query1"]) != 1 { t.Error("Expected length of query1:test == 1", "| but got", len(r.MultipartForm.Value["query1"])) } if r.MultipartForm.Value["query1"][0] != "test" { t.Error("Expected query1:test", "| but got", r.MultipartForm.Value["query1"][0]) } if r.MultipartForm.Value["hey"][0] != "hey" { t.Error("Expected hey:'hey'", "| but got", r.MultipartForm.Value["Hey"][0]) } case case9_send_duplicate_query_params: if len(r.MultipartForm.Value["param"]) != 4 { t.Error("Expected length of param:[] == 4", "| but got", len(r.MultipartForm.Value["param"])) } if r.MultipartForm.Value["param"][0] != "4" { t.Error("Expected param:0:4", "| but got", r.MultipartForm.Value["param"][0]) } if r.MultipartForm.Value["param"][1] != "3" { t.Error("Expected param:1:3", "| but got", r.MultipartForm.Value["param"][1]) } if r.MultipartForm.Value["param"][2] != "2" { t.Error("Expected param:2:2", "| but got", r.MultipartForm.Value["param"][2]) } if r.MultipartForm.Value["param"][3] != "1" { t.Error("Expected param:3:1", "| but got", r.MultipartForm.Value["param"][3]) } case case10_send_file_by_path, case11_send_file_by_path_without_name, case14_send_file_by_content_with_name, case20_send_file_as_osfile: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if r.MultipartForm.File["file1"][0].Filename != "LICENSE" { t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["file1"][0].Filename) } if r.MultipartForm.File["file1"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file1"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["file1"][0]) case case10a_send_file_by_path_with_name, case10b_send_file_by_path_pointer, case21_send_file_as_osfile_with_name: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if r.MultipartForm.File["file1"][0].Filename != "MY_LICENSE" { t.Error("Expected Filename:MY_LICENSE", "| but got", r.MultipartForm.File["file1"][0].Filename) } case case12_send_file_by_path_without_name_but_with_fieldname: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if _, ok := r.MultipartForm.File["my_fieldname"]; !ok { keys := reflect.ValueOf(r.MultipartForm.File).MapKeys() t.Error("Expected Fieldname:my_fieldname", "| but got", keys) } if r.MultipartForm.File["my_fieldname"][0].Filename != "LICENSE" { t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["my_fieldname"][0].Filename) } if r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["my_fieldname"][0]) case case13_send_file_by_content_without_name, case13a_send_file_by_content_without_name_pointer: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if r.MultipartForm.File["file1"][0].Filename != "filename" { t.Error("Expected Filename:filename", "| but got", r.MultipartForm.File["file1"][0].Filename) } if r.MultipartForm.File["file1"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file1"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["file1"][0]) case case15_send_file_by_content_without_name_but_with_fieldname: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if _, ok := r.MultipartForm.File["my_fieldname"]; !ok { keys := reflect.ValueOf(r.MultipartForm.File).MapKeys() t.Error("Expected Fieldname:my_fieldname", "| but got", keys) } if r.MultipartForm.File["my_fieldname"][0].Filename != "filename" { t.Error("Expected Filename:filename", "| but got", r.MultipartForm.File["my_fieldname"][0].Filename) } if r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["my_fieldname"][0]) case case16_send_file_by_content_with_name_and_with_fieldname, case22_send_file_as_osfile_with_name_and_with_fieldname: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if _, ok := r.MultipartForm.File["my_fieldname"]; !ok { keys := reflect.ValueOf(r.MultipartForm.File).MapKeys() t.Error("Expected Fieldname:my_fieldname", "| but got", keys) } if r.MultipartForm.File["my_fieldname"][0].Filename != "MY_LICENSE" { t.Error("Expected Filename:MY_LICENSE", "| but got", r.MultipartForm.File["my_fieldname"][0].Filename) } if r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["my_fieldname"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["my_fieldname"][0]) case case17_send_file_multiple_by_path_and_content_without_name: if len(r.MultipartForm.File) != 2 { t.Error("Expected length of files:[] == 2", "| but got", len(r.MultipartForm.File)) } // depends on map iteration order if r.MultipartForm.File["file1"][0].Filename != "LICENSE" && r.MultipartForm.File["file1"][0].Filename != "filename" { t.Error("Expected Filename:LICENSE||filename", "| but got", r.MultipartForm.File["file1"][0].Filename) } if r.MultipartForm.File["file1"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file1"][0].Header["Content-Type"]) } // depends on map iteration order if r.MultipartForm.File["file2"][0].Filename != "LICENSE" && r.MultipartForm.File["file2"][0].Filename != "filename" { t.Error("Expected Filename:LICENSE||filename", "| but got", r.MultipartForm.File["file2"][0].Filename) } if r.MultipartForm.File["file2"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file2"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["file1"][0]) checkFile(t, r.MultipartForm.File["file2"][0]) case case18_send_file_multiple_by_path_and_content_with_name: if len(r.MultipartForm.File) != 2 { t.Error("Expected length of files:[] == 2", "| but got", len(r.MultipartForm.File)) } // depends on map iteration order if r.MultipartForm.File["file1"][0].Filename != "LICENSE" && r.MultipartForm.File["file1"][0].Filename != "MY_LICENSE" { t.Error("Expected Filename:LICENSE||MY_LICENSE", "| but got", r.MultipartForm.File["file1"][0].Filename) } if r.MultipartForm.File["file1"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file1"][0].Header["Content-Type"]) } // depends on map iteration order if r.MultipartForm.File["file2"][0].Filename != "LICENSE" && r.MultipartForm.File["file2"][0].Filename != "MY_LICENSE" { t.Error("Expected Filename:LICENSE||MY_LICENSE", "| but got", r.MultipartForm.File["file2"][0].Filename) } if r.MultipartForm.File["file2"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file2"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["file1"][0]) checkFile(t, r.MultipartForm.File["file2"][0]) case case19_integration_send_file_and_data: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if r.MultipartForm.File["file1"][0].Filename != "LICENSE" { t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["file1"][0].Filename) } if r.MultipartForm.File["file1"][0].Header["Content-Type"][0] != "application/octet-stream" { t.Error("Expected Header:Content-Type:application/octet-stream", "| but got", r.MultipartForm.File["file1"][0].Header["Content-Type"]) } checkFile(t, r.MultipartForm.File["file1"][0]) if len(r.MultipartForm.Value["query1"]) != 1 { t.Error("Expected length of query1:test == 1", "| but got", len(r.MultipartForm.Value["query1"])) } if r.MultipartForm.Value["query1"][0] != "test" { t.Error("Expected query1:test", "| but got", r.MultipartForm.Value["query1"][0]) } case case23_send_file_with_file_as_fieldname: if len(r.MultipartForm.File) != 2 { t.Error("Expected length of files:[] == 2", "| but got", len(r.MultipartForm.File)) } if val, ok := r.MultipartForm.File["file1"]; !ok { t.Error("Expected file with key: file1", "| but got ", val) } if val, ok := r.MultipartForm.File["file2"]; !ok { t.Error("Expected file with key: file2", "| but got ", val) } if r.MultipartForm.File["file1"][0].Filename != "b.file" { t.Error("Expected Filename:b.file", "| but got", r.MultipartForm.File["file1"][0].Filename) } if r.MultipartForm.File["file2"][0].Filename != "LICENSE" { t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["file2"][0].Filename) } checkFile(t, r.MultipartForm.File["file1"][0]) checkFile(t, r.MultipartForm.File["file2"][0]) case case24_send_file_with_name_with_spaces, case25_send_file_with_name_with_spaces_only, case27_send_file_with_fieldname_with_spaces_only: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if val, ok := r.MultipartForm.File["file1"]; !ok { t.Error("Expected file with key: file1", "| but got ", val) } if r.MultipartForm.File["file1"][0].Filename != "LICENSE" { t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["file1"][0].Filename) } checkFile(t, r.MultipartForm.File["file1"][0]) case case26_send_file_with_fieldname_with_spaces: if len(r.MultipartForm.File) != 1 { t.Error("Expected length of files:[] == 1", "| but got", len(r.MultipartForm.File)) } if val, ok := r.MultipartForm.File["my_fieldname"]; !ok { t.Error("Expected file with key: my_fieldname", "| but got ", val) } if r.MultipartForm.File["my_fieldname"][0].Filename != "LICENSE" { t.Error("Expected Filename:LICENSE", "| but got", r.MultipartForm.File["my_fieldname"][0].Filename) } checkFile(t, r.MultipartForm.File["my_fieldname"][0]) } })) defer ts.Close() // "the zero case" t.Logf("case %v ", case0_send_not_supported_filetype) _, _, errs := New().Post(ts.URL + case0_send_not_supported_filetype). Type("multipart"). SendFile(42). End() if len(errs) == 0 { t.Errorf("Expected error, but got nothing: %v", errs) } New().Post(ts.URL + case1_send_string). Type("multipart"). Send("query1=test"). Send("query2=test"). End() New().Post(ts.URL + case2_send_json). Type("multipart"). Send(`{"query1":"test"}`). Send(`{"query2":"test"}`). End() New().Post(ts.URL + case3_integration_send_json_string). Type("multipart"). Send("query1=test"). Send(`{"query2":"test"}`). End() New().Post(ts.URL + case4_set_query). Type("multipart"). Query("query1=test"). Query("query2=test"). End() New().Post(ts.URL + case5_send_struct). Type("multipart"). Send(testStruct{ String: "a string", Int: 42, Btrue: true, Bfalse: false, Float: 12.345, StringArray: []string{"string1", "string2"}, IntArray: []int{1, 2}, BoolArray: []bool{true, false}, FloatArray: []float64{1.23, 4.56, 7.89}, }). End() New().Post(ts.URL + case6_send_slice_string). Type("multipart"). Send([]string{"string1", "string2"}). End() New().Post(ts.URL+case6_send_slice_string_with_custom_fieldname). Type("multipart"). Set("json_fieldname", "my_custom_data"). Send([]string{"string1", "string2"}). End() New().Post(ts.URL + case7_send_array). Type("multipart"). Send([2]string{"string1", "string2"}). End() New().Post(ts.URL + case8_integration_send_json_struct). Type("multipart"). Send(`{"query1":"test"}`). Send(heyYou{ Hey: "hey", }). End() New().Post(ts.URL + case9_send_duplicate_query_params). Type("multipart"). Send("param=1"). Send("param=2"). Send("param=3¶m=4"). End() fileByPath := "./LICENSE" New().Post(ts.URL + case10_send_file_by_path). Type("multipart"). SendFile(fileByPath). End() New().Post(ts.URL+case10a_send_file_by_path_with_name). Type("multipart"). SendFile(fileByPath, "MY_LICENSE"). End() New().Post(ts.URL+case10b_send_file_by_path_pointer). Type("multipart"). SendFile(&fileByPath, "MY_LICENSE"). End() New().Post(ts.URL+case11_send_file_by_path_without_name). Type("multipart"). SendFile(fileByPath, ""). End() New().Post(ts.URL+case12_send_file_by_path_without_name_but_with_fieldname). Type("multipart"). SendFile(fileByPath, "", "my_fieldname"). End() b, _ := ioutil.ReadFile("./LICENSE") New().Post(ts.URL + case13_send_file_by_content_without_name). Type("multipart"). SendFile(b). End() New().Post(ts.URL + case13a_send_file_by_content_without_name_pointer). Type("multipart"). SendFile(&b). End() New().Post(ts.URL+case14_send_file_by_content_with_name). Type("multipart"). SendFile(b, "LICENSE"). End() New().Post(ts.URL+case15_send_file_by_content_without_name_but_with_fieldname). Type("multipart"). SendFile(b, "", "my_fieldname"). End() New().Post(ts.URL+case16_send_file_by_content_with_name_and_with_fieldname). Type("multipart"). SendFile(b, "MY_LICENSE", "my_fieldname"). End() New().Post(ts.URL + case17_send_file_multiple_by_path_and_content_without_name). Type("multipart"). SendFile("./LICENSE"). SendFile(b). End() New().Post(ts.URL+case18_send_file_multiple_by_path_and_content_with_name). Type("multipart"). SendFile("./LICENSE"). SendFile(b, "MY_LICENSE"). End() New().Post(ts.URL + case19_integration_send_file_and_data). Type("multipart"). SendFile("./LICENSE"). Send("query1=test"). End() osFile, _ := os.Open("./LICENSE") New().Post(ts.URL + case20_send_file_as_osfile). Type("multipart"). SendFile(osFile). End() New().Post(ts.URL+case21_send_file_as_osfile_with_name). Type("multipart"). SendFile(osFile, "MY_LICENSE"). End() New().Post(ts.URL+case22_send_file_as_osfile_with_name_and_with_fieldname). Type("multipart"). SendFile(osFile, "MY_LICENSE", "my_fieldname"). End() New().Post(ts.URL+case23_send_file_with_file_as_fieldname). Type("multipart"). SendFile(b, "b.file"). SendFile(osFile, "", "file"). End() New().Post(ts.URL+case24_send_file_with_name_with_spaces). Type("multipart"). SendFile(osFile, " LICENSE "). End() New().Post(ts.URL+case25_send_file_with_name_with_spaces_only). Type("multipart"). SendFile(osFile, " "). End() New().Post(ts.URL+case26_send_file_with_fieldname_with_spaces). Type("multipart"). SendFile(osFile, "", " my_fieldname "). End() New().Post(ts.URL+case27_send_file_with_fieldname_with_spaces_only). Type("multipart"). SendFile(osFile, "", " "). End() } // testing for Patch method func TestPatch(t *testing.T) { const case1_empty = "/" const case2_set_header = "/set_header" const case3_send_json = "/send_json" 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 != PATCH { t.Errorf("Expected method %q; got %q", PATCH, r.Method) } if r.Header == nil { t.Error("Expected non-nil request Header") } switch r.URL.Path { default: t.Errorf("No testing for this case yet : %q", r.URL.Path) case case1_empty: t.Logf("case %v ", case1_empty) case case2_set_header: t.Logf("case %v ", case2_set_header) if r.Header.Get("API-Key") != "fookey" { t.Errorf("Expected 'API-Key' == %q; got %q", "fookey", r.Header.Get("API-Key")) } case case3_send_json: t.Logf("case %v ", case3_send_json) defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != `{"query1":"test","query2":"test"}` { t.Error(`Expected Body with {"query1":"test","query2":"test"}`, "| but got", string(body)) } } })) defer ts.Close() New().Patch(ts.URL + case1_empty). End() New().Patch(ts.URL+case2_set_header). Set("API-Key", "fookey"). End() New().Patch(ts.URL + case3_send_json). Send(`{"query1":"test"}`). Send(`{"query2":"test"}`). End() } func checkQuery(t *testing.T, q map[string][]string, key string, want string) { v, ok := q[key] if !ok { t.Error(key, "Not Found") } else if len(v) < 1 { t.Error("No values for", key) } else if v[0] != want { t.Errorf("Expected %v:%v | but got %v", key, want, v[0]) } return } // TODO: more check on url query (all testcases) func TestQueryFunc(t *testing.T) { const case1_send_string = "/send_string" const case2_send_struct = "/send_struct" const case3_send_string_with_duplicates = "/send_string_with_duplicates" const case4_send_map = "/send_map" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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") } v := r.URL.Query() switch r.URL.Path { default: t.Errorf("No testing for this case yet : %q", r.URL.Path) case case1_send_string, case2_send_struct: checkQuery(t, v, "query1", "test1") checkQuery(t, v, "query2", "test2") case case3_send_string_with_duplicates: checkQuery(t, v, "query1", "test1") checkQuery(t, v, "query2", "test2") if len(v["param"]) != 4 { t.Errorf("Expected Body with 4 params | but got %q", len(v["param"])) } if v["param"][0] != "1" || v["param"][1] != "2" || v["param"][2] != "3" || v["param"][3] != "4" { t.Error("Expected Body with 4 params and values", "| but got", r.URL.RawQuery) } case case4_send_map: checkQuery(t, v, "query1", "test1") checkQuery(t, v, "query2", "test2") checkQuery(t, v, "query3", "3.1415926") checkQuery(t, v, "query4", "true") } })) defer ts.Close() New().Post(ts.URL + case1_send_string). Query("query1=test1"). Query("query2=test2"). End() qq := struct { Query1 string Query2 string }{ Query1: "test1", Query2: "test2", } New().Post(ts.URL + case2_send_struct). Query(qq). End() New().Post(ts.URL + case3_send_string_with_duplicates). Query("query1=test1"). Query("query2=test2"). Query("param=1"). Query("param=2"). Query("param=3¶m=4"). End() New().Post(ts.URL + case4_send_map). Query(map[string]interface{}{ "query1": "test1", "query2": "test2", "query3": 3.1415926, "query4": true, }). End() } // TODO: more tests on redirect func TestRedirectPolicyFunc(t *testing.T) { redirectSuccess := false redirectFuncGetCalled := false tsRedirect := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { redirectSuccess = true })) defer tsRedirect.Close() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, tsRedirect.URL, http.StatusMovedPermanently) })) defer ts.Close() New(). Get(ts.URL). RedirectPolicy(func(req Request, via []Request) error { redirectFuncGetCalled = true return nil }).End() if !redirectSuccess { t.Error("Expected reaching another redirect url not original one") } if !redirectFuncGetCalled { t.Error("Expected redirect policy func to get called") } } func TestEndBytes(t *testing.T) { serverOutput := "hello world" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Write([]byte(serverOutput)) })) defer ts.Close() // Callback. { resp, bodyBytes, errs := New().Get(ts.URL).EndBytes(func(resp Response, body []byte, errs []error) { if len(errs) > 0 { t.Fatalf("Unexpected errors: %s", errs) } if resp.StatusCode != 200 { t.Fatalf("Expected StatusCode=200, actual StatusCode=%v", resp.StatusCode) } if string(body) != serverOutput { t.Errorf("Expected bodyBytes=%s, actual bodyBytes=%s", serverOutput, string(body)) } }) if len(errs) > 0 { t.Fatalf("Unexpected errors: %s", errs) } if resp.StatusCode != 200 { t.Fatalf("Expected StatusCode=200, actual StatusCode=%v", resp.StatusCode) } if string(bodyBytes) != serverOutput { t.Errorf("Expected bodyBytes=%s, actual bodyBytes=%s", serverOutput, string(bodyBytes)) } } // No callback. { resp, bodyBytes, errs := New().Get(ts.URL).EndBytes() if len(errs) > 0 { t.Errorf("Unexpected errors: %s", errs) } if resp.StatusCode != 200 { t.Errorf("Expected StatusCode=200, actual StatusCode=%v", resp.StatusCode) } if string(bodyBytes) != serverOutput { t.Errorf("Expected bodyBytes=%s, actual bodyBytes=%s", serverOutput, string(bodyBytes)) } } } func TestEndStruct(t *testing.T) { var resStruct heyYou expStruct := heyYou{Hey: "you"} serverOutput, err := json.Marshal(expStruct) if err != nil { t.Errorf("Unexpected errors: %s", err) } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Write(serverOutput) })) defer ts.Close() // Callback. { resp, bodyBytes, errs := New().Get(ts.URL).EndStruct(func(resp Response, v interface{}, body []byte, errs []error) { if len(errs) > 0 { t.Fatalf("Unexpected errors: %s", errs) } if resp.StatusCode != 200 { t.Fatalf("Expected StatusCode=200, actual StatusCode=%v", resp.StatusCode) } if !reflect.DeepEqual(expStruct, resStruct) { resBytes, _ := json.Marshal(resStruct) t.Errorf("Expected body=%s, actual bodyBytes=%s", serverOutput, string(resBytes)) } if !reflect.DeepEqual(body, serverOutput) { t.Errorf("Expected bodyBytes=%s, actual bodyBytes=%s", serverOutput, string(body)) } }) if len(errs) > 0 { t.Fatalf("Unexpected errors: %s", errs) } if resp.StatusCode != 200 { t.Fatalf("Expected StatusCode=200, actual StatusCode=%v", resp.StatusCode) } if !reflect.DeepEqual(bodyBytes, serverOutput) { t.Errorf("Expected bodyBytes=%s, actual bodyBytes=%s", serverOutput, string(bodyBytes)) } } // No callback. { resp, bodyBytes, errs := New().Get(ts.URL).EndStruct(&resStruct) if len(errs) > 0 { t.Errorf("Unexpected errors: %s", errs) } if resp.StatusCode != 200 { t.Errorf("Expected StatusCode=200, actual StatusCode=%v", resp.StatusCode) } if !reflect.DeepEqual(expStruct, resStruct) { resBytes, _ := json.Marshal(resStruct) t.Errorf("Expected body=%s, actual bodyBytes=%s", serverOutput, string(resBytes)) } if !reflect.DeepEqual(bodyBytes, serverOutput) { t.Errorf("Expected bodyBytes=%s, actual bodyBytes=%s", serverOutput, string(bodyBytes)) } } } func TestProxyFunc(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "proxy passed") })) defer ts.Close() // start proxy proxy := goproxy.NewProxyHttpServer() proxy.OnRequest().DoFunc( func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { return r, nil }) ts2 := httptest.NewServer(proxy) // sending request via Proxy resp, body, _ := New().Proxy(ts2.URL).Get(ts.URL).End() if resp.StatusCode != 200 { t.Error("Expected 200 Status code") } if body != "proxy passed" { t.Error("Expected 'proxy passed' body string") } } func TestTimeoutFunc(t *testing.T) { // 1st case, dial timeout startTime := time.Now() _, _, errs := New().Timeout(1000 * time.Millisecond).Get("http://www.google.com:81").End() elapsedTime := time.Since(startTime) if errs == nil { t.Error("Expected dial timeout error but get nothing") } if elapsedTime < 1000*time.Millisecond || elapsedTime > 1500*time.Millisecond { t.Errorf("Expected timeout in between 1000 -> 1500 ms | but got %d", elapsedTime) } // 2st case, read/write timeout (Can dial to url but want to timeout because too long operation on the server) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(1100 * time.Millisecond) // slightly longer than expected w.WriteHeader(200) })) request := New().Timeout(1000 * time.Millisecond) startTime = time.Now() _, _, errs = request.Get(ts.URL).End() elapsedTime = time.Since(startTime) if errs == nil { t.Error("Expected dial+read/write timeout | but get nothing") } if elapsedTime < 1000*time.Millisecond || elapsedTime > 1500*time.Millisecond { t.Errorf("Expected timeout in between 1000 -> 1500 ms | but got %d", elapsedTime) } // 3rd case, testing reuse of same request ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(1100 * time.Millisecond) // slightly longer than expected w.WriteHeader(200) })) startTime = time.Now() _, _, errs = request.Get(ts.URL).End() elapsedTime = time.Since(startTime) if errs == nil { t.Error("Expected dial+read/write timeout | but get nothing") } if elapsedTime < 1000*time.Millisecond || elapsedTime > 1500*time.Millisecond { t.Errorf("Expected timeout in between 1000 -> 1500 ms | but got %d", elapsedTime) } } func TestCookies(t *testing.T) { request := New().Timeout(60 * time.Second) _, _, errs := request.Get("https://github.com").End() if errs != nil { t.Error("Cookies test request did not complete") return } domain, _ := url.Parse("https://github.com") if len(request.Client.Jar.Cookies(domain)) == 0 { t.Error("Expected cookies | but get nothing") } } func TestGetSetCookie(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != GET { t.Errorf("Expected method %q; got %q", GET, r.Method) } c, err := r.Cookie("API-Cookie-Name") if err != nil { t.Error(err) } if c == nil { t.Error("Expected non-nil request Cookie 'API-Cookie-Name'") } else if c.Value != "api-cookie-value" { t.Errorf("Expected 'API-Cookie-Name' == %q; got %q", "api-cookie-value", c.Value) } })) defer ts.Close() New().Get(ts.URL). AddCookie(&http.Cookie{Name: "API-Cookie-Name", Value: "api-cookie-value"}). End() } func TestGetSetCookies(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != GET { t.Errorf("Expected method %q; got %q", GET, r.Method) } c, err := r.Cookie("API-Cookie-Name1") if err != nil { t.Error(err) } if c == nil { t.Error("Expected non-nil request Cookie 'API-Cookie-Name1'") } else if c.Value != "api-cookie-value1" { t.Errorf("Expected 'API-Cookie-Name1' == %q; got %q", "api-cookie-value1", c.Value) } c, err = r.Cookie("API-Cookie-Name2") if err != nil { t.Error(err) } if c == nil { t.Error("Expected non-nil request Cookie 'API-Cookie-Name2'") } else if c.Value != "api-cookie-value2" { t.Errorf("Expected 'API-Cookie-Name2' == %q; got %q", "api-cookie-value2", c.Value) } })) defer ts.Close() New().Get(ts.URL).AddCookies([]*http.Cookie{ {Name: "API-Cookie-Name1", Value: "api-cookie-value1"}, {Name: "API-Cookie-Name2", Value: "api-cookie-value2"}, }).End() } func TestErrorTypeWrongKey(t *testing.T) { //defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, checkTypeWrongKey") })) defer ts.Close() _, _, err := New(). Get(ts.URL). Type("wrongtype"). End() if len(err) != 0 { if err[0].Error() != "Type func: incorrect type \"wrongtype\"" { t.Errorf("Wrong error message: " + err[0].Error()) } } else { t.Error("Should have error") } } func TestBasicAuth(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { auth := strings.SplitN(r.Header["Authorization"][0], " ", 2) if len(auth) != 2 || auth[0] != "Basic" { t.Error("bad syntax") } payload, _ := base64.StdEncoding.DecodeString(auth[1]) pair := strings.SplitN(string(payload), ":", 2) if pair[0] != "myusername" || pair[1] != "mypassword" { t.Error("Wrong username/password") } })) defer ts.Close() New().Post(ts.URL). SetBasicAuth("myusername", "mypassword"). End() } func TestXml(t *testing.T) { xml := `ToveJaniReminderDon't forget me this weekend!` 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/xml" { t.Error("Expected Header Content-Type -> application/xml", "| but got", r.Header.Get("Content-Type")) } defer r.Body.Close() body, _ := ioutil.ReadAll(r.Body) if string(body) != xml { t.Error(`Expected XML `, xml, "| but got", string(body)) } })) defer ts.Close() New().Post(ts.URL). Type("xml"). Send(xml). End() New().Post(ts.URL). Set("Content-Type", "application/xml"). Send(xml). End() } func TestPlainText(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). Type("text"). Send(text). End() New().Post(ts.URL). Set("Content-Type", "text/plain"). Send(text). End() } func TestAsCurlCommand(t *testing.T) { var ( endpoint = "http://github.com/parnurzeal/gorequest" jsonData = `{"here": "is", "some": {"json": ["data"]}}` ) request := New().Timeout(10*time.Second).Put(endpoint).Set("Content-Type", "application/json").Send(jsonData) curlComand, err := request.AsCurlCommand() if err != nil { t.Fatal(err) } expected := fmt.Sprintf(`curl -X 'PUT' -d '%v' -H 'Content-Type: application/json' '%v'`, strings.Replace(jsonData, " ", "", -1), endpoint) if curlComand != expected { t.Fatalf("\nExpected curlCommand=%v\n but actual result=%v", expected, curlComand) } } func TestSetDebugByEnvironmentVar(t *testing.T) { endpoint := "http://github.com/parnurzeal/gorequest" var buf bytes.Buffer logger := log.New(&buf, "[gorequest]", log.LstdFlags) os.Setenv("GOREQUEST_DEBUG", "1") New().SetLogger(logger).Get(endpoint).End() if len(buf.String()) == 0 { t.Fatalf("\nExpected gorequest to log request and response object if GOREQUEST_DEBUG=1") } os.Setenv("GOREQUEST_DEBUG", "") buf.Reset() New().SetLogger(logger).Get(endpoint).End() if len(buf.String()) > 0 { t.Fatalf("\nExpected gorequest not to log request and response object if GOREQUEST_DEBUG is not set.") } }