middleware.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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. "encoding/xml"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "mime/multipart"
  12. "net/http"
  13. "net/url"
  14. "os"
  15. "path/filepath"
  16. "reflect"
  17. "strings"
  18. "time"
  19. )
  20. //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  21. // Request Middleware(s)
  22. //___________________________________
  23. func parseRequestURL(c *Client, r *Request) error {
  24. // GitHub #103 Path Params
  25. if len(r.pathParams) > 0 {
  26. for p, v := range r.pathParams {
  27. r.URL = strings.Replace(r.URL, "{"+p+"}", url.PathEscape(v), -1)
  28. }
  29. }
  30. if len(c.pathParams) > 0 {
  31. for p, v := range c.pathParams {
  32. r.URL = strings.Replace(r.URL, "{"+p+"}", url.PathEscape(v), -1)
  33. }
  34. }
  35. // Parsing request URL
  36. reqURL, err := url.Parse(r.URL)
  37. if err != nil {
  38. return err
  39. }
  40. // If Request.URL is relative path then added c.HostURL into
  41. // the request URL otherwise Request.URL will be used as-is
  42. if !reqURL.IsAbs() {
  43. r.URL = reqURL.String()
  44. if len(r.URL) > 0 && r.URL[0] != '/' {
  45. r.URL = "/" + r.URL
  46. }
  47. reqURL, err = url.Parse(c.HostURL + r.URL)
  48. if err != nil {
  49. return err
  50. }
  51. }
  52. // Adding Query Param
  53. query := make(url.Values)
  54. for k, v := range c.QueryParam {
  55. for _, iv := range v {
  56. query.Add(k, iv)
  57. }
  58. }
  59. for k, v := range r.QueryParam {
  60. // remove query param from client level by key
  61. // since overrides happens for that key in the request
  62. query.Del(k)
  63. for _, iv := range v {
  64. query.Add(k, iv)
  65. }
  66. }
  67. // GitHub #123 Preserve query string order partially.
  68. // Since not feasible in `SetQuery*` resty methods, because
  69. // standard package `url.Encode(...)` sorts the query params
  70. // alphabetically
  71. if len(query) > 0 {
  72. if IsStringEmpty(reqURL.RawQuery) {
  73. reqURL.RawQuery = query.Encode()
  74. } else {
  75. reqURL.RawQuery = reqURL.RawQuery + "&" + query.Encode()
  76. }
  77. }
  78. r.URL = reqURL.String()
  79. return nil
  80. }
  81. func parseRequestHeader(c *Client, r *Request) error {
  82. hdr := make(http.Header)
  83. for k := range c.Header {
  84. hdr[k] = append(hdr[k], c.Header[k]...)
  85. }
  86. for k := range r.Header {
  87. hdr.Del(k)
  88. hdr[k] = append(hdr[k], r.Header[k]...)
  89. }
  90. if IsStringEmpty(hdr.Get(hdrUserAgentKey)) {
  91. hdr.Set(hdrUserAgentKey, fmt.Sprintf(hdrUserAgentValue, Version))
  92. }
  93. ct := hdr.Get(hdrContentTypeKey)
  94. if IsStringEmpty(hdr.Get(hdrAcceptKey)) && !IsStringEmpty(ct) &&
  95. (IsJSONType(ct) || IsXMLType(ct)) {
  96. hdr.Set(hdrAcceptKey, hdr.Get(hdrContentTypeKey))
  97. }
  98. r.Header = hdr
  99. return nil
  100. }
  101. func parseRequestBody(c *Client, r *Request) (err error) {
  102. if isPayloadSupported(r.Method, c.AllowGetMethodPayload) {
  103. // Handling Multipart
  104. if r.isMultiPart && !(r.Method == MethodPatch) {
  105. if err = handleMultipart(c, r); err != nil {
  106. return
  107. }
  108. goto CL
  109. }
  110. // Handling Form Data
  111. if len(c.FormData) > 0 || len(r.FormData) > 0 {
  112. handleFormData(c, r)
  113. goto CL
  114. }
  115. // Handling Request body
  116. if r.Body != nil {
  117. handleContentType(c, r)
  118. if err = handleRequestBody(c, r); err != nil {
  119. return
  120. }
  121. }
  122. }
  123. CL:
  124. // by default resty won't set content length, you can if you want to :)
  125. if (c.setContentLength || r.setContentLength) && r.bodyBuf != nil {
  126. r.Header.Set(hdrContentLengthKey, fmt.Sprintf("%d", r.bodyBuf.Len()))
  127. }
  128. return
  129. }
  130. func createHTTPRequest(c *Client, r *Request) (err error) {
  131. if r.bodyBuf == nil {
  132. if reader, ok := r.Body.(io.Reader); ok {
  133. r.RawRequest, err = http.NewRequest(r.Method, r.URL, reader)
  134. } else {
  135. r.RawRequest, err = http.NewRequest(r.Method, r.URL, nil)
  136. }
  137. } else {
  138. r.RawRequest, err = http.NewRequest(r.Method, r.URL, r.bodyBuf)
  139. }
  140. if err != nil {
  141. return
  142. }
  143. // Assign close connection option
  144. r.RawRequest.Close = c.closeConnection
  145. // Add headers into http request
  146. r.RawRequest.Header = r.Header
  147. // Add cookies into http request
  148. for _, cookie := range c.Cookies {
  149. r.RawRequest.AddCookie(cookie)
  150. }
  151. // it's for non-http scheme option
  152. if r.RawRequest.URL != nil && r.RawRequest.URL.Scheme == "" {
  153. r.RawRequest.URL.Scheme = c.scheme
  154. r.RawRequest.URL.Host = r.URL
  155. }
  156. // Use context if it was specified
  157. r.addContextIfAvailable()
  158. return
  159. }
  160. func addCredentials(c *Client, r *Request) error {
  161. var isBasicAuth bool
  162. // Basic Auth
  163. if r.UserInfo != nil { // takes precedence
  164. r.RawRequest.SetBasicAuth(r.UserInfo.Username, r.UserInfo.Password)
  165. isBasicAuth = true
  166. } else if c.UserInfo != nil {
  167. r.RawRequest.SetBasicAuth(c.UserInfo.Username, c.UserInfo.Password)
  168. isBasicAuth = true
  169. }
  170. if !c.DisableWarn {
  171. if isBasicAuth && !strings.HasPrefix(r.URL, "https") {
  172. c.Log.Println("WARNING - Using Basic Auth in HTTP mode is not secure.")
  173. }
  174. }
  175. // Token Auth
  176. if !IsStringEmpty(r.Token) { // takes precedence
  177. r.RawRequest.Header.Set(hdrAuthorizationKey, "Bearer "+r.Token)
  178. } else if !IsStringEmpty(c.Token) {
  179. r.RawRequest.Header.Set(hdrAuthorizationKey, "Bearer "+c.Token)
  180. }
  181. return nil
  182. }
  183. func requestLogger(c *Client, r *Request) error {
  184. if c.Debug {
  185. rr := r.RawRequest
  186. rl := &RequestLog{Header: copyHeaders(rr.Header), Body: r.fmtBodyString()}
  187. if c.requestLog != nil {
  188. if err := c.requestLog(rl); err != nil {
  189. return err
  190. }
  191. }
  192. reqLog := "\n---------------------- REQUEST LOG -----------------------\n" +
  193. fmt.Sprintf("%s %s %s\n", r.Method, rr.URL.RequestURI(), rr.Proto) +
  194. fmt.Sprintf("HOST : %s\n", rr.URL.Host) +
  195. fmt.Sprintf("HEADERS:\n") +
  196. composeHeaders(rl.Header) + "\n" +
  197. fmt.Sprintf("BODY :\n%v\n", rl.Body) +
  198. "----------------------------------------------------------\n"
  199. c.Log.Print(reqLog)
  200. }
  201. return nil
  202. }
  203. //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  204. // Response Middleware(s)
  205. //___________________________________
  206. func responseLogger(c *Client, res *Response) error {
  207. if c.Debug {
  208. rl := &ResponseLog{Header: copyHeaders(res.Header()), Body: res.fmtBodyString(c.debugBodySizeLimit)}
  209. if c.responseLog != nil {
  210. if err := c.responseLog(rl); err != nil {
  211. return err
  212. }
  213. }
  214. resLog := "\n---------------------- RESPONSE LOG -----------------------\n" +
  215. fmt.Sprintf("STATUS : %s\n", res.Status()) +
  216. fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) +
  217. fmt.Sprintf("RESPONSE TIME : %v\n", res.Time()) +
  218. "HEADERS:\n" +
  219. composeHeaders(rl.Header) + "\n"
  220. if res.Request.isSaveResponse {
  221. resLog += fmt.Sprintf("BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n")
  222. } else {
  223. resLog += fmt.Sprintf("BODY :\n%v\n", rl.Body)
  224. }
  225. resLog += "----------------------------------------------------------\n"
  226. c.Log.Print(resLog)
  227. }
  228. return nil
  229. }
  230. func parseResponseBody(c *Client, res *Response) (err error) {
  231. if res.StatusCode() == http.StatusNoContent {
  232. return
  233. }
  234. // Handles only JSON or XML content type
  235. ct := firstNonEmpty(res.Header().Get(hdrContentTypeKey), res.Request.fallbackContentType)
  236. if IsJSONType(ct) || IsXMLType(ct) {
  237. // HTTP status code > 199 and < 300, considered as Result
  238. if res.IsSuccess() {
  239. if res.Request.Result != nil {
  240. err = Unmarshalc(c, ct, res.body, res.Request.Result)
  241. return
  242. }
  243. }
  244. // HTTP status code > 399, considered as Error
  245. if res.IsError() {
  246. // global error interface
  247. if res.Request.Error == nil && c.Error != nil {
  248. res.Request.Error = reflect.New(c.Error).Interface()
  249. }
  250. if res.Request.Error != nil {
  251. err = Unmarshalc(c, ct, res.body, res.Request.Error)
  252. }
  253. }
  254. }
  255. return
  256. }
  257. func handleMultipart(c *Client, r *Request) (err error) {
  258. r.bodyBuf = acquireBuffer()
  259. w := multipart.NewWriter(r.bodyBuf)
  260. for k, v := range c.FormData {
  261. for _, iv := range v {
  262. if err = w.WriteField(k, iv); err != nil {
  263. return err
  264. }
  265. }
  266. }
  267. for k, v := range r.FormData {
  268. for _, iv := range v {
  269. if strings.HasPrefix(k, "@") { // file
  270. err = addFile(w, k[1:], iv)
  271. if err != nil {
  272. return
  273. }
  274. } else { // form value
  275. if err = w.WriteField(k, iv); err != nil {
  276. return err
  277. }
  278. }
  279. }
  280. }
  281. // #21 - adding io.Reader support
  282. if len(r.multipartFiles) > 0 {
  283. for _, f := range r.multipartFiles {
  284. err = addFileReader(w, f)
  285. if err != nil {
  286. return
  287. }
  288. }
  289. }
  290. // GitHub #130 adding multipart field support with content type
  291. if len(r.multipartFields) > 0 {
  292. for _, mf := range r.multipartFields {
  293. if err = addMultipartFormField(w, mf); err != nil {
  294. return
  295. }
  296. }
  297. }
  298. r.Header.Set(hdrContentTypeKey, w.FormDataContentType())
  299. err = w.Close()
  300. return
  301. }
  302. func handleFormData(c *Client, r *Request) {
  303. formData := url.Values{}
  304. for k, v := range c.FormData {
  305. for _, iv := range v {
  306. formData.Add(k, iv)
  307. }
  308. }
  309. for k, v := range r.FormData {
  310. // remove form data field from client level by key
  311. // since overrides happens for that key in the request
  312. formData.Del(k)
  313. for _, iv := range v {
  314. formData.Add(k, iv)
  315. }
  316. }
  317. r.bodyBuf = bytes.NewBuffer([]byte(formData.Encode()))
  318. r.Header.Set(hdrContentTypeKey, formContentType)
  319. r.isFormData = true
  320. }
  321. func handleContentType(c *Client, r *Request) {
  322. contentType := r.Header.Get(hdrContentTypeKey)
  323. if IsStringEmpty(contentType) {
  324. contentType = DetectContentType(r.Body)
  325. r.Header.Set(hdrContentTypeKey, contentType)
  326. }
  327. }
  328. func handleRequestBody(c *Client, r *Request) (err error) {
  329. var bodyBytes []byte
  330. contentType := r.Header.Get(hdrContentTypeKey)
  331. kind := kindOf(r.Body)
  332. r.bodyBuf = nil
  333. if reader, ok := r.Body.(io.Reader); ok {
  334. if c.setContentLength || r.setContentLength { // keep backward compability
  335. r.bodyBuf = acquireBuffer()
  336. _, err = r.bodyBuf.ReadFrom(reader)
  337. r.Body = nil
  338. } else {
  339. // Otherwise buffer less processing for `io.Reader`, sounds good.
  340. return
  341. }
  342. } else if b, ok := r.Body.([]byte); ok {
  343. bodyBytes = b
  344. } else if s, ok := r.Body.(string); ok {
  345. bodyBytes = []byte(s)
  346. } else if IsJSONType(contentType) &&
  347. (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
  348. bodyBytes, err = jsonMarshal(c, r, r.Body)
  349. } else if IsXMLType(contentType) && (kind == reflect.Struct) {
  350. bodyBytes, err = xml.Marshal(r.Body)
  351. }
  352. if bodyBytes == nil && r.bodyBuf == nil {
  353. err = errors.New("unsupported 'Body' type/value")
  354. }
  355. // if any errors during body bytes handling, return it
  356. if err != nil {
  357. return
  358. }
  359. // []byte into Buffer
  360. if bodyBytes != nil && r.bodyBuf == nil {
  361. r.bodyBuf = acquireBuffer()
  362. _, _ = r.bodyBuf.Write(bodyBytes)
  363. }
  364. return
  365. }
  366. func saveResponseIntoFile(c *Client, res *Response) error {
  367. if res.Request.isSaveResponse {
  368. file := ""
  369. if len(c.outputDirectory) > 0 && !filepath.IsAbs(res.Request.outputFile) {
  370. file += c.outputDirectory + string(filepath.Separator)
  371. }
  372. file = filepath.Clean(file + res.Request.outputFile)
  373. if err := createDirectory(filepath.Dir(file)); err != nil {
  374. return err
  375. }
  376. outFile, err := os.Create(file)
  377. if err != nil {
  378. return err
  379. }
  380. defer closeq(outFile)
  381. // io.Copy reads maximum 32kb size, it is perfect for large file download too
  382. defer closeq(res.RawResponse.Body)
  383. written, err := io.Copy(outFile, res.RawResponse.Body)
  384. if err != nil {
  385. return err
  386. }
  387. res.size = written
  388. }
  389. return nil
  390. }