gclient_request.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // Copyright GoFrame Author(https://goframe.org). 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 gclient
  7. import (
  8. "bytes"
  9. "context"
  10. "io"
  11. "io/ioutil"
  12. "mime/multipart"
  13. "net/http"
  14. "net/url"
  15. "os"
  16. "strings"
  17. "time"
  18. "github.com/gogf/gf/v2/encoding/gjson"
  19. "github.com/gogf/gf/v2/errors/gcode"
  20. "github.com/gogf/gf/v2/errors/gerror"
  21. "github.com/gogf/gf/v2/internal/httputil"
  22. "github.com/gogf/gf/v2/internal/json"
  23. "github.com/gogf/gf/v2/internal/utils"
  24. "github.com/gogf/gf/v2/os/gfile"
  25. "github.com/gogf/gf/v2/text/gregex"
  26. "github.com/gogf/gf/v2/text/gstr"
  27. "github.com/gogf/gf/v2/util/gconv"
  28. )
  29. // Get send GET request and returns the response object.
  30. // Note that the response object MUST be closed if it'll never be used.
  31. func (c *Client) Get(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  32. return c.DoRequest(ctx, http.MethodGet, url, data...)
  33. }
  34. // Put send PUT request and returns the response object.
  35. // Note that the response object MUST be closed if it'll never be used.
  36. func (c *Client) Put(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  37. return c.DoRequest(ctx, http.MethodPut, url, data...)
  38. }
  39. // Post sends request using HTTP method POST and returns the response object.
  40. // Note that the response object MUST be closed if it'll never be used.
  41. func (c *Client) Post(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  42. return c.DoRequest(ctx, http.MethodPost, url, data...)
  43. }
  44. // Delete send DELETE request and returns the response object.
  45. // Note that the response object MUST be closed if it'll never be used.
  46. func (c *Client) Delete(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  47. return c.DoRequest(ctx, http.MethodDelete, url, data...)
  48. }
  49. // Head send HEAD request and returns the response object.
  50. // Note that the response object MUST be closed if it'll never be used.
  51. func (c *Client) Head(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  52. return c.DoRequest(ctx, http.MethodHead, url, data...)
  53. }
  54. // Patch send PATCH request and returns the response object.
  55. // Note that the response object MUST be closed if it'll never be used.
  56. func (c *Client) Patch(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  57. return c.DoRequest(ctx, http.MethodPatch, url, data...)
  58. }
  59. // Connect send CONNECT request and returns the response object.
  60. // Note that the response object MUST be closed if it'll never be used.
  61. func (c *Client) Connect(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  62. return c.DoRequest(ctx, http.MethodConnect, url, data...)
  63. }
  64. // Options send OPTIONS request and returns the response object.
  65. // Note that the response object MUST be closed if it'll never be used.
  66. func (c *Client) Options(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  67. return c.DoRequest(ctx, http.MethodOptions, url, data...)
  68. }
  69. // Trace send TRACE request and returns the response object.
  70. // Note that the response object MUST be closed if it'll never be used.
  71. func (c *Client) Trace(ctx context.Context, url string, data ...interface{}) (*Response, error) {
  72. return c.DoRequest(ctx, http.MethodTrace, url, data...)
  73. }
  74. // PostForm issues a POST to the specified URL,
  75. // with data's keys and values URL-encoded as the request body.
  76. //
  77. // The Content-Type header is set to application/x-www-form-urlencoded.
  78. // To set other headers, use NewRequest and Client.Do.
  79. //
  80. // When err is nil, resp always contains a non-nil resp.Body.
  81. // Caller should close resp.Body when done reading from it.
  82. //
  83. // See the Client.Do method documentation for details on how redirects
  84. // are handled.
  85. //
  86. // To make a request with a specified context.Context, use NewRequestWithContext
  87. // and Client.Do.
  88. // Deprecated: use Post instead.
  89. func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
  90. return nil, gerror.NewCode(
  91. gcode.CodeNotSupported,
  92. `PostForm is not supported, please use Post instead`,
  93. )
  94. }
  95. // DoRequest sends request with given HTTP method and data and returns the response object.
  96. // Note that the response object MUST be closed if it'll never be used.
  97. //
  98. // Note that it uses "multipart/form-data" as its Content-Type if it contains file uploading,
  99. // else it uses "application/x-www-form-urlencoded". It also automatically detects the post
  100. // content for JSON format, and for that it automatically sets the Content-Type as
  101. // "application/json".
  102. func (c *Client) DoRequest(ctx context.Context, method, url string, data ...interface{}) (resp *Response, err error) {
  103. req, err := c.prepareRequest(ctx, method, url, data...)
  104. if err != nil {
  105. return nil, err
  106. }
  107. // Client middleware.
  108. if len(c.middlewareHandler) > 0 {
  109. mdlHandlers := make([]HandlerFunc, 0, len(c.middlewareHandler)+1)
  110. mdlHandlers = append(mdlHandlers, c.middlewareHandler...)
  111. mdlHandlers = append(mdlHandlers, func(cli *Client, r *http.Request) (*Response, error) {
  112. return cli.callRequest(r)
  113. })
  114. ctx = context.WithValue(req.Context(), clientMiddlewareKey, &clientMiddleware{
  115. client: c,
  116. handlers: mdlHandlers,
  117. handlerIndex: -1,
  118. })
  119. req = req.WithContext(ctx)
  120. resp, err = c.Next(req)
  121. } else {
  122. resp, err = c.callRequest(req)
  123. }
  124. return resp, err
  125. }
  126. // prepareRequest verifies request parameters, builds and returns http request.
  127. func (c *Client) prepareRequest(ctx context.Context, method, url string, data ...interface{}) (req *http.Request, err error) {
  128. method = strings.ToUpper(method)
  129. if len(c.prefix) > 0 {
  130. url = c.prefix + gstr.Trim(url)
  131. }
  132. if !gstr.ContainsI(url, httpProtocolName) {
  133. url = httpProtocolName + `://` + url
  134. }
  135. var params string
  136. if len(data) > 0 {
  137. switch c.header[httpHeaderContentType] {
  138. case httpHeaderContentTypeJson:
  139. switch data[0].(type) {
  140. case string, []byte:
  141. params = gconv.String(data[0])
  142. default:
  143. if b, err := json.Marshal(data[0]); err != nil {
  144. return nil, err
  145. } else {
  146. params = string(b)
  147. }
  148. }
  149. case httpHeaderContentTypeXml:
  150. switch data[0].(type) {
  151. case string, []byte:
  152. params = gconv.String(data[0])
  153. default:
  154. if b, err := gjson.New(data[0]).ToXml(); err != nil {
  155. return nil, err
  156. } else {
  157. params = string(b)
  158. }
  159. }
  160. default:
  161. params = httputil.BuildParams(data[0])
  162. }
  163. }
  164. if method == http.MethodGet {
  165. var bodyBuffer *bytes.Buffer
  166. if params != "" {
  167. switch c.header[httpHeaderContentType] {
  168. case
  169. httpHeaderContentTypeJson,
  170. httpHeaderContentTypeXml:
  171. bodyBuffer = bytes.NewBuffer([]byte(params))
  172. default:
  173. // It appends the parameters to the url
  174. // if http method is GET and Content-Type is not specified.
  175. if gstr.Contains(url, "?") {
  176. url = url + "&" + params
  177. } else {
  178. url = url + "?" + params
  179. }
  180. bodyBuffer = bytes.NewBuffer(nil)
  181. }
  182. } else {
  183. bodyBuffer = bytes.NewBuffer(nil)
  184. }
  185. if req, err = http.NewRequest(method, url, bodyBuffer); err != nil {
  186. err = gerror.Wrapf(err, `http.NewRequest failed with method "%s" and URL "%s"`, method, url)
  187. return nil, err
  188. }
  189. } else {
  190. if strings.Contains(params, httpParamFileHolder) {
  191. // File uploading request.
  192. var (
  193. buffer = bytes.NewBuffer(nil)
  194. writer = multipart.NewWriter(buffer)
  195. )
  196. for _, item := range strings.Split(params, "&") {
  197. array := strings.Split(item, "=")
  198. if len(array[1]) > 6 && strings.Compare(array[1][0:6], httpParamFileHolder) == 0 {
  199. path := array[1][6:]
  200. if !gfile.Exists(path) {
  201. return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path)
  202. }
  203. var (
  204. file io.Writer
  205. formFileName = gfile.Basename(path)
  206. formFieldName = array[0]
  207. )
  208. if file, err = writer.CreateFormFile(formFieldName, formFileName); err != nil {
  209. err = gerror.Wrapf(err, `CreateFormFile failed with "%s", "%s"`, formFieldName, formFileName)
  210. return nil, err
  211. } else {
  212. var f *os.File
  213. if f, err = gfile.Open(path); err != nil {
  214. return nil, err
  215. }
  216. if _, err = io.Copy(file, f); err != nil {
  217. err = gerror.Wrapf(err, `io.Copy failed from "%s" to form "%s"`, path, formFieldName)
  218. _ = f.Close()
  219. return nil, err
  220. }
  221. _ = f.Close()
  222. }
  223. } else {
  224. var (
  225. fieldName = array[0]
  226. fieldValue = array[1]
  227. )
  228. if err = writer.WriteField(fieldName, fieldValue); err != nil {
  229. err = gerror.Wrapf(err, `write form field failed with "%s", "%s"`, fieldName, fieldValue)
  230. return nil, err
  231. }
  232. }
  233. }
  234. // Close finishes the multipart message and writes the trailing
  235. // boundary end line to the output.
  236. if err = writer.Close(); err != nil {
  237. err = gerror.Wrapf(err, `form writer close failed`)
  238. return nil, err
  239. }
  240. if req, err = http.NewRequest(method, url, buffer); err != nil {
  241. err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url)
  242. return nil, err
  243. } else {
  244. req.Header.Set(httpHeaderContentType, writer.FormDataContentType())
  245. }
  246. } else {
  247. // Normal request.
  248. paramBytes := []byte(params)
  249. if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
  250. err = gerror.Wrapf(err, `http.NewRequest failed for method "%s" and URL "%s"`, method, url)
  251. return nil, err
  252. } else {
  253. if v, ok := c.header[httpHeaderContentType]; ok {
  254. // Custom Content-Type.
  255. req.Header.Set(httpHeaderContentType, v)
  256. } else if len(paramBytes) > 0 {
  257. if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
  258. // Auto-detecting and setting the post content format: JSON.
  259. req.Header.Set(httpHeaderContentType, httpHeaderContentTypeJson)
  260. } else if gregex.IsMatchString(httpRegexParamJson, params) {
  261. // If the parameters passed like "name=value", it then uses form type.
  262. req.Header.Set(httpHeaderContentType, httpHeaderContentTypeForm)
  263. }
  264. }
  265. }
  266. }
  267. }
  268. // Context.
  269. if ctx != nil {
  270. req = req.WithContext(ctx)
  271. }
  272. // Custom header.
  273. if len(c.header) > 0 {
  274. for k, v := range c.header {
  275. req.Header.Set(k, v)
  276. }
  277. }
  278. // It's necessary set the req.Host if you want to custom the host value of the request.
  279. // It uses the "Host" value from header if it's not empty.
  280. if reqHeaderHost := req.Header.Get(httpHeaderHost); reqHeaderHost != "" {
  281. req.Host = reqHeaderHost
  282. }
  283. // Custom Cookie.
  284. if len(c.cookies) > 0 {
  285. headerCookie := ""
  286. for k, v := range c.cookies {
  287. if len(headerCookie) > 0 {
  288. headerCookie += ";"
  289. }
  290. headerCookie += k + "=" + v
  291. }
  292. if len(headerCookie) > 0 {
  293. req.Header.Set(httpHeaderCookie, headerCookie)
  294. }
  295. }
  296. // HTTP basic authentication.
  297. if len(c.authUser) > 0 {
  298. req.SetBasicAuth(c.authUser, c.authPass)
  299. }
  300. return req, nil
  301. }
  302. // callRequest sends request with give http.Request, and returns the responses object.
  303. // Note that the response object MUST be closed if it'll never be used.
  304. func (c *Client) callRequest(req *http.Request) (resp *Response, err error) {
  305. resp = &Response{
  306. request: req,
  307. }
  308. // Dump feature.
  309. // The request body can be reused for dumping
  310. // raw HTTP request-response procedure.
  311. reqBodyContent, _ := ioutil.ReadAll(req.Body)
  312. resp.requestBody = reqBodyContent
  313. req.Body = utils.NewReadCloser(reqBodyContent, false)
  314. for {
  315. if resp.Response, err = c.Do(req); err != nil {
  316. err = gerror.Wrapf(err, `request failed`)
  317. // The response might not be nil when err != nil.
  318. if resp.Response != nil {
  319. _ = resp.Response.Body.Close()
  320. }
  321. if c.retryCount > 0 {
  322. c.retryCount--
  323. time.Sleep(c.retryInterval)
  324. } else {
  325. // return resp, err
  326. break
  327. }
  328. } else {
  329. break
  330. }
  331. }
  332. return resp, err
  333. }