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