github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/util.go (about) 1 // Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. 2 // resty source code and usage is governed by a MIT style 3 // license that can be found in the LICENSE file. 4 5 package resty 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "log" 12 "mime/multipart" 13 "net/http" 14 "net/textproto" 15 "os" 16 "path/filepath" 17 "reflect" 18 "runtime" 19 "sort" 20 "strings" 21 "sync" 22 ) 23 24 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 25 // Logger interface 26 //_______________________________________________________________________ 27 28 // Logger interface is to abstract the logging from Resty. Gives control to 29 // the Resty users, choice of the logger. 30 type Logger interface { 31 Errorf(format string, v ...interface{}) 32 Warnf(format string, v ...interface{}) 33 Debugf(format string, v ...interface{}) 34 } 35 36 func createLogger() *logger { 37 l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)} 38 return l 39 } 40 41 var _ Logger = (*logger)(nil) 42 43 type logger struct { 44 l *log.Logger 45 } 46 47 func (l *logger) Errorf(format string, v ...interface{}) { 48 l.output("ERROR RESTY "+format, v...) 49 } 50 51 func (l *logger) Warnf(format string, v ...interface{}) { 52 l.output("WARN RESTY "+format, v...) 53 } 54 55 func (l *logger) Debugf(format string, v ...interface{}) { 56 l.output("DEBUG RESTY "+format, v...) 57 } 58 59 func (l *logger) output(format string, v ...interface{}) { 60 if len(v) == 0 { 61 l.l.Print(format) 62 return 63 } 64 l.l.Printf(format, v...) 65 } 66 67 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 68 // Package Helper methods 69 //_______________________________________________________________________ 70 71 // IsStringEmpty method tells whether given string is empty or not 72 func IsStringEmpty(str string) bool { 73 return len(strings.TrimSpace(str)) == 0 74 } 75 76 // DetectContentType method is used to figure out `Request.Body` content type for request header 77 func DetectContentType(body interface{}) string { 78 contentType := plainTextType 79 kind := kindOf(body) 80 switch kind { 81 case reflect.Struct, reflect.Map: 82 contentType = jsonContentType 83 case reflect.String: 84 contentType = plainTextType 85 default: 86 if b, ok := body.([]byte); ok { 87 contentType = http.DetectContentType(b) 88 } else if kind == reflect.Slice { 89 contentType = jsonContentType 90 } 91 } 92 93 return contentType 94 } 95 96 // IsJSONType method is to check JSON content type or not 97 func IsJSONType(ct string) bool { return jsonCheck.MatchString(ct) } 98 99 // IsXMLType method is to check XML content type or not 100 func IsXMLType(ct string) bool { return xmlCheck.MatchString(ct) } 101 102 // Unmarshalc content into object from JSON or XML 103 func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) { 104 if IsJSONType(ct) { 105 err = c.JSONUnmarshal(b, d) 106 } else if IsXMLType(ct) { 107 err = c.XMLUnmarshal(b, d) 108 } 109 110 return 111 } 112 113 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 114 // RequestLog and ResponseLog type 115 //_______________________________________________________________________ 116 117 // RequestLog struct is used to collected information from resty request 118 // instance for debug logging. It sent to request log callback before resty 119 // actually logs the information. 120 type RequestLog struct { 121 Header http.Header 122 Body string 123 } 124 125 // ResponseLog struct is used to collected information from resty response 126 // instance for debug logging. It sent to response log callback before resty 127 // actually logs the information. 128 type ResponseLog struct { 129 Header http.Header 130 Body string 131 } 132 133 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 134 // Package Unexported methods 135 //_______________________________________________________________________ 136 137 // way to disable the HTML escape as opt-in 138 func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) { 139 if !r.jsonEscapeHTML || !c.jsonEscapeHTML { 140 return noescapeJSONMarshal(d) 141 } 142 143 data, err := c.JSONMarshal(d) 144 if err != nil { 145 return nil, err 146 } 147 148 buf := acquireBuffer() 149 _, _ = buf.Write(data) 150 return buf, nil 151 } 152 153 func firstNonEmpty(v ...string) string { 154 for _, s := range v { 155 if !IsStringEmpty(s) { 156 return s 157 } 158 } 159 return "" 160 } 161 162 var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") 163 164 func escapeQuotes(s string) string { 165 return quoteEscaper.Replace(s) 166 } 167 168 func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader { 169 hdr := make(textproto.MIMEHeader) 170 171 var contentDispositionValue string 172 if IsStringEmpty(fileName) { 173 contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param) 174 } else { 175 contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 176 param, escapeQuotes(fileName)) 177 } 178 hdr.Set("Content-Disposition", contentDispositionValue) 179 180 if !IsStringEmpty(contentType) { 181 hdr.Set(hdrContentTypeKey, contentType) 182 } 183 return hdr 184 } 185 186 func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error { 187 partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType)) 188 if err != nil { 189 return err 190 } 191 192 _, err = io.Copy(partWriter, mf.Reader) 193 return err 194 } 195 196 func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error { 197 // Auto detect actual multipart content type 198 cbuf := make([]byte, 512) 199 size, err := r.Read(cbuf) 200 if err != nil && err != io.EOF { 201 return err 202 } 203 204 partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf))) 205 if err != nil { 206 return err 207 } 208 209 if _, err = partWriter.Write(cbuf[:size]); err != nil { 210 return err 211 } 212 213 _, err = io.Copy(partWriter, r) 214 return err 215 } 216 217 func addFile(w *multipart.Writer, fieldName, path string) error { 218 file, err := os.Open(path) 219 if err != nil { 220 return err 221 } 222 defer closeq(file) 223 return writeMultipartFormFile(w, fieldName, filepath.Base(path), file) 224 } 225 226 func addFileReader(w *multipart.Writer, f *File) error { 227 return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader) 228 } 229 230 func getPointer(v interface{}) interface{} { 231 vv := valueOf(v) 232 if vv.Kind() == reflect.Ptr { 233 return v 234 } 235 return reflect.New(vv.Type()).Interface() 236 } 237 238 func isPayloadSupported(m string, allowMethodGet bool) bool { 239 return !(m == http.MethodHead || m == http.MethodOptions || (m == http.MethodGet && !allowMethodGet)) 240 } 241 242 func typeOf(i interface{}) reflect.Type { 243 return indirect(valueOf(i)).Type() 244 } 245 246 func valueOf(i interface{}) reflect.Value { 247 return reflect.ValueOf(i) 248 } 249 250 func indirect(v reflect.Value) reflect.Value { 251 return reflect.Indirect(v) 252 } 253 254 func kindOf(v interface{}) reflect.Kind { 255 return typeOf(v).Kind() 256 } 257 258 func createDirectory(dir string) (err error) { 259 if _, err = os.Stat(dir); err != nil { 260 if os.IsNotExist(err) { 261 if err = os.MkdirAll(dir, 0o755); err != nil { 262 return 263 } 264 } 265 } 266 return 267 } 268 269 func canJSONMarshal(contentType string, kind reflect.Kind) bool { 270 return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) 271 } 272 273 func funcName(i interface{}) string { 274 return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() 275 } 276 277 func acquireBuffer() *bytes.Buffer { 278 return bufPool.Get().(*bytes.Buffer) 279 } 280 281 func releaseBuffer(buf *bytes.Buffer) { 282 if buf != nil { 283 buf.Reset() 284 bufPool.Put(buf) 285 } 286 } 287 288 // requestBodyReleaser wraps requests's body and implements custom Close for it. 289 // The Close method closes original body and releases request body back to sync.Pool. 290 type requestBodyReleaser struct { 291 releaseOnce sync.Once 292 reqBuf *bytes.Buffer 293 io.ReadCloser 294 } 295 296 func newRequestBodyReleaser(respBody io.ReadCloser, reqBuf *bytes.Buffer) io.ReadCloser { 297 if reqBuf == nil { 298 return respBody 299 } 300 301 return &requestBodyReleaser{ 302 reqBuf: reqBuf, 303 ReadCloser: respBody, 304 } 305 } 306 307 func (rr *requestBodyReleaser) Close() error { 308 err := rr.ReadCloser.Close() 309 rr.releaseOnce.Do(func() { 310 releaseBuffer(rr.reqBuf) 311 }) 312 313 return err 314 } 315 316 func closeq(v interface{}) { 317 if c, ok := v.(io.Closer); ok { 318 _ = c.Close() 319 } 320 } 321 322 func composeHeaders(c *Client, r *Request, hdrs http.Header) string { 323 str := make([]string, 0, len(hdrs)) 324 for _, k := range sortHeaderKeys(hdrs) { 325 var v string 326 if k == "Cookie" { 327 cv := strings.TrimSpace(strings.Join(hdrs[k], ", ")) 328 if c.GetClient().Jar != nil { 329 for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) { 330 if cv != "" { 331 cv = cv + "; " + c.String() 332 } else { 333 cv = c.String() 334 } 335 } 336 } 337 v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv)) 338 } else { 339 v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", "))) 340 } 341 if v != "" { 342 str = append(str, "\t"+v) 343 } 344 } 345 return strings.Join(str, "\n") 346 } 347 348 func sortHeaderKeys(hdrs http.Header) []string { 349 keys := make([]string, 0, len(hdrs)) 350 for key := range hdrs { 351 keys = append(keys, key) 352 } 353 sort.Strings(keys) 354 return keys 355 } 356 357 func copyHeaders(hdrs http.Header) http.Header { 358 nh := http.Header{} 359 for k, v := range hdrs { 360 nh[k] = v 361 } 362 return nh 363 } 364 365 type noRetryErr struct { 366 err error 367 } 368 369 func (e *noRetryErr) Error() string { return e.err.Error() } 370 371 func wrapNoRetryErr(err error) error { 372 if err != nil { 373 err = &noRetryErr{err: err} 374 } 375 return err 376 } 377 378 func unwrapNoRetryErr(err error) error { 379 if e, ok := err.(*noRetryErr); ok { 380 err = e.err 381 } 382 return err 383 }