Files
2018-11-19 10:24:54 +08:00

350 lines
7.9 KiB
Go

package storage
// The original library rpc.v7 logic in github.com/qiniu/x has its own bugs
// under the concurrent http calls, we make a fork of the library and fix
// the bug
import (
"bytes"
"encoding/json"
"fmt"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/conf"
"github.com/qiniu/x/reqid.v7"
. "golang.org/x/net/context"
"io"
"io/ioutil"
"net/http"
"net/url"
"runtime"
"strings"
)
var UserAgent = "Golang qiniu/rpc package"
var DefaultClient = Client{&http.Client{Transport: http.DefaultTransport}}
// --------------------------------------------------------------------
type Client struct {
*http.Client
}
// userApp should be [A-Za-z0-9_\ \-\.]*
func SetAppName(userApp string) error {
UserAgent = fmt.Sprintf(
"QiniuGo/%s (%s; %s; %s) %s", conf.Version, runtime.GOOS, runtime.GOARCH, userApp, runtime.Version())
return nil
}
// --------------------------------------------------------------------
func newRequest(ctx Context, method, reqUrl string, headers http.Header, body io.Reader) (req *http.Request, err error) {
req, err = http.NewRequest(method, reqUrl, body)
if err != nil {
return
}
if headers == nil {
headers = http.Header{}
}
req.Header = headers
//check access token
mac, ok := ctx.Value("mac").(*qbox.Mac)
if ok {
token, signErr := mac.SignRequest(req)
if signErr != nil {
err = signErr
return
}
req.Header.Add("Authorization", "QBox "+token)
}
return
}
func (r Client) DoRequest(ctx Context, method, reqUrl string, headers http.Header) (resp *http.Response, err error) {
req, err := newRequest(ctx, method, reqUrl, headers, nil)
if err != nil {
return
}
return r.Do(ctx, req)
}
func (r Client) DoRequestWith(ctx Context, method, reqUrl string, headers http.Header, body io.Reader,
bodyLength int) (resp *http.Response, err error) {
req, err := newRequest(ctx, method, reqUrl, headers, body)
if err != nil {
return
}
req.ContentLength = int64(bodyLength)
return r.Do(ctx, req)
}
func (r Client) DoRequestWith64(ctx Context, method, reqUrl string, headers http.Header, body io.Reader,
bodyLength int64) (resp *http.Response, err error) {
req, err := newRequest(ctx, method, reqUrl, headers, body)
if err != nil {
return
}
req.ContentLength = bodyLength
return r.Do(ctx, req)
}
func (r Client) DoRequestWithForm(ctx Context, method, reqUrl string, headers http.Header,
data map[string][]string) (resp *http.Response, err error) {
if headers == nil {
headers = http.Header{}
}
headers.Add("Content-Type", "application/x-www-form-urlencoded")
requestData := url.Values(data).Encode()
if method == "GET" || method == "HEAD" || method == "DELETE" {
if strings.ContainsRune(reqUrl, '?') {
reqUrl += "&"
} else {
reqUrl += "?"
}
return r.DoRequest(ctx, method, reqUrl+requestData, headers)
}
return r.DoRequestWith(ctx, method, reqUrl, headers, strings.NewReader(requestData), len(requestData))
}
func (r Client) DoRequestWithJson(ctx Context, method, reqUrl string, headers http.Header,
data interface{}) (resp *http.Response, err error) {
reqBody, err := json.Marshal(data)
if err != nil {
return
}
if headers == nil {
headers = http.Header{}
}
headers.Add("Content-Type", "application/json")
return r.DoRequestWith(ctx, method, reqUrl, headers, bytes.NewReader(reqBody), len(reqBody))
}
func (r Client) Do(ctx Context, req *http.Request) (resp *http.Response, err error) {
if ctx == nil {
ctx = Background()
}
if reqId, ok := reqid.FromContext(ctx); ok {
req.Header.Set("X-Reqid", reqId)
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", UserAgent)
}
transport := r.Transport // don't change r.Transport
if transport == nil {
transport = http.DefaultTransport
}
// avoid cancel() is called before Do(req), but isn't accurate
select {
case <-ctx.Done():
err = ctx.Err()
return
default:
}
if tr, ok := getRequestCanceler(transport); ok {
// support CancelRequest
reqC := make(chan bool, 1)
go func() {
resp, err = r.Client.Do(req)
reqC <- true
}()
select {
case <-reqC:
case <-ctx.Done():
tr.CancelRequest(req)
<-reqC
err = ctx.Err()
}
} else {
resp, err = r.Client.Do(req)
}
return
}
// --------------------------------------------------------------------
type ErrorInfo struct {
Err string `json:"error,omitempty"`
Key string `json:"key,omitempty"`
Reqid string `json:"reqid,omitempty"`
Errno int `json:"errno,omitempty"`
Code int `json:"code"`
}
func (r *ErrorInfo) ErrorDetail() string {
msg, _ := json.Marshal(r)
return string(msg)
}
func (r *ErrorInfo) Error() string {
return r.Err
}
func (r *ErrorInfo) RpcError() (code, errno int, key, err string) {
return r.Code, r.Errno, r.Key, r.Err
}
func (r *ErrorInfo) HttpCode() int {
return r.Code
}
// --------------------------------------------------------------------
func parseError(e *ErrorInfo, r io.Reader) {
body, err1 := ioutil.ReadAll(r)
if err1 != nil {
e.Err = err1.Error()
return
}
var ret struct {
Err string `json:"error"`
Key string `json:"key"`
Errno int `json:"errno"`
}
if json.Unmarshal(body, &ret) == nil && ret.Err != "" {
// qiniu error msg style returns here
e.Err, e.Key, e.Errno = ret.Err, ret.Key, ret.Errno
return
}
e.Err = string(body)
}
func ResponseError(resp *http.Response) (err error) {
e := &ErrorInfo{
Reqid: resp.Header.Get("X-Reqid"),
Code: resp.StatusCode,
}
if resp.StatusCode > 299 {
if resp.ContentLength != 0 {
ct, ok := resp.Header["Content-Type"]
if ok && strings.HasPrefix(ct[0], "application/json") {
parseError(e, resp.Body)
}
}
}
return e
}
func CallRet(ctx Context, ret interface{}, resp *http.Response) (err error) {
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode/100 == 2 {
if ret != nil && resp.ContentLength != 0 {
err = json.NewDecoder(resp.Body).Decode(ret)
if err != nil {
return
}
}
if resp.StatusCode == 200 {
return nil
}
}
return ResponseError(resp)
}
func (r Client) CallWithForm(ctx Context, ret interface{}, method, reqUrl string, headers http.Header,
param map[string][]string) (err error) {
resp, err := r.DoRequestWithForm(ctx, method, reqUrl, headers, param)
if err != nil {
return err
}
return CallRet(ctx, ret, resp)
}
func (r Client) CallWithJson(ctx Context, ret interface{}, method, reqUrl string, headers http.Header,
param interface{}) (err error) {
resp, err := r.DoRequestWithJson(ctx, method, reqUrl, headers, param)
if err != nil {
return err
}
return CallRet(ctx, ret, resp)
}
func (r Client) CallWith(ctx Context, ret interface{}, method, reqUrl string, headers http.Header, body io.Reader,
bodyLength int) (err error) {
resp, err := r.DoRequestWith(ctx, method, reqUrl, headers, body, bodyLength)
if err != nil {
return err
}
return CallRet(ctx, ret, resp)
}
func (r Client) CallWith64(ctx Context, ret interface{}, method, reqUrl string, headers http.Header, body io.Reader,
bodyLength int64) (err error) {
resp, err := r.DoRequestWith64(ctx, method, reqUrl, headers, body, bodyLength)
if err != nil {
return err
}
return CallRet(ctx, ret, resp)
}
func (r Client) Call(ctx Context, ret interface{}, method, reqUrl string, headers http.Header) (err error) {
resp, err := r.DoRequestWith(ctx, method, reqUrl, headers, nil, 0)
if err != nil {
return err
}
return CallRet(ctx, ret, resp)
}
// ---------------------------------------------------------------------------
type requestCanceler interface {
CancelRequest(req *http.Request)
}
type nestedObjectGetter interface {
NestedObject() interface{}
}
func getRequestCanceler(tp http.RoundTripper) (rc requestCanceler, ok bool) {
if rc, ok = tp.(requestCanceler); ok {
return
}
p := interface{}(tp)
for {
getter, ok1 := p.(nestedObjectGetter)
if !ok1 {
return
}
p = getter.NestedObject()
if rc, ok = p.(requestCanceler); ok {
return
}
}
}
// --------------------------------------------------------------------