github.com/wangyougui/gf/v2@v2.6.5/net/gclient/gclient_request.go (about)

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