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  }