gclient_request.go 12 KB

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