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 }