...

Source file src/github.com/go-resty/resty/v2/util.go

Documentation: github.com/go-resty/resty/v2

     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 {
    98  	return jsonCheck.MatchString(ct)
    99  }
   100  
   101  // IsXMLType method is to check XML content type or not
   102  func IsXMLType(ct string) bool {
   103  	return xmlCheck.MatchString(ct)
   104  }
   105  
   106  // Unmarshalc content into object from JSON or XML
   107  func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
   108  	if IsJSONType(ct) {
   109  		err = c.JSONUnmarshal(b, d)
   110  	} else if IsXMLType(ct) {
   111  		err = c.XMLUnmarshal(b, d)
   112  	}
   113  
   114  	return
   115  }
   116  
   117  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   118  // RequestLog and ResponseLog type
   119  //_______________________________________________________________________
   120  
   121  // RequestLog struct is used to collected information from resty request
   122  // instance for debug logging. It sent to request log callback before resty
   123  // actually logs the information.
   124  type RequestLog struct {
   125  	Header http.Header
   126  	Body   string
   127  }
   128  
   129  // ResponseLog struct is used to collected information from resty response
   130  // instance for debug logging. It sent to response log callback before resty
   131  // actually logs the information.
   132  type ResponseLog struct {
   133  	Header http.Header
   134  	Body   string
   135  }
   136  
   137  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
   138  // Package Unexported methods
   139  //_______________________________________________________________________
   140  
   141  // way to disable the HTML escape as opt-in
   142  func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) {
   143  	if !r.jsonEscapeHTML || !c.jsonEscapeHTML {
   144  		return noescapeJSONMarshal(d)
   145  	}
   146  
   147  	data, err := c.JSONMarshal(d)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	buf := acquireBuffer()
   153  	_, _ = buf.Write(data)
   154  	return buf, nil
   155  }
   156  
   157  func firstNonEmpty(v ...string) string {
   158  	for _, s := range v {
   159  		if !IsStringEmpty(s) {
   160  			return s
   161  		}
   162  	}
   163  	return ""
   164  }
   165  
   166  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
   167  
   168  func escapeQuotes(s string) string {
   169  	return quoteEscaper.Replace(s)
   170  }
   171  
   172  func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
   173  	hdr := make(textproto.MIMEHeader)
   174  
   175  	var contentDispositionValue string
   176  	if IsStringEmpty(fileName) {
   177  		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
   178  	} else {
   179  		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
   180  			param, escapeQuotes(fileName))
   181  	}
   182  	hdr.Set("Content-Disposition", contentDispositionValue)
   183  
   184  	if !IsStringEmpty(contentType) {
   185  		hdr.Set(hdrContentTypeKey, contentType)
   186  	}
   187  	return hdr
   188  }
   189  
   190  func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
   191  	partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	_, err = io.Copy(partWriter, mf.Reader)
   197  	return err
   198  }
   199  
   200  func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
   201  	// Auto detect actual multipart content type
   202  	cbuf := make([]byte, 512)
   203  	size, err := r.Read(cbuf)
   204  	if err != nil && err != io.EOF {
   205  		return err
   206  	}
   207  
   208  	partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	if _, err = partWriter.Write(cbuf[:size]); err != nil {
   214  		return err
   215  	}
   216  
   217  	_, err = io.Copy(partWriter, r)
   218  	return err
   219  }
   220  
   221  func addFile(w *multipart.Writer, fieldName, path string) error {
   222  	file, err := os.Open(path)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	defer closeq(file)
   227  	return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
   228  }
   229  
   230  func addFileReader(w *multipart.Writer, f *File) error {
   231  	return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
   232  }
   233  
   234  func getPointer(v interface{}) interface{} {
   235  	vv := valueOf(v)
   236  	if vv.Kind() == reflect.Ptr {
   237  		return v
   238  	}
   239  	return reflect.New(vv.Type()).Interface()
   240  }
   241  
   242  func isPayloadSupported(m string, allowMethodGet bool) bool {
   243  	return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
   244  }
   245  
   246  func typeOf(i interface{}) reflect.Type {
   247  	return indirect(valueOf(i)).Type()
   248  }
   249  
   250  func valueOf(i interface{}) reflect.Value {
   251  	return reflect.ValueOf(i)
   252  }
   253  
   254  func indirect(v reflect.Value) reflect.Value {
   255  	return reflect.Indirect(v)
   256  }
   257  
   258  func kindOf(v interface{}) reflect.Kind {
   259  	return typeOf(v).Kind()
   260  }
   261  
   262  func createDirectory(dir string) (err error) {
   263  	if _, err = os.Stat(dir); err != nil {
   264  		if os.IsNotExist(err) {
   265  			if err = os.MkdirAll(dir, 0755); err != nil {
   266  				return
   267  			}
   268  		}
   269  	}
   270  	return
   271  }
   272  
   273  func canJSONMarshal(contentType string, kind reflect.Kind) bool {
   274  	return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
   275  }
   276  
   277  func functionName(i interface{}) string {
   278  	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
   279  }
   280  
   281  func acquireBuffer() *bytes.Buffer {
   282  	return bufPool.Get().(*bytes.Buffer)
   283  }
   284  
   285  func releaseBuffer(buf *bytes.Buffer) {
   286  	if buf != nil {
   287  		buf.Reset()
   288  		bufPool.Put(buf)
   289  	}
   290  }
   291  
   292  // requestBodyReleaser wraps requests's body and implements custom Close for it.
   293  // The Close method closes original body and releases request body back to sync.Pool.
   294  type requestBodyReleaser struct {
   295  	releaseOnce sync.Once
   296  	reqBuf      *bytes.Buffer
   297  	io.ReadCloser
   298  }
   299  
   300  func newRequestBodyReleaser(respBody io.ReadCloser, reqBuf *bytes.Buffer) io.ReadCloser {
   301  	if reqBuf == nil {
   302  		return respBody
   303  	}
   304  
   305  	return &requestBodyReleaser{
   306  		reqBuf:     reqBuf,
   307  		ReadCloser: respBody,
   308  	}
   309  }
   310  
   311  func (rr *requestBodyReleaser) Close() error {
   312  	err := rr.ReadCloser.Close()
   313  	rr.releaseOnce.Do(func() {
   314  		releaseBuffer(rr.reqBuf)
   315  	})
   316  
   317  	return err
   318  }
   319  
   320  func closeq(v interface{}) {
   321  	if c, ok := v.(io.Closer); ok {
   322  		silently(c.Close())
   323  	}
   324  }
   325  
   326  func silently(_ ...interface{}) {}
   327  
   328  func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
   329  	str := make([]string, 0, len(hdrs))
   330  	for _, k := range sortHeaderKeys(hdrs) {
   331  		var v string
   332  		if k == "Cookie" {
   333  			cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
   334  			if c.GetClient().Jar != nil {
   335  				for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
   336  					if cv != "" {
   337  						cv = cv + "; " + c.String()
   338  					} else {
   339  						cv = c.String()
   340  					}
   341  				}
   342  			}
   343  			v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
   344  		} else {
   345  			v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
   346  		}
   347  		if v != "" {
   348  			str = append(str, "\t"+v)
   349  		}
   350  	}
   351  	return strings.Join(str, "\n")
   352  }
   353  
   354  func sortHeaderKeys(hdrs http.Header) []string {
   355  	keys := make([]string, 0, len(hdrs))
   356  	for key := range hdrs {
   357  		keys = append(keys, key)
   358  	}
   359  	sort.Strings(keys)
   360  	return keys
   361  }
   362  
   363  func copyHeaders(hdrs http.Header) http.Header {
   364  	nh := http.Header{}
   365  	for k, v := range hdrs {
   366  		nh[k] = v
   367  	}
   368  	return nh
   369  }
   370  
   371  type noRetryErr struct {
   372  	err error
   373  }
   374  
   375  func (e *noRetryErr) Error() string {
   376  	return e.err.Error()
   377  }
   378  
   379  func wrapNoRetryErr(err error) error {
   380  	if err != nil {
   381  		err = &noRetryErr{err: err}
   382  	}
   383  	return err
   384  }
   385  
   386  func unwrapNoRetryErr(err error) error {
   387  	if e, ok := err.(*noRetryErr); ok {
   388  		err = e.err
   389  	}
   390  	return err
   391  }
   392  

View as plain text