ghttp_client_request.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package ghttp
  7. import (
  8. "bytes"
  9. "errors"
  10. "fmt"
  11. "github.com/gogf/gf/internal/json"
  12. "github.com/gogf/gf/internal/utils"
  13. "io"
  14. "io/ioutil"
  15. "mime/multipart"
  16. "net/http"
  17. "os"
  18. "strings"
  19. "time"
  20. "github.com/gogf/gf/encoding/gparser"
  21. "github.com/gogf/gf/text/gregex"
  22. "github.com/gogf/gf/text/gstr"
  23. "github.com/gogf/gf/util/gconv"
  24. "github.com/gogf/gf/os/gfile"
  25. )
  26. // Get send GET request and returns the response object.
  27. // Note that the response object MUST be closed if it'll be never used.
  28. func (c *Client) Get(url string, data ...interface{}) (*ClientResponse, error) {
  29. return c.DoRequest("GET", url, data...)
  30. }
  31. // Put send PUT request and returns the response object.
  32. // Note that the response object MUST be closed if it'll be never used.
  33. func (c *Client) Put(url string, data ...interface{}) (*ClientResponse, error) {
  34. return c.DoRequest("PUT", url, data...)
  35. }
  36. // Post sends request using HTTP method POST and returns the response object.
  37. // Note that the response object MUST be closed if it'll be never used.
  38. func (c *Client) Post(url string, data ...interface{}) (*ClientResponse, error) {
  39. return c.DoRequest("POST", url, data...)
  40. }
  41. // Delete send DELETE request and returns the response object.
  42. // Note that the response object MUST be closed if it'll be never used.
  43. func (c *Client) Delete(url string, data ...interface{}) (*ClientResponse, error) {
  44. return c.DoRequest("DELETE", url, data...)
  45. }
  46. // Head send HEAD request and returns the response object.
  47. // Note that the response object MUST be closed if it'll be never used.
  48. func (c *Client) Head(url string, data ...interface{}) (*ClientResponse, error) {
  49. return c.DoRequest("HEAD", url, data...)
  50. }
  51. // Patch send PATCH request and returns the response object.
  52. // Note that the response object MUST be closed if it'll be never used.
  53. func (c *Client) Patch(url string, data ...interface{}) (*ClientResponse, error) {
  54. return c.DoRequest("PATCH", url, data...)
  55. }
  56. // Connect send CONNECT request and returns the response object.
  57. // Note that the response object MUST be closed if it'll be never used.
  58. func (c *Client) Connect(url string, data ...interface{}) (*ClientResponse, error) {
  59. return c.DoRequest("CONNECT", url, data...)
  60. }
  61. // Options send OPTIONS request and returns the response object.
  62. // Note that the response object MUST be closed if it'll be never used.
  63. func (c *Client) Options(url string, data ...interface{}) (*ClientResponse, error) {
  64. return c.DoRequest("OPTIONS", url, data...)
  65. }
  66. // Trace send TRACE request and returns the response object.
  67. // Note that the response object MUST be closed if it'll be never used.
  68. func (c *Client) Trace(url string, data ...interface{}) (*ClientResponse, error) {
  69. return c.DoRequest("TRACE", url, data...)
  70. }
  71. // DoRequest sends request with given HTTP method and data and returns the response object.
  72. // Note that the response object MUST be closed if it'll be never used.
  73. //
  74. // Note that it uses "multipart/form-data" as its Content-Type if it contains file uploading,
  75. // else it uses "application/x-www-form-urlencoded". It also automatically detects the post
  76. // content for JSON format, and for that it automatically sets the Content-Type as
  77. // "application/json".
  78. func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *ClientResponse, err error) {
  79. method = strings.ToUpper(method)
  80. if len(c.prefix) > 0 {
  81. url = c.prefix + gstr.Trim(url)
  82. }
  83. param := ""
  84. if len(data) > 0 {
  85. switch c.header["Content-Type"] {
  86. case "application/json":
  87. switch data[0].(type) {
  88. case string, []byte:
  89. param = gconv.String(data[0])
  90. default:
  91. if b, err := json.Marshal(data[0]); err != nil {
  92. return nil, err
  93. } else {
  94. param = gconv.UnsafeBytesToStr(b)
  95. }
  96. }
  97. case "application/xml":
  98. switch data[0].(type) {
  99. case string, []byte:
  100. param = gconv.String(data[0])
  101. default:
  102. if b, err := gparser.VarToXml(data[0]); err != nil {
  103. return nil, err
  104. } else {
  105. param = gconv.UnsafeBytesToStr(b)
  106. }
  107. }
  108. default:
  109. param = BuildParams(data[0])
  110. }
  111. }
  112. var req *http.Request
  113. if method == "GET" {
  114. // It appends the parameters to the url if http method is GET.
  115. if param != "" {
  116. if gstr.Contains(url, "?") {
  117. url = url + "&" + param
  118. } else {
  119. url = url + "?" + param
  120. }
  121. }
  122. if req, err = http.NewRequest(method, url, bytes.NewBuffer(nil)); err != nil {
  123. return nil, err
  124. }
  125. } else {
  126. if strings.Contains(param, "@file:") {
  127. // File uploading request.
  128. buffer := new(bytes.Buffer)
  129. writer := multipart.NewWriter(buffer)
  130. for _, item := range strings.Split(param, "&") {
  131. array := strings.Split(item, "=")
  132. if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 {
  133. path := array[1][6:]
  134. if !gfile.Exists(path) {
  135. return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
  136. }
  137. if file, err := writer.CreateFormFile(array[0], gfile.Basename(path)); err == nil {
  138. if f, err := os.Open(path); err == nil {
  139. if _, err = io.Copy(file, f); err != nil {
  140. f.Close()
  141. return nil, err
  142. }
  143. f.Close()
  144. } else {
  145. return nil, err
  146. }
  147. } else {
  148. return nil, err
  149. }
  150. } else {
  151. if err = writer.WriteField(array[0], array[1]); err != nil {
  152. return nil, err
  153. }
  154. }
  155. }
  156. // Close finishes the multipart message and writes the trailing
  157. // boundary end line to the output.
  158. if err = writer.Close(); err != nil {
  159. return nil, err
  160. }
  161. if req, err = http.NewRequest(method, url, buffer); err != nil {
  162. return nil, err
  163. } else {
  164. req.Header.Set("Content-Type", writer.FormDataContentType())
  165. }
  166. } else {
  167. // Normal request.
  168. paramBytes := []byte(param)
  169. if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
  170. return nil, err
  171. } else {
  172. if v, ok := c.header["Content-Type"]; ok {
  173. // Custom Content-Type.
  174. req.Header.Set("Content-Type", v)
  175. } else if len(paramBytes) > 0 {
  176. if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
  177. // Auto detecting and setting the post content format: JSON.
  178. req.Header.Set("Content-Type", "application/json")
  179. } else if gregex.IsMatchString(`^[\w\[\]]+=.+`, param) {
  180. // If the parameters passed like "name=value", it then uses form type.
  181. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  182. }
  183. }
  184. }
  185. }
  186. }
  187. // Context.
  188. if c.ctx != nil {
  189. req = req.WithContext(c.ctx)
  190. }
  191. // Custom header.
  192. if len(c.header) > 0 {
  193. for k, v := range c.header {
  194. req.Header.Set(k, v)
  195. }
  196. }
  197. // It's necessary set the req.Host if you want to custom the host value of the request.
  198. // It uses the "Host" value from header if it's not set in the request.
  199. if host := req.Header.Get("Host"); host != "" && req.Host == "" {
  200. req.Host = host
  201. }
  202. // Custom Cookie.
  203. if len(c.cookies) > 0 {
  204. headerCookie := ""
  205. for k, v := range c.cookies {
  206. if len(headerCookie) > 0 {
  207. headerCookie += ";"
  208. }
  209. headerCookie += k + "=" + v
  210. }
  211. if len(headerCookie) > 0 {
  212. req.Header.Set("Cookie", headerCookie)
  213. }
  214. }
  215. // HTTP basic authentication.
  216. if len(c.authUser) > 0 {
  217. req.SetBasicAuth(c.authUser, c.authPass)
  218. }
  219. resp = &ClientResponse{
  220. request: req,
  221. }
  222. // The request body can be reused for dumping
  223. // raw HTTP request-response procedure.
  224. reqBodyContent, _ := ioutil.ReadAll(req.Body)
  225. resp.requestBody = reqBodyContent
  226. req.Body = utils.NewReadCloser(reqBodyContent, false)
  227. for {
  228. if resp.Response, err = c.Do(req); err != nil {
  229. // The response might not be nil when err != nil.
  230. if resp.Response != nil {
  231. resp.Response.Body.Close()
  232. }
  233. if c.retryCount > 0 {
  234. c.retryCount--
  235. time.Sleep(c.retryInterval)
  236. } else {
  237. return resp, err
  238. }
  239. } else {
  240. break
  241. }
  242. }
  243. // Auto saving cookie content.
  244. if c.browserMode {
  245. now := time.Now()
  246. for _, v := range resp.Response.Cookies() {
  247. if !v.Expires.IsZero() && v.Expires.UnixNano() < now.UnixNano() {
  248. delete(c.cookies, v.Name)
  249. } else {
  250. c.cookies[v.Name] = v.Value
  251. }
  252. }
  253. }
  254. return resp, nil
  255. }