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 // --------------------------------------------------------------------