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 }