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