github.com/qiniu/x@v1.11.9/rpc/rpc_client.go (about)

     1  package rpc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  
    13  	"github.com/qiniu/x/reqid"
    14  
    15  	. "context"
    16  )
    17  
    18  var (
    19  	UserAgent = "Golang qiniu/rpc package"
    20  )
    21  
    22  var (
    23  	ErrInvalidRequestURL = errors.New("invalid request url")
    24  )
    25  
    26  // --------------------------------------------------------------------
    27  
    28  type Client struct {
    29  	*http.Client
    30  }
    31  
    32  var (
    33  	DefaultClient = Client{&http.Client{Transport: http.DefaultTransport}}
    34  )
    35  
    36  // --------------------------------------------------------------------
    37  
    38  func NewRequest(method, url1 string, body io.Reader) (req *http.Request, err error) {
    39  	var host string
    40  
    41  	// url1 = "-H <Host> http://<ip>[:<port>]/<path>"
    42  	//
    43  	if strings.HasPrefix(url1, "-H") {
    44  		url2 := strings.TrimLeft(url1[2:], " \t")
    45  		pos := strings.Index(url2, " ")
    46  		if pos <= 0 {
    47  			return nil, ErrInvalidRequestURL
    48  		}
    49  		host = url2[:pos]
    50  		url1 = strings.TrimLeft(url2[pos+1:], " \t")
    51  	}
    52  
    53  	req, err = http.NewRequest(method, url1, body)
    54  	if err != nil {
    55  		return
    56  	}
    57  	if host != "" {
    58  		req.Host = host
    59  	}
    60  	return
    61  }
    62  
    63  func (r Client) DoRequest(ctx Context, method, url string) (resp *http.Response, err error) {
    64  	req, err := NewRequest(method, url, nil)
    65  	if err != nil {
    66  		return
    67  	}
    68  	return r.Do(ctx, req)
    69  }
    70  
    71  func (r Client) DoRequestWith(
    72  	ctx Context, method, url1 string,
    73  	bodyType string, body io.Reader, bodyLength int) (resp *http.Response, err error) {
    74  
    75  	req, err := NewRequest(method, url1, body)
    76  	if err != nil {
    77  		return
    78  	}
    79  	req.Header.Set("Content-Type", bodyType)
    80  	req.ContentLength = int64(bodyLength)
    81  	return r.Do(ctx, req)
    82  }
    83  
    84  func (r Client) DoRequestWith64(
    85  	ctx Context, method, url1 string,
    86  	bodyType string, body io.Reader, bodyLength int64) (resp *http.Response, err error) {
    87  
    88  	req, err := NewRequest(method, url1, body)
    89  	if err != nil {
    90  		return
    91  	}
    92  	req.Header.Set("Content-Type", bodyType)
    93  	req.ContentLength = bodyLength
    94  	return r.Do(ctx, req)
    95  }
    96  
    97  func (r Client) DoRequestWithForm(
    98  	ctx Context, method, url1 string, data map[string][]string) (resp *http.Response, err error) {
    99  
   100  	msg := url.Values(data).Encode()
   101  	if method == "GET" || method == "HEAD" || method == "DELETE" {
   102  		if strings.ContainsRune(url1, '?') {
   103  			url1 += "&"
   104  		} else {
   105  			url1 += "?"
   106  		}
   107  		return r.DoRequest(ctx, method, url1+msg)
   108  	}
   109  	return r.DoRequestWith(
   110  		ctx, method, url1, "application/x-www-form-urlencoded", strings.NewReader(msg), len(msg))
   111  }
   112  
   113  func (r Client) DoRequestWithJson(
   114  	ctx Context, method, url1 string, data interface{}) (resp *http.Response, err error) {
   115  
   116  	msg, err := json.Marshal(data)
   117  	if err != nil {
   118  		return
   119  	}
   120  	return r.DoRequestWith(
   121  		ctx, method, url1, "application/json", bytes.NewReader(msg), len(msg))
   122  }
   123  
   124  func (r Client) Do(ctx Context, req *http.Request) (resp *http.Response, err error) {
   125  	if ctx == nil {
   126  		ctx = Background()
   127  	}
   128  
   129  	if reqid, ok := reqid.FromContext(ctx); ok {
   130  		req.Header.Set("X-Reqid", reqid)
   131  	}
   132  
   133  	if _, ok := req.Header["User-Agent"]; !ok {
   134  		req.Header.Set("User-Agent", UserAgent)
   135  	}
   136  
   137  	transport := r.Transport // don't change r.Transport
   138  	if transport == nil {
   139  		transport = http.DefaultTransport
   140  	}
   141  
   142  	// avoid cancel() is called before Do(req), but isn't accurate
   143  	select {
   144  	case <-ctx.Done():
   145  		err = ctx.Err()
   146  		return
   147  	default:
   148  	}
   149  
   150  	if tr, ok := getRequestCanceler(transport); ok { // support CancelRequest
   151  		reqC := make(chan bool, 1)
   152  		go func() {
   153  			resp, err = r.Client.Do(req)
   154  			reqC <- true
   155  		}()
   156  		select {
   157  		case <-reqC:
   158  		case <-ctx.Done():
   159  			tr.CancelRequest(req)
   160  			<-reqC
   161  			err = ctx.Err()
   162  		}
   163  	} else {
   164  		resp, err = r.Client.Do(req)
   165  	}
   166  	return
   167  }
   168  
   169  // --------------------------------------------------------------------
   170  
   171  type ErrorInfo struct {
   172  	Err   string `json:"error,omitempty"`
   173  	Key   string `json:"key,omitempty"`
   174  	Reqid string `json:"reqid,omitempty"`
   175  	Errno int    `json:"errno,omitempty"`
   176  	Code  int    `json:"code"`
   177  }
   178  
   179  func (r *ErrorInfo) ErrorDetail() string {
   180  	msg, _ := json.Marshal(r)
   181  	return string(msg)
   182  }
   183  
   184  func (r *ErrorInfo) Error() string {
   185  	return r.Err
   186  }
   187  
   188  func (r *ErrorInfo) RpcError() (code, errno int, key, err string) {
   189  	return r.Code, r.Errno, r.Key, r.Err
   190  }
   191  
   192  func (r *ErrorInfo) HttpCode() int {
   193  	return r.Code
   194  }
   195  
   196  // --------------------------------------------------------------------
   197  
   198  func parseError(e *ErrorInfo, r io.Reader) {
   199  	body, err1 := ioutil.ReadAll(r)
   200  	if err1 != nil {
   201  		e.Err = err1.Error()
   202  		return
   203  	}
   204  
   205  	var ret struct {
   206  		Err   string `json:"error"`
   207  		Key   string `json:"key"`
   208  		Errno int    `json:"errno"`
   209  	}
   210  	if json.Unmarshal(body, &ret) == nil && ret.Err != "" {
   211  		// qiniu error msg style returns here
   212  		e.Err, e.Key, e.Errno = ret.Err, ret.Key, ret.Errno
   213  		return
   214  	}
   215  	e.Err = string(body)
   216  }
   217  
   218  func ResponseError(resp *http.Response) (err error) {
   219  	e := &ErrorInfo{
   220  		Reqid: resp.Header.Get("X-Reqid"),
   221  		Code:  resp.StatusCode,
   222  	}
   223  	if resp.StatusCode > 299 {
   224  		if resp.ContentLength != 0 {
   225  			ct, ok := resp.Header["Content-Type"]
   226  			if ok && strings.HasPrefix(ct[0], "application/json") {
   227  				parseError(e, resp.Body)
   228  			}
   229  		}
   230  	}
   231  	return e
   232  }
   233  
   234  func CallRet(ctx Context, ret interface{}, resp *http.Response) (err error) {
   235  	defer func() {
   236  		io.Copy(ioutil.Discard, resp.Body)
   237  		resp.Body.Close()
   238  	}()
   239  
   240  	if resp.StatusCode/100 == 2 {
   241  		if ret != nil && resp.ContentLength != 0 {
   242  			err = json.NewDecoder(resp.Body).Decode(ret)
   243  			if err != nil {
   244  				return
   245  			}
   246  		}
   247  		if resp.StatusCode == 200 {
   248  			return nil
   249  		}
   250  	}
   251  	return ResponseError(resp)
   252  }
   253  
   254  func (r Client) CallWithForm(
   255  	ctx Context, ret interface{}, method, url1 string, param map[string][]string) (err error) {
   256  
   257  	resp, err := r.DoRequestWithForm(ctx, method, url1, param)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	return CallRet(ctx, ret, resp)
   262  }
   263  
   264  func (r Client) CallWithJson(
   265  	ctx Context, ret interface{}, method, url1 string, param interface{}) (err error) {
   266  
   267  	resp, err := r.DoRequestWithJson(ctx, method, url1, param)
   268  	if err != nil {
   269  		return err
   270  	}
   271  	return CallRet(ctx, ret, resp)
   272  }
   273  
   274  func (r Client) CallWith(
   275  	ctx Context, ret interface{}, method, url1, bodyType string, body io.Reader, bodyLength int) (err error) {
   276  
   277  	resp, err := r.DoRequestWith(ctx, method, url1, bodyType, body, bodyLength)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	return CallRet(ctx, ret, resp)
   282  }
   283  
   284  func (r Client) CallWith64(
   285  	ctx Context, ret interface{}, method, url1, bodyType string, body io.Reader, bodyLength int64) (err error) {
   286  
   287  	resp, err := r.DoRequestWith64(ctx, method, url1, bodyType, body, bodyLength)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	return CallRet(ctx, ret, resp)
   292  }
   293  
   294  func (r Client) Call(
   295  	ctx Context, ret interface{}, method, url1 string) (err error) {
   296  
   297  	resp, err := r.DoRequestWith(ctx, method, url1, "application/x-www-form-urlencoded", nil, 0)
   298  	if err != nil {
   299  		return err
   300  	}
   301  	return CallRet(ctx, ret, resp)
   302  }
   303  
   304  // ---------------------------------------------------------------------------
   305  
   306  type requestCanceler interface {
   307  	CancelRequest(req *http.Request)
   308  }
   309  
   310  type nestedObjectGetter interface {
   311  	NestedObject() interface{}
   312  }
   313  
   314  func getRequestCanceler(tp http.RoundTripper) (rc requestCanceler, ok bool) {
   315  	if rc, ok = tp.(requestCanceler); ok {
   316  		return
   317  	}
   318  
   319  	p := interface{}(tp)
   320  	for {
   321  		getter, ok1 := p.(nestedObjectGetter)
   322  		if !ok1 {
   323  			return
   324  		}
   325  		p = getter.NestedObject()
   326  		if rc, ok = p.(requestCanceler); ok {
   327  			return
   328  		}
   329  	}
   330  }
   331  
   332  // --------------------------------------------------------------------