client.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. // Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
  2. // resty source code and usage is governed by a MIT style
  3. // license that can be found in the LICENSE file.
  4. package resty
  5. import (
  6. "bytes"
  7. "compress/gzip"
  8. "crypto/tls"
  9. "crypto/x509"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "io/ioutil"
  14. "log"
  15. "net/http"
  16. "net/url"
  17. "reflect"
  18. "regexp"
  19. "strings"
  20. "sync"
  21. "time"
  22. )
  23. const (
  24. // MethodGet HTTP method
  25. MethodGet = "GET"
  26. // MethodPost HTTP method
  27. MethodPost = "POST"
  28. // MethodPut HTTP method
  29. MethodPut = "PUT"
  30. // MethodDelete HTTP method
  31. MethodDelete = "DELETE"
  32. // MethodPatch HTTP method
  33. MethodPatch = "PATCH"
  34. // MethodHead HTTP method
  35. MethodHead = "HEAD"
  36. // MethodOptions HTTP method
  37. MethodOptions = "OPTIONS"
  38. )
  39. var (
  40. hdrUserAgentKey = http.CanonicalHeaderKey("User-Agent")
  41. hdrAcceptKey = http.CanonicalHeaderKey("Accept")
  42. hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type")
  43. hdrContentLengthKey = http.CanonicalHeaderKey("Content-Length")
  44. hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")
  45. hdrAuthorizationKey = http.CanonicalHeaderKey("Authorization")
  46. plainTextType = "text/plain; charset=utf-8"
  47. jsonContentType = "application/json; charset=utf-8"
  48. formContentType = "application/x-www-form-urlencoded"
  49. jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`)
  50. xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`)
  51. hdrUserAgentValue = "go-resty/%s (https://github.com/go-resty/resty)"
  52. bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
  53. )
  54. // Client type is used for HTTP/RESTful global values
  55. // for all request raised from the client
  56. type Client struct {
  57. HostURL string
  58. QueryParam url.Values
  59. FormData url.Values
  60. Header http.Header
  61. UserInfo *User
  62. Token string
  63. Cookies []*http.Cookie
  64. Error reflect.Type
  65. Debug bool
  66. DisableWarn bool
  67. AllowGetMethodPayload bool
  68. Log *log.Logger
  69. RetryCount int
  70. RetryWaitTime time.Duration
  71. RetryMaxWaitTime time.Duration
  72. RetryConditions []RetryConditionFunc
  73. JSONMarshal func(v interface{}) ([]byte, error)
  74. JSONUnmarshal func(data []byte, v interface{}) error
  75. jsonEscapeHTML bool
  76. httpClient *http.Client
  77. setContentLength bool
  78. isHTTPMode bool
  79. outputDirectory string
  80. scheme string
  81. proxyURL *url.URL
  82. closeConnection bool
  83. notParseResponse bool
  84. debugBodySizeLimit int64
  85. logPrefix string
  86. pathParams map[string]string
  87. beforeRequest []func(*Client, *Request) error
  88. udBeforeRequest []func(*Client, *Request) error
  89. preReqHook func(*Client, *Request) error
  90. afterResponse []func(*Client, *Response) error
  91. requestLog func(*RequestLog) error
  92. responseLog func(*ResponseLog) error
  93. }
  94. // User type is to hold an username and password information
  95. type User struct {
  96. Username, Password string
  97. }
  98. //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  99. // Client methods
  100. //___________________________________
  101. // SetHostURL method is to set Host URL in the client instance. It will be used with request
  102. // raised from this client with relative URL
  103. // // Setting HTTP address
  104. // resty.SetHostURL("http://myjeeva.com")
  105. //
  106. // // Setting HTTPS address
  107. // resty.SetHostURL("https://myjeeva.com")
  108. //
  109. func (c *Client) SetHostURL(url string) *Client {
  110. c.HostURL = strings.TrimRight(url, "/")
  111. return c
  112. }
  113. // SetHeader method sets a single header field and its value in the client instance.
  114. // These headers will be applied to all requests raised from this client instance.
  115. // Also it can be overridden at request level header options, see `resty.R().SetHeader`
  116. // or `resty.R().SetHeaders`.
  117. //
  118. // Example: To set `Content-Type` and `Accept` as `application/json`
  119. //
  120. // resty.
  121. // SetHeader("Content-Type", "application/json").
  122. // SetHeader("Accept", "application/json")
  123. //
  124. func (c *Client) SetHeader(header, value string) *Client {
  125. c.Header.Set(header, value)
  126. return c
  127. }
  128. // SetHeaders method sets multiple headers field and its values at one go in the client instance.
  129. // These headers will be applied to all requests raised from this client instance. Also it can be
  130. // overridden at request level headers options, see `resty.R().SetHeaders` or `resty.R().SetHeader`.
  131. //
  132. // Example: To set `Content-Type` and `Accept` as `application/json`
  133. //
  134. // resty.SetHeaders(map[string]string{
  135. // "Content-Type": "application/json",
  136. // "Accept": "application/json",
  137. // })
  138. //
  139. func (c *Client) SetHeaders(headers map[string]string) *Client {
  140. for h, v := range headers {
  141. c.Header.Set(h, v)
  142. }
  143. return c
  144. }
  145. // SetCookieJar method sets custom http.CookieJar in the resty client. Its way to override default.
  146. // Example: sometimes we don't want to save cookies in api contacting, we can remove the default
  147. // CookieJar in resty client.
  148. //
  149. // resty.SetCookieJar(nil)
  150. //
  151. func (c *Client) SetCookieJar(jar http.CookieJar) *Client {
  152. c.httpClient.Jar = jar
  153. return c
  154. }
  155. // SetCookie method appends a single cookie in the client instance.
  156. // These cookies will be added to all the request raised from this client instance.
  157. // resty.SetCookie(&http.Cookie{
  158. // Name:"go-resty",
  159. // Value:"This is cookie value",
  160. // Path: "/",
  161. // Domain: "sample.com",
  162. // MaxAge: 36000,
  163. // HttpOnly: true,
  164. // Secure: false,
  165. // })
  166. //
  167. func (c *Client) SetCookie(hc *http.Cookie) *Client {
  168. c.Cookies = append(c.Cookies, hc)
  169. return c
  170. }
  171. // SetCookies method sets an array of cookies in the client instance.
  172. // These cookies will be added to all the request raised from this client instance.
  173. // cookies := make([]*http.Cookie, 0)
  174. //
  175. // cookies = append(cookies, &http.Cookie{
  176. // Name:"go-resty-1",
  177. // Value:"This is cookie 1 value",
  178. // Path: "/",
  179. // Domain: "sample.com",
  180. // MaxAge: 36000,
  181. // HttpOnly: true,
  182. // Secure: false,
  183. // })
  184. //
  185. // cookies = append(cookies, &http.Cookie{
  186. // Name:"go-resty-2",
  187. // Value:"This is cookie 2 value",
  188. // Path: "/",
  189. // Domain: "sample.com",
  190. // MaxAge: 36000,
  191. // HttpOnly: true,
  192. // Secure: false,
  193. // })
  194. //
  195. // // Setting a cookies into resty
  196. // resty.SetCookies(cookies)
  197. //
  198. func (c *Client) SetCookies(cs []*http.Cookie) *Client {
  199. c.Cookies = append(c.Cookies, cs...)
  200. return c
  201. }
  202. // SetQueryParam method sets single parameter and its value in the client instance.
  203. // It will be formed as query string for the request. For example: `search=kitchen%20papers&size=large`
  204. // in the URL after `?` mark. These query params will be added to all the request raised from
  205. // this client instance. Also it can be overridden at request level Query Param options,
  206. // see `resty.R().SetQueryParam` or `resty.R().SetQueryParams`.
  207. // resty.
  208. // SetQueryParam("search", "kitchen papers").
  209. // SetQueryParam("size", "large")
  210. //
  211. func (c *Client) SetQueryParam(param, value string) *Client {
  212. c.QueryParam.Set(param, value)
  213. return c
  214. }
  215. // SetQueryParams method sets multiple parameters and their values at one go in the client instance.
  216. // It will be formed as query string for the request. For example: `search=kitchen%20papers&size=large`
  217. // in the URL after `?` mark. These query params will be added to all the request raised from this
  218. // client instance. Also it can be overridden at request level Query Param options,
  219. // see `resty.R().SetQueryParams` or `resty.R().SetQueryParam`.
  220. // resty.SetQueryParams(map[string]string{
  221. // "search": "kitchen papers",
  222. // "size": "large",
  223. // })
  224. //
  225. func (c *Client) SetQueryParams(params map[string]string) *Client {
  226. for p, v := range params {
  227. c.SetQueryParam(p, v)
  228. }
  229. return c
  230. }
  231. // SetFormData method sets Form parameters and their values in the client instance.
  232. // It's applicable only HTTP method `POST` and `PUT` and requets content type would be set as
  233. // `application/x-www-form-urlencoded`. These form data will be added to all the request raised from
  234. // this client instance. Also it can be overridden at request level form data, see `resty.R().SetFormData`.
  235. // resty.SetFormData(map[string]string{
  236. // "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F",
  237. // "user_id": "3455454545",
  238. // })
  239. //
  240. func (c *Client) SetFormData(data map[string]string) *Client {
  241. for k, v := range data {
  242. c.FormData.Set(k, v)
  243. }
  244. return c
  245. }
  246. // SetBasicAuth method sets the basic authentication header in the HTTP request. Example:
  247. // Authorization: Basic <base64-encoded-value>
  248. //
  249. // Example: To set the header for username "go-resty" and password "welcome"
  250. // resty.SetBasicAuth("go-resty", "welcome")
  251. //
  252. // This basic auth information gets added to all the request rasied from this client instance.
  253. // Also it can be overridden or set one at the request level is supported, see `resty.R().SetBasicAuth`.
  254. //
  255. func (c *Client) SetBasicAuth(username, password string) *Client {
  256. c.UserInfo = &User{Username: username, Password: password}
  257. return c
  258. }
  259. // SetAuthToken method sets bearer auth token header in the HTTP request. Example:
  260. // Authorization: Bearer <auth-token-value-comes-here>
  261. //
  262. // Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F
  263. //
  264. // resty.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F")
  265. //
  266. // This bearer auth token gets added to all the request rasied from this client instance.
  267. // Also it can be overridden or set one at the request level is supported, see `resty.R().SetAuthToken`.
  268. //
  269. func (c *Client) SetAuthToken(token string) *Client {
  270. c.Token = token
  271. return c
  272. }
  273. // R method creates a request instance, its used for Get, Post, Put, Delete, Patch, Head and Options.
  274. func (c *Client) R() *Request {
  275. r := &Request{
  276. QueryParam: url.Values{},
  277. FormData: url.Values{},
  278. Header: http.Header{},
  279. client: c,
  280. multipartFiles: []*File{},
  281. multipartFields: []*MultipartField{},
  282. pathParams: map[string]string{},
  283. jsonEscapeHTML: true,
  284. }
  285. return r
  286. }
  287. // NewRequest is an alias for R(). Creates a request instance, its used for
  288. // Get, Post, Put, Delete, Patch, Head and Options.
  289. func (c *Client) NewRequest() *Request {
  290. return c.R()
  291. }
  292. // OnBeforeRequest method appends request middleware into the before request chain.
  293. // Its gets applied after default `go-resty` request middlewares and before request
  294. // been sent from `go-resty` to host server.
  295. // resty.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
  296. // // Now you have access to Client and Request instance
  297. // // manipulate it as per your need
  298. //
  299. // return nil // if its success otherwise return error
  300. // })
  301. //
  302. func (c *Client) OnBeforeRequest(m func(*Client, *Request) error) *Client {
  303. c.udBeforeRequest = append(c.udBeforeRequest, m)
  304. return c
  305. }
  306. // OnAfterResponse method appends response middleware into the after response chain.
  307. // Once we receive response from host server, default `go-resty` response middleware
  308. // gets applied and then user assigened response middlewares applied.
  309. // resty.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
  310. // // Now you have access to Client and Response instance
  311. // // manipulate it as per your need
  312. //
  313. // return nil // if its success otherwise return error
  314. // })
  315. //
  316. func (c *Client) OnAfterResponse(m func(*Client, *Response) error) *Client {
  317. c.afterResponse = append(c.afterResponse, m)
  318. return c
  319. }
  320. // SetPreRequestHook method sets the given pre-request function into resty client.
  321. // It is called right before the request is fired.
  322. //
  323. // Note: Only one pre-request hook can be registered. Use `resty.OnBeforeRequest` for mutilple.
  324. func (c *Client) SetPreRequestHook(h func(*Client, *Request) error) *Client {
  325. if c.preReqHook != nil {
  326. c.Log.Printf("Overwriting an existing pre-request hook: %s", functionName(h))
  327. }
  328. c.preReqHook = h
  329. return c
  330. }
  331. // SetDebug method enables the debug mode on `go-resty` client. Client logs details of every request and response.
  332. // For `Request` it logs information such as HTTP verb, Relative URL path, Host, Headers, Body if it has one.
  333. // For `Response` it logs information such as Status, Response Time, Headers, Body if it has one.
  334. // resty.SetDebug(true)
  335. //
  336. func (c *Client) SetDebug(d bool) *Client {
  337. c.Debug = d
  338. return c
  339. }
  340. // SetDebugBodyLimit sets the maximum size for which the response body will be logged in debug mode.
  341. // resty.SetDebugBodyLimit(1000000)
  342. //
  343. func (c *Client) SetDebugBodyLimit(sl int64) *Client {
  344. c.debugBodySizeLimit = sl
  345. return c
  346. }
  347. // OnRequestLog method used to set request log callback into resty. Registered callback gets
  348. // called before the resty actually logs the information.
  349. func (c *Client) OnRequestLog(rl func(*RequestLog) error) *Client {
  350. if c.requestLog != nil {
  351. c.Log.Printf("Overwriting an existing on-request-log callback from=%s to=%s", functionName(c.requestLog), functionName(rl))
  352. }
  353. c.requestLog = rl
  354. return c
  355. }
  356. // OnResponseLog method used to set response log callback into resty. Registered callback gets
  357. // called before the resty actually logs the information.
  358. func (c *Client) OnResponseLog(rl func(*ResponseLog) error) *Client {
  359. if c.responseLog != nil {
  360. c.Log.Printf("Overwriting an existing on-response-log callback from=%s to=%s", functionName(c.responseLog), functionName(rl))
  361. }
  362. c.responseLog = rl
  363. return c
  364. }
  365. // SetDisableWarn method disables the warning message on `go-resty` client.
  366. // For example: go-resty warns the user when BasicAuth used on HTTP mode.
  367. // resty.SetDisableWarn(true)
  368. //
  369. func (c *Client) SetDisableWarn(d bool) *Client {
  370. c.DisableWarn = d
  371. return c
  372. }
  373. // SetAllowGetMethodPayload method allows the GET method with payload on `go-resty` client.
  374. // For example: go-resty allows the user sends request with a payload on HTTP GET method.
  375. // resty.SetAllowGetMethodPayload(true)
  376. //
  377. func (c *Client) SetAllowGetMethodPayload(a bool) *Client {
  378. c.AllowGetMethodPayload = a
  379. return c
  380. }
  381. // SetLogger method sets given writer for logging go-resty request and response details.
  382. // Default is os.Stderr
  383. // file, _ := os.OpenFile("/Users/jeeva/go-resty.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
  384. //
  385. // resty.SetLogger(file)
  386. //
  387. func (c *Client) SetLogger(w io.Writer) *Client {
  388. c.Log = getLogger(w)
  389. return c
  390. }
  391. // SetContentLength method enables the HTTP header `Content-Length` value for every request.
  392. // By default go-resty won't set `Content-Length`.
  393. // resty.SetContentLength(true)
  394. //
  395. // Also you have an option to enable for particular request. See `resty.R().SetContentLength`
  396. //
  397. func (c *Client) SetContentLength(l bool) *Client {
  398. c.setContentLength = l
  399. return c
  400. }
  401. // SetTimeout method sets timeout for request raised from client.
  402. // resty.SetTimeout(time.Duration(1 * time.Minute))
  403. //
  404. func (c *Client) SetTimeout(timeout time.Duration) *Client {
  405. c.httpClient.Timeout = timeout
  406. return c
  407. }
  408. // SetError method is to register the global or client common `Error` object into go-resty.
  409. // It is used for automatic unmarshalling if response status code is greater than 399 and
  410. // content type either JSON or XML. Can be pointer or non-pointer.
  411. // resty.SetError(&Error{})
  412. // // OR
  413. // resty.SetError(Error{})
  414. //
  415. func (c *Client) SetError(err interface{}) *Client {
  416. c.Error = typeOf(err)
  417. return c
  418. }
  419. // SetRedirectPolicy method sets the client redirect poilicy. go-resty provides ready to use
  420. // redirect policies. Wanna create one for yourself refer `redirect.go`.
  421. //
  422. // resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
  423. //
  424. // // Need multiple redirect policies together
  425. // resty.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net"))
  426. //
  427. func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client {
  428. for _, p := range policies {
  429. if _, ok := p.(RedirectPolicy); !ok {
  430. c.Log.Printf("ERORR: %v does not implement resty.RedirectPolicy (missing Apply method)",
  431. functionName(p))
  432. }
  433. }
  434. c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
  435. for _, p := range policies {
  436. if err := p.(RedirectPolicy).Apply(req, via); err != nil {
  437. return err
  438. }
  439. }
  440. return nil // looks good, go ahead
  441. }
  442. return c
  443. }
  444. // SetRetryCount method enables retry on `go-resty` client and allows you
  445. // to set no. of retry count. Resty uses a Backoff mechanism.
  446. func (c *Client) SetRetryCount(count int) *Client {
  447. c.RetryCount = count
  448. return c
  449. }
  450. // SetRetryWaitTime method sets default wait time to sleep before retrying
  451. // request.
  452. // Default is 100 milliseconds.
  453. func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client {
  454. c.RetryWaitTime = waitTime
  455. return c
  456. }
  457. // SetRetryMaxWaitTime method sets max wait time to sleep before retrying
  458. // request.
  459. // Default is 2 seconds.
  460. func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client {
  461. c.RetryMaxWaitTime = maxWaitTime
  462. return c
  463. }
  464. // AddRetryCondition method adds a retry condition function to array of functions
  465. // that are checked to determine if the request is retried. The request will
  466. // retry if any of the functions return true and error is nil.
  467. func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
  468. c.RetryConditions = append(c.RetryConditions, condition)
  469. return c
  470. }
  471. // SetHTTPMode method sets go-resty mode to 'http'
  472. func (c *Client) SetHTTPMode() *Client {
  473. return c.SetMode("http")
  474. }
  475. // SetRESTMode method sets go-resty mode to 'rest'
  476. func (c *Client) SetRESTMode() *Client {
  477. return c.SetMode("rest")
  478. }
  479. // SetMode method sets go-resty client mode to given value such as 'http' & 'rest'.
  480. // 'rest':
  481. // - No Redirect
  482. // - Automatic response unmarshal if it is JSON or XML
  483. // 'http':
  484. // - Up to 10 Redirects
  485. // - No automatic unmarshall. Response will be treated as `response.String()`
  486. //
  487. // If you want more redirects, use FlexibleRedirectPolicy
  488. // resty.SetRedirectPolicy(FlexibleRedirectPolicy(20))
  489. //
  490. func (c *Client) SetMode(mode string) *Client {
  491. // HTTP
  492. if mode == "http" {
  493. c.isHTTPMode = true
  494. c.SetRedirectPolicy(FlexibleRedirectPolicy(10))
  495. c.afterResponse = []func(*Client, *Response) error{
  496. responseLogger,
  497. saveResponseIntoFile,
  498. }
  499. return c
  500. }
  501. // RESTful
  502. c.isHTTPMode = false
  503. c.SetRedirectPolicy(NoRedirectPolicy())
  504. c.afterResponse = []func(*Client, *Response) error{
  505. responseLogger,
  506. parseResponseBody,
  507. saveResponseIntoFile,
  508. }
  509. return c
  510. }
  511. // Mode method returns the current client mode. Typically its a "http" or "rest".
  512. // Default is "rest"
  513. func (c *Client) Mode() string {
  514. if c.isHTTPMode {
  515. return "http"
  516. }
  517. return "rest"
  518. }
  519. // SetTLSClientConfig method sets TLSClientConfig for underling client Transport.
  520. //
  521. // Example:
  522. // // One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial
  523. // resty.SetTLSClientConfig(&tls.Config{ RootCAs: roots })
  524. //
  525. // // or One can disable security check (https)
  526. // resty.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true })
  527. // Note: This method overwrites existing `TLSClientConfig`.
  528. //
  529. func (c *Client) SetTLSClientConfig(config *tls.Config) *Client {
  530. transport, err := c.getTransport()
  531. if err != nil {
  532. c.Log.Printf("ERROR %v", err)
  533. return c
  534. }
  535. transport.TLSClientConfig = config
  536. return c
  537. }
  538. // SetProxy method sets the Proxy URL and Port for resty client.
  539. // resty.SetProxy("http://proxyserver:8888")
  540. //
  541. // Alternatives: At request level proxy, see `Request.SetProxy`. OR Without this `SetProxy` method,
  542. // you can also set Proxy via environment variable. By default `Go` uses setting from `HTTP_PROXY`.
  543. //
  544. func (c *Client) SetProxy(proxyURL string) *Client {
  545. transport, err := c.getTransport()
  546. if err != nil {
  547. c.Log.Printf("ERROR %v", err)
  548. return c
  549. }
  550. if pURL, err := url.Parse(proxyURL); err == nil {
  551. c.proxyURL = pURL
  552. transport.Proxy = http.ProxyURL(c.proxyURL)
  553. } else {
  554. c.Log.Printf("ERROR %v", err)
  555. c.RemoveProxy()
  556. }
  557. return c
  558. }
  559. // RemoveProxy method removes the proxy configuration from resty client
  560. // resty.RemoveProxy()
  561. //
  562. func (c *Client) RemoveProxy() *Client {
  563. transport, err := c.getTransport()
  564. if err != nil {
  565. c.Log.Printf("ERROR %v", err)
  566. return c
  567. }
  568. c.proxyURL = nil
  569. transport.Proxy = nil
  570. return c
  571. }
  572. // SetCertificates method helps to set client certificates into resty conveniently.
  573. //
  574. func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
  575. config, err := c.getTLSConfig()
  576. if err != nil {
  577. c.Log.Printf("ERROR %v", err)
  578. return c
  579. }
  580. config.Certificates = append(config.Certificates, certs...)
  581. return c
  582. }
  583. // SetRootCertificate method helps to add one or more root certificates into resty client
  584. // resty.SetRootCertificate("/path/to/root/pemFile.pem")
  585. //
  586. func (c *Client) SetRootCertificate(pemFilePath string) *Client {
  587. rootPemData, err := ioutil.ReadFile(pemFilePath)
  588. if err != nil {
  589. c.Log.Printf("ERROR %v", err)
  590. return c
  591. }
  592. config, err := c.getTLSConfig()
  593. if err != nil {
  594. c.Log.Printf("ERROR %v", err)
  595. return c
  596. }
  597. if config.RootCAs == nil {
  598. config.RootCAs = x509.NewCertPool()
  599. }
  600. config.RootCAs.AppendCertsFromPEM(rootPemData)
  601. return c
  602. }
  603. // SetOutputDirectory method sets output directory for saving HTTP response into file.
  604. // If the output directory not exists then resty creates one. This setting is optional one,
  605. // if you're planning using absoule path in `Request.SetOutput` and can used together.
  606. // resty.SetOutputDirectory("/save/http/response/here")
  607. //
  608. func (c *Client) SetOutputDirectory(dirPath string) *Client {
  609. c.outputDirectory = dirPath
  610. return c
  611. }
  612. // SetTransport method sets custom `*http.Transport` or any `http.RoundTripper`
  613. // compatible interface implementation in the resty client.
  614. //
  615. // NOTE:
  616. //
  617. // - If transport is not type of `*http.Transport` then you may not be able to
  618. // take advantage of some of the `resty` client settings.
  619. //
  620. // - It overwrites the resty client transport instance and it's configurations.
  621. //
  622. // transport := &http.Transport{
  623. // // somthing like Proxying to httptest.Server, etc...
  624. // Proxy: func(req *http.Request) (*url.URL, error) {
  625. // return url.Parse(server.URL)
  626. // },
  627. // }
  628. //
  629. // resty.SetTransport(transport)
  630. //
  631. func (c *Client) SetTransport(transport http.RoundTripper) *Client {
  632. if transport != nil {
  633. c.httpClient.Transport = transport
  634. }
  635. return c
  636. }
  637. // SetScheme method sets custom scheme in the resty client. It's way to override default.
  638. // resty.SetScheme("http")
  639. //
  640. func (c *Client) SetScheme(scheme string) *Client {
  641. if !IsStringEmpty(scheme) {
  642. c.scheme = scheme
  643. }
  644. return c
  645. }
  646. // SetCloseConnection method sets variable `Close` in http request struct with the given
  647. // value. More info: https://golang.org/src/net/http/request.go
  648. func (c *Client) SetCloseConnection(close bool) *Client {
  649. c.closeConnection = close
  650. return c
  651. }
  652. // SetDoNotParseResponse method instructs `Resty` not to parse the response body automatically.
  653. // Resty exposes the raw response body as `io.ReadCloser`. Also do not forget to close the body,
  654. // otherwise you might get into connection leaks, no connection reuse.
  655. //
  656. // Please Note: Response middlewares are not applicable, if you use this option. Basically you have
  657. // taken over the control of response parsing from `Resty`.
  658. func (c *Client) SetDoNotParseResponse(parse bool) *Client {
  659. c.notParseResponse = parse
  660. return c
  661. }
  662. // SetLogPrefix method sets the Resty logger prefix value.
  663. func (c *Client) SetLogPrefix(prefix string) *Client {
  664. c.logPrefix = prefix
  665. c.Log.SetPrefix(prefix)
  666. return c
  667. }
  668. // SetPathParams method sets multiple URL path key-value pairs at one go in the
  669. // resty client instance.
  670. // resty.SetPathParams(map[string]string{
  671. // "userId": "sample@sample.com",
  672. // "subAccountId": "100002",
  673. // })
  674. //
  675. // Result:
  676. // URL - /v1/users/{userId}/{subAccountId}/details
  677. // Composed URL - /v1/users/sample@sample.com/100002/details
  678. // It replace the value of the key while composing request URL. Also it can be
  679. // overridden at request level Path Params options, see `Request.SetPathParams`.
  680. func (c *Client) SetPathParams(params map[string]string) *Client {
  681. for p, v := range params {
  682. c.pathParams[p] = v
  683. }
  684. return c
  685. }
  686. // SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
  687. //
  688. // NOTE: This option only applicable to standard JSON Marshaller.
  689. func (c *Client) SetJSONEscapeHTML(b bool) *Client {
  690. c.jsonEscapeHTML = b
  691. return c
  692. }
  693. // IsProxySet method returns the true if proxy is set on client otherwise false.
  694. func (c *Client) IsProxySet() bool {
  695. return c.proxyURL != nil
  696. }
  697. // GetClient method returns the current http.Client used by the resty client.
  698. func (c *Client) GetClient() *http.Client {
  699. return c.httpClient
  700. }
  701. //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  702. // Client Unexported methods
  703. //___________________________________
  704. // executes the given `Request` object and returns response
  705. func (c *Client) execute(req *Request) (*Response, error) {
  706. defer releaseBuffer(req.bodyBuf)
  707. // Apply Request middleware
  708. var err error
  709. // user defined on before request methods
  710. // to modify the *resty.Request object
  711. for _, f := range c.udBeforeRequest {
  712. if err = f(c, req); err != nil {
  713. return nil, err
  714. }
  715. }
  716. // resty middlewares
  717. for _, f := range c.beforeRequest {
  718. if err = f(c, req); err != nil {
  719. return nil, err
  720. }
  721. }
  722. // call pre-request if defined
  723. if c.preReqHook != nil {
  724. if err = c.preReqHook(c, req); err != nil {
  725. return nil, err
  726. }
  727. }
  728. if hostHeader := req.Header.Get("Host"); hostHeader != "" {
  729. req.RawRequest.Host = hostHeader
  730. }
  731. if err = requestLogger(c, req); err != nil {
  732. return nil, err
  733. }
  734. req.Time = time.Now()
  735. resp, err := c.httpClient.Do(req.RawRequest)
  736. response := &Response{
  737. Request: req,
  738. RawResponse: resp,
  739. receivedAt: time.Now(),
  740. }
  741. if err != nil || req.notParseResponse || c.notParseResponse {
  742. return response, err
  743. }
  744. if !req.isSaveResponse {
  745. defer closeq(resp.Body)
  746. body := resp.Body
  747. // GitHub #142 & #187
  748. if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 {
  749. if _, ok := body.(*gzip.Reader); !ok {
  750. body, err = gzip.NewReader(body)
  751. if err != nil {
  752. return response, err
  753. }
  754. defer closeq(body)
  755. }
  756. }
  757. if response.body, err = ioutil.ReadAll(body); err != nil {
  758. return response, err
  759. }
  760. response.size = int64(len(response.body))
  761. }
  762. // Apply Response middleware
  763. for _, f := range c.afterResponse {
  764. if err = f(c, response); err != nil {
  765. break
  766. }
  767. }
  768. return response, err
  769. }
  770. // enables a log prefix
  771. func (c *Client) enableLogPrefix() {
  772. c.Log.SetFlags(log.LstdFlags)
  773. c.Log.SetPrefix(c.logPrefix)
  774. }
  775. // disables a log prefix
  776. func (c *Client) disableLogPrefix() {
  777. c.Log.SetFlags(0)
  778. c.Log.SetPrefix("")
  779. }
  780. // getting TLS client config if not exists then create one
  781. func (c *Client) getTLSConfig() (*tls.Config, error) {
  782. transport, err := c.getTransport()
  783. if err != nil {
  784. return nil, err
  785. }
  786. if transport.TLSClientConfig == nil {
  787. transport.TLSClientConfig = &tls.Config{}
  788. }
  789. return transport.TLSClientConfig, nil
  790. }
  791. // returns `*http.Transport` currently in use or error
  792. // in case currently used `transport` is not an `*http.Transport`
  793. func (c *Client) getTransport() (*http.Transport, error) {
  794. if c.httpClient.Transport == nil {
  795. c.SetTransport(new(http.Transport))
  796. }
  797. if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
  798. return transport, nil
  799. }
  800. return nil, errors.New("current transport is not an *http.Transport instance")
  801. }
  802. //
  803. // File
  804. //
  805. // File represent file information for multipart request
  806. type File struct {
  807. Name string
  808. ParamName string
  809. io.Reader
  810. }
  811. // String returns string value of current file details
  812. func (f *File) String() string {
  813. return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name)
  814. }
  815. // MultipartField represent custom data part for multipart request
  816. type MultipartField struct {
  817. Param string
  818. FileName string
  819. ContentType string
  820. io.Reader
  821. }