github.com/qiniu/go-sdk/v7@v7.27.0/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptrace"
    11  	"net/http/httputil"
    12  	"net/url"
    13  	"runtime"
    14  	"strings"
    15  
    16  	"github.com/qiniu/go-sdk/v7/auth"
    17  	"github.com/qiniu/go-sdk/v7/conf"
    18  	internal_io "github.com/qiniu/go-sdk/v7/internal/io"
    19  	"github.com/qiniu/go-sdk/v7/internal/log"
    20  	"github.com/qiniu/go-sdk/v7/reqid"
    21  )
    22  
    23  var (
    24  	UserAgent     = getUserAgentWithAppName("default")
    25  	DefaultClient = Client{&http.Client{Transport: DefaultTransport}}
    26  
    27  	// 用来打印调试信息
    28  	DebugMode     = false
    29  	DeepDebugInfo = false
    30  )
    31  
    32  // --------------------------------------------------------------------
    33  
    34  // Client 负责发送HTTP请求到七牛接口服务器
    35  type Client struct {
    36  	*http.Client
    37  }
    38  
    39  // TurnOnDebug 开启Debug模式
    40  func TurnOnDebug() {
    41  	DebugMode = true
    42  }
    43  
    44  // userApp should be [A-Za-z0-9_\ \-\.]*
    45  func SetAppName(userApp string) error {
    46  	UserAgent = getUserAgentWithAppName(userApp)
    47  	return nil
    48  }
    49  
    50  func getUserAgentWithAppName(userApp string) string {
    51  	userAgentPrefix := "QiniuGo/"
    52  	if testRuntime {
    53  		userAgentPrefix = "QiniuGo_Debug/"
    54  	}
    55  	return fmt.Sprintf("%s%s (%s; %s; %s) %s",
    56  		userAgentPrefix, conf.Version, runtime.GOOS, runtime.GOARCH, userApp, runtime.Version())
    57  }
    58  
    59  // --------------------------------------------------------------------
    60  
    61  func newRequest(ctx context.Context, method, reqUrl string, headers http.Header, body io.Reader) (req *http.Request, err error) {
    62  	req, err = http.NewRequest(method, reqUrl, body)
    63  	if err != nil {
    64  		return
    65  	}
    66  
    67  	if headers == nil {
    68  		headers = http.Header{}
    69  	}
    70  
    71  	err = addDefaultHeader(headers)
    72  	if err != nil {
    73  		return
    74  	}
    75  
    76  	req.Header = headers
    77  	req = req.WithContext(ctx)
    78  
    79  	// check access token
    80  	mac, t, ok := auth.CredentialsFromContext(ctx)
    81  	if ok {
    82  		err = mac.AddToken(t, req)
    83  		if err != nil {
    84  			return
    85  		}
    86  	}
    87  	if DebugMode {
    88  		trace := &httptrace.ClientTrace{
    89  			GotConn: func(connInfo httptrace.GotConnInfo) {
    90  				remoteAddr := connInfo.Conn.RemoteAddr()
    91  				log.Debug(fmt.Sprintf("Network: %s, Remote ip:%s, URL: %s", remoteAddr.Network(), remoteAddr.String(), req.URL))
    92  			},
    93  		}
    94  		req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
    95  
    96  		var (
    97  			hasBody     = DeepDebugInfo
    98  			contentType = req.Header.Get("Content-Type")
    99  		)
   100  		if contentType != conf.CONTENT_TYPE_JSON && contentType != conf.CONTENT_TYPE_FORM {
   101  			hasBody = false
   102  		}
   103  
   104  		bs, bErr := httputil.DumpRequest(req, hasBody)
   105  		if bErr != nil {
   106  			err = bErr
   107  			return
   108  		}
   109  		log.Debug(string(bs))
   110  	}
   111  	return
   112  }
   113  
   114  func (r Client) DoRequest(ctx context.Context, method, reqUrl string, headers http.Header) (resp *http.Response, err error) {
   115  	req, err := newRequest(ctx, method, reqUrl, headers, nil)
   116  	if err != nil {
   117  		return
   118  	}
   119  	return r.Do(ctx, req)
   120  }
   121  
   122  func (r Client) DoRequestWith(ctx context.Context, method, reqUrl string, headers http.Header, body io.Reader,
   123  	bodyLength int,
   124  ) (resp *http.Response, err error) {
   125  	req, err := newRequest(ctx, method, reqUrl, headers, body)
   126  	if err != nil {
   127  		return
   128  	}
   129  	req.ContentLength = int64(bodyLength)
   130  	return r.Do(ctx, req)
   131  }
   132  
   133  func (r Client) DoRequestWith64(ctx context.Context, method, reqUrl string, headers http.Header, body io.Reader,
   134  	bodyLength int64,
   135  ) (resp *http.Response, err error) {
   136  	req, err := newRequest(ctx, method, reqUrl, headers, body)
   137  	if err != nil {
   138  		return
   139  	}
   140  	req.ContentLength = bodyLength
   141  	return r.Do(ctx, req)
   142  }
   143  
   144  func (r Client) DoRequestWithBodyGetter(ctx context.Context, method, reqUrl string, headers http.Header, body io.Reader,
   145  	getBody func() (io.ReadCloser, error), bodyLength int64,
   146  ) (resp *http.Response, err error) {
   147  	req, err := newRequest(ctx, method, reqUrl, headers, body)
   148  	if err != nil {
   149  		return
   150  	}
   151  	req.ContentLength = bodyLength
   152  	req.GetBody = getBody
   153  	return r.Do(ctx, req)
   154  }
   155  
   156  func (r Client) DoRequestWithForm(ctx context.Context, method, reqUrl string, headers http.Header,
   157  	data map[string][]string,
   158  ) (resp *http.Response, err error) {
   159  	if headers == nil {
   160  		headers = http.Header{}
   161  	}
   162  	headers.Set("Content-Type", conf.CONTENT_TYPE_FORM)
   163  
   164  	requestData := url.Values(data).Encode()
   165  	if method == "GET" || method == "HEAD" || method == "DELETE" {
   166  		if strings.ContainsRune(reqUrl, '?') {
   167  			reqUrl += "&"
   168  		} else {
   169  			reqUrl += "?"
   170  		}
   171  		return r.DoRequest(ctx, method, reqUrl+requestData, headers)
   172  	}
   173  
   174  	return r.DoRequestWith(ctx, method, reqUrl, headers, strings.NewReader(requestData), len(requestData))
   175  }
   176  
   177  func (r Client) DoRequestWithJson(ctx context.Context, method, reqUrl string, headers http.Header,
   178  	data interface{},
   179  ) (resp *http.Response, err error) {
   180  	reqBody, err := json.Marshal(data)
   181  	if err != nil {
   182  		return
   183  	}
   184  
   185  	if headers == nil {
   186  		headers = http.Header{}
   187  	}
   188  	headers.Set("Content-Type", conf.CONTENT_TYPE_JSON)
   189  	return r.DoRequestWith(ctx, method, reqUrl, headers, bytes.NewReader(reqBody), len(reqBody))
   190  }
   191  
   192  func (r Client) Do(ctx context.Context, req *http.Request) (resp *http.Response, err error) {
   193  	reqctx := req.Context()
   194  
   195  	if reqId, ok := reqid.ReqidFromContext(ctx); ok {
   196  		req.Header.Set("X-Reqid", reqId)
   197  	} else if reqId, ok = reqid.ReqidFromContext(reqctx); ok {
   198  		req.Header.Set("X-Reqid", reqId)
   199  	}
   200  
   201  	if _, ok := req.Header["User-Agent"]; !ok {
   202  		req.Header.Set("User-Agent", UserAgent)
   203  	}
   204  
   205  	resp, err = r.Client.Do(req)
   206  	return
   207  }
   208  
   209  // --------------------------------------------------------------------
   210  
   211  type ErrorInfo struct {
   212  	Err       string `json:"error,omitempty"`
   213  	ErrorCode string `json:"error_code,omitempty"`
   214  	Key       string `json:"key,omitempty"`
   215  	Reqid     string `json:"reqid,omitempty"`
   216  	Errno     int    `json:"errno,omitempty"`
   217  	Code      int    `json:"code"`
   218  }
   219  
   220  func (r *ErrorInfo) ErrorDetail() string {
   221  	msg, _ := json.Marshal(r)
   222  	return string(msg)
   223  }
   224  
   225  func (r *ErrorInfo) Error() string {
   226  	return r.Err
   227  }
   228  
   229  func (r *ErrorInfo) RpcError() (code, errno int, key, err string) {
   230  	return r.Code, r.Errno, r.Key, r.Err
   231  }
   232  
   233  func (r *ErrorInfo) HttpCode() int {
   234  	return r.Code
   235  }
   236  
   237  // --------------------------------------------------------------------
   238  
   239  func parseError(e *ErrorInfo, r io.Reader) {
   240  	body, err1 := internal_io.ReadAll(r)
   241  	if err1 != nil {
   242  		e.Err = err1.Error()
   243  		return
   244  	}
   245  
   246  	var ret struct {
   247  		Err       string `json:"error"`
   248  		Key       string `json:"key"`
   249  		Errno     int    `json:"errno"`
   250  		ErrorCode string `json:"error_code,omitempty"`
   251  	}
   252  	if decodeJsonFromData(body, &ret) == nil && ret.Err != "" {
   253  		// qiniu error msg style returns here
   254  		e.Err, e.Key, e.Errno, e.ErrorCode = ret.Err, ret.Key, ret.Errno, ret.ErrorCode
   255  		return
   256  	}
   257  	e.Err = string(body)
   258  }
   259  
   260  func ResponseError(resp *http.Response) error {
   261  	e := &ErrorInfo{
   262  		Reqid: resp.Header.Get("X-Reqid"),
   263  		Code:  resp.StatusCode,
   264  	}
   265  
   266  	defer func() {
   267  		if resp.Body != nil {
   268  			io.Copy(io.Discard, resp.Body)
   269  			resp.Body.Close()
   270  		}
   271  	}()
   272  
   273  	if resp.StatusCode > 299 {
   274  		if resp.ContentLength != 0 {
   275  			ct, ok := resp.Header["Content-Type"]
   276  			if ok && strings.HasPrefix(ct[0], "application/json") {
   277  				parseError(e, resp.Body)
   278  			} else {
   279  				bs, err := internal_io.ReadAll(resp.Body)
   280  				if err != nil {
   281  					e.Err = fmt.Sprintf("failed to read from response body: %s", err)
   282  				} else {
   283  					e.Err = strings.TrimRight(string(bs), "\n")
   284  				}
   285  			}
   286  		}
   287  	}
   288  	return e
   289  }
   290  
   291  func CallRet(ctx context.Context, ret interface{}, resp *http.Response) (err error) {
   292  	defer func() {
   293  		_ = internal_io.SinkAll(resp.Body)
   294  		resp.Body.Close()
   295  	}()
   296  
   297  	if DebugMode {
   298  
   299  		hasBody := DeepDebugInfo
   300  		switch {
   301  		case
   302  			resp.Header.Get("Content-Type") == "application/octet-stream",
   303  			resp.ContentLength <= 0 || resp.ContentLength > 1024*1024:
   304  			hasBody = false
   305  		}
   306  
   307  		bs, dErr := httputil.DumpResponse(resp, hasBody)
   308  		if dErr != nil {
   309  			err = dErr
   310  			return
   311  		}
   312  		log.Debug(string(bs))
   313  	}
   314  	if resp.StatusCode/100 == 2 {
   315  		if ret != nil && resp.ContentLength != 0 {
   316  			err = DecodeJsonFromReader(resp.Body, ret)
   317  			if err != nil {
   318  				return
   319  			}
   320  		}
   321  		return nil
   322  	}
   323  	return ResponseError(resp)
   324  }
   325  
   326  func (r Client) CallWithForm(ctx context.Context, ret interface{}, method, reqUrl string, headers http.Header,
   327  	param map[string][]string,
   328  ) (err error) {
   329  	resp, err := r.DoRequestWithForm(ctx, method, reqUrl, headers, param)
   330  	if err != nil {
   331  		return err
   332  	}
   333  	return CallRet(ctx, ret, resp)
   334  }
   335  
   336  func (r Client) CallWithJson(ctx context.Context, ret interface{}, method, reqUrl string, headers http.Header,
   337  	param interface{},
   338  ) (err error) {
   339  	resp, err := r.DoRequestWithJson(ctx, method, reqUrl, headers, param)
   340  	if err != nil {
   341  		return err
   342  	}
   343  	return CallRet(ctx, ret, resp)
   344  }
   345  
   346  func (r Client) CallWith(ctx context.Context, ret interface{}, method, reqUrl string, headers http.Header, body io.Reader,
   347  	bodyLength int,
   348  ) (err error) {
   349  	resp, err := r.DoRequestWith(ctx, method, reqUrl, headers, body, bodyLength)
   350  	if err != nil {
   351  		return err
   352  	}
   353  	return CallRet(ctx, ret, resp)
   354  }
   355  
   356  func (r Client) CallWith64(ctx context.Context, ret interface{}, method, reqUrl string, headers http.Header, body io.Reader,
   357  	bodyLength int64,
   358  ) (err error) {
   359  	resp, err := r.DoRequestWith64(ctx, method, reqUrl, headers, body, bodyLength)
   360  	if err != nil {
   361  		return err
   362  	}
   363  	return CallRet(ctx, ret, resp)
   364  }
   365  
   366  func (r Client) CallWithBodyGetter(ctx context.Context, ret interface{}, method, reqUrl string, headers http.Header, body io.Reader,
   367  	getBody func() (io.ReadCloser, error), bodyLength int64,
   368  ) (err error) {
   369  	resp, err := r.DoRequestWithBodyGetter(ctx, method, reqUrl, headers, body, getBody, bodyLength)
   370  	if err != nil {
   371  		return err
   372  	}
   373  	return CallRet(ctx, ret, resp)
   374  }
   375  
   376  func (r Client) Call(ctx context.Context, ret interface{}, method, reqUrl string, headers http.Header) (err error) {
   377  	resp, err := r.DoRequestWith(ctx, method, reqUrl, headers, nil, 0)
   378  	if err != nil {
   379  		return err
   380  	}
   381  	return CallRet(ctx, ret, resp)
   382  }
   383  
   384  func (r Client) CredentialedCallWithForm(ctx context.Context, cred *auth.Credentials, tokenType auth.TokenType, ret interface{},
   385  	method, reqUrl string, headers http.Header, param map[string][]string,
   386  ) error {
   387  	ctx = auth.WithCredentialsType(ctx, cred, tokenType)
   388  	return r.CallWithForm(ctx, ret, method, reqUrl, headers, param)
   389  }
   390  
   391  func (r Client) CredentialedCallWithJson(ctx context.Context, cred *auth.Credentials, tokenType auth.TokenType, ret interface{},
   392  	method, reqUrl string, headers http.Header, param interface{},
   393  ) error {
   394  	ctx = auth.WithCredentialsType(ctx, cred, tokenType)
   395  	return r.CallWithJson(ctx, ret, method, reqUrl, headers, param)
   396  }
   397  
   398  func (r Client) CredentialedCallWith(ctx context.Context, cred *auth.Credentials, tokenType auth.TokenType, ret interface{},
   399  	method, reqUrl string, headers http.Header, body io.Reader, bodyLength int,
   400  ) error {
   401  	ctx = auth.WithCredentialsType(ctx, cred, tokenType)
   402  	return r.CallWith(ctx, ret, method, reqUrl, headers, body, bodyLength)
   403  }
   404  
   405  func (r Client) CredentialedCallWith64(ctx context.Context, cred *auth.Credentials, tokenType auth.TokenType, ret interface{},
   406  	method, reqUrl string, headers http.Header, body io.Reader, bodyLength int64,
   407  ) error {
   408  	ctx = auth.WithCredentialsType(ctx, cred, tokenType)
   409  	return r.CallWith64(ctx, ret, method, reqUrl, headers, body, bodyLength)
   410  }
   411  
   412  func (r Client) CredentialedCall(ctx context.Context, cred *auth.Credentials, tokenType auth.TokenType, ret interface{},
   413  	method, reqUrl string, headers http.Header,
   414  ) error {
   415  	ctx = auth.WithCredentialsType(ctx, cred, tokenType)
   416  	return r.Call(ctx, ret, method, reqUrl, headers)
   417  }