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