client.go raw
1 /*
2 * Copyright 2017 Baidu, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 * either express or implied. See the License for the specific language governing permissions
12 * and limitations under the License.
13 */
14
15 // client.go - definiton the BceClientConfiguration and BceClient structure
16
17 // Package bce implements the infrastructure to access BCE services.
18 //
19 // - BceClient:
20 // It is the general client of BCE to access all services. It builds http request to access the
21 // services based on the given client configuration.
22 //
23 // - BceClientConfiguration:
24 // The client configuration data structure which contains endpoint, region, credentials, retry
25 // policy, sign options and so on. It supports most of the default value and user can also
26 // access or change the default with its public fields' name.
27 //
28 // - Error types:
29 // The error types when making request or receiving response to the BCE services contains two
30 // types: the BceClientError when making request to BCE services and the BceServiceError when
31 // recieving response from them.
32 //
33 // - BceRequest:
34 // The request instance stands for an request to access the BCE services.
35 //
36 // - BceResponse:
37 // The response instance stands for an response from the BCE services.
38 package bce
39
40 import (
41 "bytes"
42 "fmt"
43 "io"
44 "io/ioutil"
45 net_http "net/http"
46 "time"
47
48 "github.com/baidubce/bce-sdk-go/auth"
49 "github.com/baidubce/bce-sdk-go/http"
50 "github.com/baidubce/bce-sdk-go/util"
51 "github.com/baidubce/bce-sdk-go/util/log"
52 )
53
54 // Client is the general interface which can perform sending request. Different service
55 // will define its own client in case of specific extension.
56 type Client interface {
57 SendRequest(*BceRequest, *BceResponse) error
58 SendRequestFromBytes(*BceRequest, *BceResponse, []byte) error
59 GetBceClientConfig() *BceClientConfiguration
60 }
61
62 // BceClient defines the general client to access the BCE services.
63 type BceClient struct {
64 Config *BceClientConfiguration
65 Signer auth.Signer // the sign algorithm
66 RateLimiters RateLimiters
67 HTTPClient *net_http.Client
68 }
69
70 // BuildHttpRequest - the helper method for the client to build http request
71 //
72 // PARAMS:
73 // - request: the input request object to be built
74 func (c *BceClient) buildHttpRequest(request *BceRequest) {
75 // Construct the http request instance for the special fields
76 request.BuildHttpRequest()
77
78 // Set the client specific configurations
79 if request.Endpoint() == "" {
80 request.SetEndpoint(c.Config.Endpoint)
81 }
82 if request.Protocol() == "" {
83 request.SetProtocol(DEFAULT_PROTOCOL)
84 }
85 if len(c.Config.ProxyUrl) != 0 {
86 request.SetProxyUrl(c.Config.ProxyUrl)
87 }
88 request.SetTimeout(c.Config.ConnectionTimeoutInMillis / 1000)
89
90 // Set the BCE request headers
91 request.SetHeader(http.HOST, request.Host())
92 request.SetHeader(http.USER_AGENT, c.Config.UserAgent)
93 request.SetHeader(http.BCE_DATE, util.FormatISO8601Date(util.NowUTCSeconds()))
94
95 //set default content-type if null
96 if request.Header(http.CONTENT_TYPE) == "" {
97 request.SetHeader(http.CONTENT_TYPE, DEFAULT_CONTENT_TYPE)
98 }
99
100 // Generate the auth string if needed
101 if c.Config.Credentials != nil {
102 c.Signer.Sign(&request.Request, c.Config.Credentials, c.Config.SignOption)
103 }
104 if c.HTTPClient != nil {
105 request.SetHTTPClient(c.HTTPClient)
106 }
107 }
108
109 // SendRequest - the client performs sending the http request with retry policy and receive the
110 // response from the BCE services.
111 //
112 // PARAMS:
113 // - req: the request object to be sent to the BCE service
114 // - resp: the response object to receive the content from BCE service
115 //
116 // RETURNS:
117 // - error: nil if ok otherwise the specific error
118 func (c *BceClient) SendRequest(req *BceRequest, resp *BceResponse) error {
119 // Return client error if it is not nil
120 if req.ClientError() != nil {
121 return req.ClientError()
122 }
123
124 // Build the http request and prepare to send
125 c.buildHttpRequest(req)
126 log.Infof("send http request: %v", req)
127
128 // Send request with the given retry policy
129 retries := 0
130 if req.Body() != nil {
131 defer req.Body().Close() // Manually close the ReadCloser body for retry
132 }
133 for {
134 // The request body should be temporarily saved if retry to send the http request
135 var retryBuf bytes.Buffer
136 var teeReader io.Reader
137 if c.Config.Retry.ShouldRetry(nil, 0) && req.Body() != nil {
138 teeReader = io.TeeReader(req.Body(), &retryBuf)
139 req.Request.SetBody(ioutil.NopCloser(teeReader))
140 }
141 httpResp, err := http.Execute(&req.Request)
142
143 if err != nil {
144 if c.Config.Retry.ShouldRetry(err, retries) {
145 delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries)
146 time.Sleep(delay_in_mills)
147 } else {
148 return &BceClientError{
149 fmt.Sprintf("execute http request failed! Retried %d times, error: %v",
150 retries, err)}
151 }
152 retries++
153 log.Warnf("send request failed: %v, retry for %d time(s)", err, retries)
154 if req.Body() != nil {
155 ioutil.ReadAll(teeReader)
156 req.Request.SetBody(ioutil.NopCloser(&retryBuf))
157 }
158 continue
159 }
160 resp.SetHttpResponse(httpResp)
161 resp.ParseResponse()
162
163 log.Infof("receive http response: status: %s, debugId: %s, requestId: %s, elapsed: %v",
164 resp.StatusText(), resp.DebugId(), resp.RequestId(), resp.ElapsedTime())
165
166 // not print this warn log with upload/download rate limit
167 if resp.ElapsedTime().Milliseconds() > DEFAULT_WARN_LOG_TIMEOUT_IN_MILLS &&
168 (c.Config.UploadRatelimit == nil && c.Config.DownloadRatelimit == nil) {
169 log.Warnf("request time more than 5 second, debugId: %s, requestId: %s, elapsed: %v",
170 resp.DebugId(), resp.RequestId(), resp.ElapsedTime())
171 }
172 for k, v := range resp.Headers() {
173 log.Debugf("%s=%s", k, v)
174 }
175 if resp.IsFail() {
176 err := resp.ServiceError()
177 if c.Config.Retry.ShouldRetry(err, retries) {
178 delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries)
179 time.Sleep(delay_in_mills)
180 } else {
181 return err
182 }
183 retries++
184 log.Warnf("send request failed, retry for %d time(s)", retries)
185 if req.Body() != nil {
186 ioutil.ReadAll(teeReader)
187 req.Request.SetBody(ioutil.NopCloser(&retryBuf))
188 }
189 continue
190 }
191 return nil
192 }
193 }
194
195 // SendRequestFromBytes - the client performs sending the http request with retry policy and receive the
196 // response from the BCE services.
197 //
198 // PARAMS:
199 // - req: the request object to be sent to the BCE service
200 // - resp: the response object to receive the content from BCE service
201 // - content: the content of body
202 //
203 // RETURNS:
204 // - error: nil if ok otherwise the specific error
205 func (c *BceClient) SendRequestFromBytes(req *BceRequest, resp *BceResponse, content []byte) error {
206 // Return client error if it is not nil
207 if req.ClientError() != nil {
208 return req.ClientError()
209 }
210 // Build the http request and prepare to send
211 c.buildHttpRequest(req)
212 log.Infof("send http request: %v", req)
213 // Send request with the given retry policy
214 retries := 0
215 for {
216 // The request body should be temporarily saved if retry to send the http request
217 buf := bytes.NewBuffer(content)
218 req.Request.SetBody(ioutil.NopCloser(buf))
219 defer req.Request.Body().Close() // Manually close the ReadCloser body for retry
220 httpResp, err := http.Execute(&req.Request)
221 if err != nil {
222 if c.Config.Retry.ShouldRetry(err, retries) {
223 delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries)
224 time.Sleep(delay_in_mills)
225 } else {
226 return &BceClientError{
227 fmt.Sprintf("execute http request failed! Retried %d times, error: %v",
228 retries, err)}
229 }
230 retries++
231 log.Warnf("send request failed: %v, retry for %d time(s)", err, retries)
232 continue
233 }
234 resp.SetHttpResponse(httpResp)
235 resp.ParseResponse()
236 log.Infof("receive http response: status: %s, debugId: %s, requestId: %s, elapsed: %v",
237 resp.StatusText(), resp.DebugId(), resp.RequestId(), resp.ElapsedTime())
238 for k, v := range resp.Headers() {
239 log.Debugf("%s=%s", k, v)
240 }
241 if resp.IsFail() {
242 err := resp.ServiceError()
243 if c.Config.Retry.ShouldRetry(err, retries) {
244 delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries)
245 time.Sleep(delay_in_mills)
246 } else {
247 return err
248 }
249 retries++
250 log.Warnf("send request failed, retry for %d time(s)", retries)
251 continue
252 }
253 return nil
254 }
255 }
256
257 func (c *BceClient) GetBceClientConfig() *BceClientConfiguration {
258 return c.Config
259 }
260
261 func NewBceClientWithExclusiveHTTPClient(conf *BceClientConfiguration, sign auth.Signer) (*BceClient, error) {
262 clientConfig := &http.ClientConfig{
263 RedirectDisabled: conf.RedirectDisabled,
264 DisableKeepAlives: conf.DisableKeepAlives,
265 NoVerifySSL: conf.NoVerifySSL,
266 DialTimeout: conf.DialTimeout,
267 KeepAlive: conf.KeepAlive,
268 ReadTimeout: conf.ReadTimeout,
269 WriteTimeout: conf.WriteTimeOut,
270 TLSHandshakeTimeout: conf.TLSHandshakeTimeout,
271 IdleConnectionTimeout: conf.IdleConnectionTimeout,
272 ResponseHeaderTimeout: conf.ResponseHeaderTimeout,
273 HTTPClientTimeout: conf.HTTPClientTimeout,
274 }
275
276 bceClient := &BceClient{
277 Config: conf,
278 Signer: sign,
279 }
280 if conf.UploadRatelimit != nil {
281 value := *conf.UploadRatelimit * 1024
282 tb := newRateLimiter(value)
283 clientConfig.PostWrite = append(clientConfig.PostWrite, func(n int, _ error) {
284 tb.LimitBandwidth(n)
285 })
286 bceClient.RateLimiters[RateLimiterSlotTx] = tb
287 }
288 if conf.DownloadRatelimit != nil {
289 value := *conf.DownloadRatelimit * 1024
290 tb := newRateLimiter(value)
291 clientConfig.PostRead = append(clientConfig.PostRead, func(n int, _ error) {
292 tb.LimitBandwidth(n)
293 })
294 bceClient.RateLimiters[RateLimiterSlotRx] = tb
295 }
296
297 if conf.HTTPClient != nil {
298 bceClient.HTTPClient = conf.HTTPClient
299 err := http.InitWithSpecifiedClient(conf.HTTPClient)
300 if err != nil {
301 return nil, err
302 }
303 } else {
304 bceClient.HTTPClient = http.InitExclusiveHTTPClient(clientConfig)
305 }
306 return bceClient, nil
307 }
308
309 func NewBceClientWithTimeout(conf *BceClientConfiguration, sign auth.Signer) *BceClient {
310 clientConfig := &http.ClientConfig{
311 RedirectDisabled: conf.RedirectDisabled,
312 DisableKeepAlives: conf.DisableKeepAlives,
313 NoVerifySSL: conf.NoVerifySSL,
314 DialTimeout: conf.DialTimeout,
315 KeepAlive: conf.KeepAlive,
316 ReadTimeout: conf.ReadTimeout,
317 WriteTimeout: conf.WriteTimeOut,
318 TLSHandshakeTimeout: conf.TLSHandshakeTimeout,
319 IdleConnectionTimeout: conf.IdleConnectionTimeout,
320 ResponseHeaderTimeout: conf.ResponseHeaderTimeout,
321 HTTPClientTimeout: conf.HTTPClientTimeout,
322 }
323
324 http.InitClientWithTimeout(clientConfig)
325 return &BceClient{Config: conf, Signer: sign}
326 }
327
328 func NewBceClient(conf *BceClientConfiguration, sign auth.Signer) *BceClient {
329 clientConfig := http.ClientConfig{
330 RedirectDisabled: conf.RedirectDisabled,
331 DisableKeepAlives: conf.DisableKeepAlives,
332 }
333 http.InitClient(clientConfig)
334 return &BceClient{Config: conf, Signer: sign}
335 }
336
337 func NewBceClientWithAkSk(ak, sk, endPoint string) (*BceClient, error) {
338 credentials, err := auth.NewBceCredentials(ak, sk)
339 if err != nil {
340 return nil, err
341 }
342 defaultSignOptions := &auth.SignOptions{
343 HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN,
344 ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS}
345 defaultConf := &BceClientConfiguration{
346 Endpoint: endPoint,
347 Region: DEFAULT_REGION,
348 UserAgent: DEFAULT_USER_AGENT,
349 Credentials: credentials,
350 SignOption: defaultSignOptions,
351 Retry: DEFAULT_RETRY_POLICY,
352 ConnectionTimeoutInMillis: DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS,
353 RedirectDisabled: false}
354 v1Signer := &auth.BceV1Signer{}
355
356 return NewBceClient(defaultConf, v1Signer), nil
357 }
358