|
- // Copyright (c) 2015-2020 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
- // resty source code and usage is governed by a MIT style
- // license that can be found in the LICENSE file.
-
- package resty
-
- import (
- "bytes"
- "encoding/xml"
- "fmt"
- "io"
- "log"
- "mime/multipart"
- "net/http"
- "net/textproto"
- "os"
- "path/filepath"
- "reflect"
- "runtime"
- "sort"
- "strings"
- )
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Logger interface
- //_______________________________________________________________________
-
- // Logger interface is to abstract the logging from Resty. Gives control to
- // the Resty users, choice of the logger.
- type Logger interface {
- Errorf(format string, v ...interface{})
- Warnf(format string, v ...interface{})
- Debugf(format string, v ...interface{})
- }
-
- func createLogger() *logger {
- l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
- return l
- }
-
- var _ Logger = (*logger)(nil)
-
- type logger struct {
- l *log.Logger
- }
-
- func (l *logger) Errorf(format string, v ...interface{}) {
- l.output("ERROR RESTY "+format, v...)
- }
-
- func (l *logger) Warnf(format string, v ...interface{}) {
- l.output("WARN RESTY "+format, v...)
- }
-
- func (l *logger) Debugf(format string, v ...interface{}) {
- l.output("DEBUG RESTY "+format, v...)
- }
-
- func (l *logger) output(format string, v ...interface{}) {
- if len(v) == 0 {
- l.l.Print(format)
- return
- }
- l.l.Printf(format, v...)
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Package Helper methods
- //_______________________________________________________________________
-
- // IsStringEmpty method tells whether given string is empty or not
- func IsStringEmpty(str string) bool {
- return len(strings.TrimSpace(str)) == 0
- }
-
- // DetectContentType method is used to figure out `Request.Body` content type for request header
- func DetectContentType(body interface{}) string {
- contentType := plainTextType
- kind := kindOf(body)
- switch kind {
- case reflect.Struct, reflect.Map:
- contentType = jsonContentType
- case reflect.String:
- contentType = plainTextType
- default:
- if b, ok := body.([]byte); ok {
- contentType = http.DetectContentType(b)
- } else if kind == reflect.Slice {
- contentType = jsonContentType
- }
- }
-
- return contentType
- }
-
- // IsJSONType method is to check JSON content type or not
- func IsJSONType(ct string) bool {
- return jsonCheck.MatchString(ct)
- }
-
- // IsXMLType method is to check XML content type or not
- func IsXMLType(ct string) bool {
- return xmlCheck.MatchString(ct)
- }
-
- // Unmarshalc content into object from JSON or XML
- func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
- if IsJSONType(ct) {
- err = c.JSONUnmarshal(b, d)
- } else if IsXMLType(ct) {
- err = xml.Unmarshal(b, d)
- }
-
- return
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // RequestLog and ResponseLog type
- //_______________________________________________________________________
-
- // RequestLog struct is used to collected information from resty request
- // instance for debug logging. It sent to request log callback before resty
- // actually logs the information.
- type RequestLog struct {
- Header http.Header
- Body string
- }
-
- // ResponseLog struct is used to collected information from resty response
- // instance for debug logging. It sent to response log callback before resty
- // actually logs the information.
- type ResponseLog struct {
- Header http.Header
- Body string
- }
-
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Package Unexported methods
- //_______________________________________________________________________
-
- // way to disable the HTML escape as opt-in
- func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
- if !r.jsonEscapeHTML {
- return noescapeJSONMarshal(d)
- } else if !c.jsonEscapeHTML {
- return noescapeJSONMarshal(d)
- }
- return c.JSONMarshal(d)
- }
-
- func firstNonEmpty(v ...string) string {
- for _, s := range v {
- if !IsStringEmpty(s) {
- return s
- }
- }
- return ""
- }
-
- var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
-
- func escapeQuotes(s string) string {
- return quoteEscaper.Replace(s)
- }
-
- func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
- hdr := make(textproto.MIMEHeader)
-
- var contentDispositionValue string
- if IsStringEmpty(fileName) {
- contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
- } else {
- contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
- param, escapeQuotes(fileName))
- }
- hdr.Set("Content-Disposition", contentDispositionValue)
-
- if !IsStringEmpty(contentType) {
- hdr.Set(hdrContentTypeKey, contentType)
- }
- return hdr
- }
-
- func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
- partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
- if err != nil {
- return err
- }
-
- _, err = io.Copy(partWriter, mf.Reader)
- return err
- }
-
- func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
- // Auto detect actual multipart content type
- cbuf := make([]byte, 512)
- size, err := r.Read(cbuf)
- if err != nil {
- return err
- }
-
- partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
- if err != nil {
- return err
- }
-
- if _, err = partWriter.Write(cbuf[:size]); err != nil {
- return err
- }
-
- _, err = io.Copy(partWriter, r)
- return err
- }
-
- func addFile(w *multipart.Writer, fieldName, path string) error {
- file, err := os.Open(path)
- if err != nil {
- return err
- }
- defer closeq(file)
- return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
- }
-
- func addFileReader(w *multipart.Writer, f *File) error {
- return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
- }
-
- func getPointer(v interface{}) interface{} {
- vv := valueOf(v)
- if vv.Kind() == reflect.Ptr {
- return v
- }
- return reflect.New(vv.Type()).Interface()
- }
-
- func isPayloadSupported(m string, allowMethodGet bool) bool {
- return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
- }
-
- func typeOf(i interface{}) reflect.Type {
- return indirect(valueOf(i)).Type()
- }
-
- func valueOf(i interface{}) reflect.Value {
- return reflect.ValueOf(i)
- }
-
- func indirect(v reflect.Value) reflect.Value {
- return reflect.Indirect(v)
- }
-
- func kindOf(v interface{}) reflect.Kind {
- return typeOf(v).Kind()
- }
-
- func createDirectory(dir string) (err error) {
- if _, err = os.Stat(dir); err != nil {
- if os.IsNotExist(err) {
- if err = os.MkdirAll(dir, 0755); err != nil {
- return
- }
- }
- }
- return
- }
-
- func canJSONMarshal(contentType string, kind reflect.Kind) bool {
- return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
- }
-
- func functionName(i interface{}) string {
- return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
- }
-
- func acquireBuffer() *bytes.Buffer {
- return bufPool.Get().(*bytes.Buffer)
- }
-
- func releaseBuffer(buf *bytes.Buffer) {
- if buf != nil {
- buf.Reset()
- bufPool.Put(buf)
- }
- }
-
- func closeq(v interface{}) {
- if c, ok := v.(io.Closer); ok {
- silently(c.Close())
- }
- }
-
- func silently(_ ...interface{}) {}
-
- func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
- str := make([]string, 0, len(hdrs))
- for _, k := range sortHeaderKeys(hdrs) {
- var v string
- if k == "Cookie" {
- cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
- if c.GetClient().Jar != nil {
- for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
- if cv != "" {
- cv = cv + "; " + c.String()
- } else {
- cv = c.String()
- }
- }
- }
- v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
- } else {
- v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
- }
- if v != "" {
- str = append(str, "\t"+v)
- }
- }
- return strings.Join(str, "\n")
- }
-
- func sortHeaderKeys(hdrs http.Header) []string {
- keys := make([]string, 0, len(hdrs))
- for key := range hdrs {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- return keys
- }
-
- func copyHeaders(hdrs http.Header) http.Header {
- nh := http.Header{}
- for k, v := range hdrs {
- nh[k] = v
- }
- return nh
- }
-
- type noRetryErr struct {
- err error
- }
-
- func (e *noRetryErr) Error() string {
- return e.err.Error()
- }
-
- func wrapNoRetryErr(err error) error {
- if err != nil {
- err = &noRetryErr{err: err}
- }
- return err
- }
-
- func unwrapNoRetryErr(err error) error {
- if e, ok := err.(*noRetryErr); ok {
- err = e.err
- }
- return err
- }
|