github.com/morlay/goqcloud@v0.0.0-20181123023149-b00e0b0b9afc/client.go (about)

     1  package goqcloud
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"reflect"
    11  	"time"
    12  
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/morlay/goqcloud/signature"
    16  	"github.com/morlay/goqcloud/transform"
    17  )
    18  
    19  type Client interface {
    20  	Request(service string, action string, version string) RequestSender
    21  }
    22  
    23  type RequestSender interface {
    24  	Do(req interface{}, resp interface{}) error
    25  }
    26  
    27  type MaybeErrorResponse interface {
    28  	GetError() error
    29  }
    30  
    31  func NewClientWithCredential(secretId, secretKey string, opts ...*ClientOption) *ClientWithCredential {
    32  	return &ClientWithCredential{
    33  		ClientOption: ComposeClientOptions(opts...),
    34  		Credential: signature.Credential{
    35  			SecretId:  secretId,
    36  			SecretKey: secretKey,
    37  		},
    38  	}
    39  }
    40  
    41  func ComposeClientOptions(opts ...*ClientOption) *ClientOption {
    42  	if len(opts) == 0 {
    43  		return nil
    44  	}
    45  
    46  	opt := &ClientOption{}
    47  
    48  	for i := range opts {
    49  		o := opts[i]
    50  		if o.Timeout != 0 {
    51  			opt.Timeout = o.Timeout
    52  		}
    53  		if o.Transports != nil {
    54  			opt.Transports = o.Transports
    55  		}
    56  	}
    57  
    58  	return opt
    59  }
    60  
    61  func ClientOptionWithTimeout(timeout time.Duration) *ClientOption {
    62  	return &ClientOption{
    63  		Timeout: timeout,
    64  	}
    65  }
    66  
    67  func ClientOptionWithTransports(transports ...transform.Transport) *ClientOption {
    68  	return &ClientOption{
    69  		Transports: transports,
    70  	}
    71  }
    72  
    73  type ClientOption struct {
    74  	Timeout    time.Duration
    75  	Transports []transform.Transport
    76  }
    77  
    78  type ClientWithCredential struct {
    79  	*ClientOption
    80  	signature.Credential
    81  }
    82  
    83  func (c *ClientWithCredential) Request(service string, action string, version string) RequestSender {
    84  	opt := c.ClientOption
    85  	if opt == nil {
    86  		opt = &ClientOption{}
    87  	}
    88  
    89  	opt.Transports = append(opt.Transports, signature.NewAutoSignTransport(&c.Credential))
    90  
    91  	return &httpRequestSender{
    92  		service:      service,
    93  		action:       action,
    94  		version:      version,
    95  		ClientOption: *opt,
    96  	}
    97  }
    98  
    99  type httpRequestSender struct {
   100  	service string
   101  	version string
   102  	action  string
   103  	ClientOption
   104  }
   105  
   106  func (r *httpRequestSender) Do(req interface{}, resp interface{}) error {
   107  	request, errForTransform := r.transformRequest(req)
   108  	if errForTransform != nil {
   109  		return NewTencentCloudError(
   110  			"SDK.TransformRequestFailed",
   111  			errForTransform.Error(),
   112  		)
   113  	}
   114  
   115  	if r.Timeout == 0 {
   116  		r.Timeout = 60 * time.Second
   117  	}
   118  
   119  	client := &http.Client{}
   120  	client.Timeout = r.Timeout
   121  	client.Transport = transform.ComposeTransports(r.Transports...)(&http.Transport{
   122  		DialContext: (&net.Dialer{
   123  			Timeout:   client.Timeout,
   124  			KeepAlive: 0,
   125  		}).DialContext,
   126  		DisableKeepAlives: true,
   127  	})
   128  
   129  	response, errForDo := client.Do(request)
   130  	if errForDo != nil {
   131  		return NewTencentCloudError(
   132  			"SDK.DoRequestFailed",
   133  			errForDo.Error(),
   134  		)
   135  	}
   136  	defer response.Body.Close()
   137  
   138  	data, err := ioutil.ReadAll(response.Body)
   139  	if err != nil {
   140  		return NewTencentCloudError(
   141  			"SDK.ResponseBodyReadFailed",
   142  			err.Error(),
   143  		)
   144  	}
   145  
   146  	if err := json.Unmarshal(data, &TencentCloudResponse{Response: resp}); err != nil {
   147  		return NewTencentCloudError(
   148  			"SDK.JSONUnmarshalFailed",
   149  			err.Error(),
   150  		)
   151  	}
   152  
   153  	if mayError, ok := resp.(MaybeErrorResponse); ok {
   154  		return mayError.GetError()
   155  	}
   156  	return nil
   157  }
   158  
   159  func (r *httpRequestSender) transformRequest(req interface{}) (*http.Request, error) {
   160  	u, _ := url.Parse(fmt.Sprintf("https://%s.tencentcloudapi.com", r.service))
   161  
   162  	s := transform.NewParameterScanner()
   163  	if err := s.Scan(reflect.ValueOf(req)); err != nil {
   164  		return nil, err
   165  	}
   166  	params := s.Params()
   167  
   168  	params.Set("Action", r.action)
   169  	params.Set("Version", r.version)
   170  
   171  	u.RawQuery = params.Encode()
   172  
   173  	return http.NewRequest("GET", u.String(), nil)
   174  }
   175  
   176  type TencentCloudResponse struct {
   177  	Response interface{} `json:"Response"`
   178  }
   179  
   180  type TencentCloudBaseResponse struct {
   181  	RequestId string             `json:"RequestId"`
   182  	Error     *TencentCloudError `json:"Error,omitempty"`
   183  }
   184  
   185  func (resp *TencentCloudBaseResponse) GetError() error {
   186  	if resp.Error != nil {
   187  		return resp.Error
   188  	}
   189  	return nil
   190  }
   191  
   192  func NewTencentCloudError(code string, msg string) *TencentCloudError {
   193  	return &TencentCloudError{
   194  		Code:    code,
   195  		Message: msg,
   196  	}
   197  }
   198  
   199  type TencentCloudError struct {
   200  	Code    string `json:"Code"`
   201  	Message string `json:"Message"`
   202  }
   203  
   204  func (e *TencentCloudError) Error() string {
   205  	return fmt.Sprintf("[%s] %s", e.Code, e.Message)
   206  }
   207  
   208  func NewLogTransport() transform.Transport {
   209  	return func(rt http.RoundTripper) http.RoundTripper {
   210  		return &LogTransport{
   211  			NextRoundTripper: rt,
   212  		}
   213  	}
   214  }
   215  
   216  type LogTransport struct {
   217  	NextRoundTripper http.RoundTripper
   218  }
   219  
   220  func (t *LogTransport) RoundTrip(request *http.Request) (*http.Response, error) {
   221  	logrus.Infof("%s %s", request.Method, request.URL.String())
   222  	return t.NextRoundTripper.RoundTrip(request)
   223  }