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 }