github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/openstack/obs/conf.go (about)

     1  package obs
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  type securityProvider struct {
    19  	ak            string
    20  	sk            string
    21  	securityToken string
    22  }
    23  
    24  type urlHolder struct {
    25  	scheme string
    26  	host   string
    27  	port   int
    28  }
    29  
    30  type config struct {
    31  	securityProvider *securityProvider
    32  	urlHolder        *urlHolder
    33  	pathStyle        bool
    34  	cname            bool
    35  	sslVerify        bool
    36  	endpoint         string
    37  	signature        SignatureType
    38  	region           string
    39  	connectTimeout   int
    40  	socketTimeout    int
    41  	headerTimeout    int
    42  	idleConnTimeout  int
    43  	finalTimeout     int
    44  	maxRetryCount    int
    45  	proxyUrl         string
    46  	maxConnsPerHost  int
    47  	pemCerts         []byte
    48  	transport        *http.Transport
    49  	ctx              context.Context
    50  	maxRedirectCount int
    51  }
    52  
    53  func (conf config) String() string {
    54  	return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
    55  		"\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
    56  		"\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, proxyUrl:%s, maxRedirectCount:%d]",
    57  		conf.endpoint, conf.signature, conf.pathStyle, conf.region,
    58  		conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
    59  		conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.proxyUrl, conf.maxRedirectCount,
    60  	)
    61  }
    62  
    63  type Configurer func(conf *config)
    64  
    65  // WithSslVerify is a wrapper for WithSslVerifyAndPemCerts.
    66  func WithSslVerify(sslVerify bool) Configurer {
    67  	return WithSslVerifyAndPemCerts(sslVerify, nil)
    68  }
    69  
    70  // WithSslVerifyAndPemCerts is a configurer for ObsClient to set conf.sslVerify and conf.pemCerts.
    71  func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) Configurer {
    72  	return func(conf *config) {
    73  		conf.sslVerify = sslVerify
    74  		conf.pemCerts = pemCerts
    75  	}
    76  }
    77  
    78  // WithHeaderTimeout is a configurer for ObsClient to set the timeout period of obtaining the response headers.
    79  func WithHeaderTimeout(headerTimeout int) Configurer {
    80  	return func(conf *config) {
    81  		conf.headerTimeout = headerTimeout
    82  	}
    83  }
    84  
    85  // WithProxyUrl is a configurer for ObsClient to set HTTP proxy.
    86  func WithProxyUrl(proxyUrl string) Configurer {
    87  	return func(conf *config) {
    88  		conf.proxyUrl = proxyUrl
    89  	}
    90  }
    91  
    92  // WithMaxConnections is a configurer for ObsClient to set the maximum number of idle HTTP connections.
    93  func WithMaxConnections(maxConnsPerHost int) Configurer {
    94  	return func(conf *config) {
    95  		conf.maxConnsPerHost = maxConnsPerHost
    96  	}
    97  }
    98  
    99  // WithPathStyle is a configurer for ObsClient.
   100  func WithPathStyle(pathStyle bool) Configurer {
   101  	return func(conf *config) {
   102  		conf.pathStyle = pathStyle
   103  	}
   104  }
   105  
   106  // WithSignature is a configurer for ObsClient.
   107  func WithSignature(signature SignatureType) Configurer {
   108  	return func(conf *config) {
   109  		conf.signature = signature
   110  	}
   111  }
   112  
   113  // WithRegion is a configurer for ObsClient.
   114  func WithRegion(region string) Configurer {
   115  	return func(conf *config) {
   116  		conf.region = region
   117  	}
   118  }
   119  
   120  // WithConnectTimeout is a configurer for ObsClient to set timeout period for establishing
   121  // a http/https connection, in seconds.
   122  func WithConnectTimeout(connectTimeout int) Configurer {
   123  	return func(conf *config) {
   124  		conf.connectTimeout = connectTimeout
   125  	}
   126  }
   127  
   128  // WithSocketTimeout is a configurer for ObsClient to set the timeout duration for transmitting data at
   129  // the socket layer, in seconds.
   130  func WithSocketTimeout(socketTimeout int) Configurer {
   131  	return func(conf *config) {
   132  		conf.socketTimeout = socketTimeout
   133  	}
   134  }
   135  
   136  // WithIdleConnTimeout is a configurer for ObsClient to set the timeout period of an idle HTTP connection
   137  // in the connection pool, in seconds.
   138  func WithIdleConnTimeout(idleConnTimeout int) Configurer {
   139  	return func(conf *config) {
   140  		conf.idleConnTimeout = idleConnTimeout
   141  	}
   142  }
   143  
   144  // WithMaxRetryCount is a configurer for ObsClient to set the maximum number of retries when an HTTP/HTTPS connection is abnormal.
   145  func WithMaxRetryCount(maxRetryCount int) Configurer {
   146  	return func(conf *config) {
   147  		conf.maxRetryCount = maxRetryCount
   148  	}
   149  }
   150  
   151  // WithSecurityToken is a configurer for ObsClient to set the security token in the temporary access keys.
   152  func WithSecurityToken(securityToken string) Configurer {
   153  	return func(conf *config) {
   154  		conf.securityProvider.securityToken = securityToken
   155  	}
   156  }
   157  
   158  // WithHttpTransport is a configurer for ObsClient to set the customized http Transport.
   159  func WithHttpTransport(transport *http.Transport) Configurer {
   160  	return func(conf *config) {
   161  		conf.transport = transport
   162  	}
   163  }
   164  
   165  // WithRequestContext is a configurer for ObsClient to set the context for each HTTP request.
   166  func WithRequestContext(ctx context.Context) Configurer {
   167  	return func(conf *config) {
   168  		conf.ctx = ctx
   169  	}
   170  }
   171  
   172  // WithCustomDomainName is a configurer for ObsClient.
   173  func WithCustomDomainName(cname bool) Configurer {
   174  	return func(conf *config) {
   175  		conf.cname = cname
   176  	}
   177  }
   178  
   179  // WithMaxRedirectCount is a configurer for ObsClient to set the maximum number of times that the request is redirected.
   180  func WithMaxRedirectCount(maxRedirectCount int) Configurer {
   181  	return func(conf *config) {
   182  		conf.maxRedirectCount = maxRedirectCount
   183  	}
   184  }
   185  
   186  func (conf *config) prepareConfig() {
   187  	if conf.connectTimeout <= 0 {
   188  		conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
   189  	}
   190  
   191  	if conf.socketTimeout <= 0 {
   192  		conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
   193  	}
   194  
   195  	conf.finalTimeout = conf.socketTimeout * 10
   196  
   197  	if conf.headerTimeout <= 0 {
   198  		conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
   199  	}
   200  
   201  	if conf.idleConnTimeout < 0 {
   202  		conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
   203  	}
   204  
   205  	if conf.maxRetryCount < 0 {
   206  		conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
   207  	}
   208  
   209  	if conf.maxConnsPerHost <= 0 {
   210  		conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
   211  	}
   212  
   213  	if conf.maxRedirectCount < 0 {
   214  		conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT
   215  	}
   216  
   217  	if conf.pathStyle && conf.signature == SignatureObs {
   218  		conf.signature = SignatureV2
   219  	}
   220  }
   221  
   222  func (conf *config) initConfigWithDefault() error {
   223  	conf.securityProvider.ak = strings.TrimSpace(conf.securityProvider.ak)
   224  	conf.securityProvider.sk = strings.TrimSpace(conf.securityProvider.sk)
   225  	conf.securityProvider.securityToken = strings.TrimSpace(conf.securityProvider.securityToken)
   226  	conf.endpoint = strings.TrimSpace(conf.endpoint)
   227  	if conf.endpoint == "" {
   228  		return errors.New("endpoint is not set")
   229  	}
   230  
   231  	if index := strings.Index(conf.endpoint, "?"); index > 0 {
   232  		conf.endpoint = conf.endpoint[:index]
   233  	}
   234  
   235  	for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
   236  		conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
   237  	}
   238  
   239  	if conf.signature == "" {
   240  		conf.signature = DEFAULT_SIGNATURE
   241  	}
   242  
   243  	urlHolder := &urlHolder{}
   244  	var address string
   245  	if strings.HasPrefix(conf.endpoint, "https://") {
   246  		urlHolder.scheme = "https"
   247  		address = conf.endpoint[len("https://"):]
   248  	} else if strings.HasPrefix(conf.endpoint, "http://") {
   249  		urlHolder.scheme = "http"
   250  		address = conf.endpoint[len("http://"):]
   251  	} else {
   252  		urlHolder.scheme = "https"
   253  		address = conf.endpoint
   254  	}
   255  
   256  	addr := strings.Split(address, ":")
   257  	if len(addr) == 2 {
   258  		if port, err := strconv.Atoi(addr[1]); err == nil {
   259  			urlHolder.port = port
   260  		}
   261  	}
   262  	urlHolder.host = addr[0]
   263  	if urlHolder.port == 0 {
   264  		if urlHolder.scheme == "https" {
   265  			urlHolder.port = 443
   266  		} else {
   267  			urlHolder.port = 80
   268  		}
   269  	}
   270  
   271  	if IsIP(urlHolder.host) {
   272  		conf.pathStyle = true
   273  	}
   274  
   275  	conf.urlHolder = urlHolder
   276  
   277  	conf.region = strings.TrimSpace(conf.region)
   278  	if conf.region == "" {
   279  		conf.region = DEFAULT_REGION
   280  	}
   281  
   282  	conf.prepareConfig()
   283  	conf.proxyUrl = strings.TrimSpace(conf.proxyUrl)
   284  	return nil
   285  }
   286  
   287  func (conf *config) getTransport() error {
   288  	if conf.transport == nil {
   289  		conf.transport = &http.Transport{
   290  			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
   291  				conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
   292  				if err != nil {
   293  					return nil, err
   294  				}
   295  				return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
   296  			},
   297  			MaxIdleConns:          conf.maxConnsPerHost,
   298  			MaxIdleConnsPerHost:   conf.maxConnsPerHost,
   299  			ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
   300  			IdleConnTimeout:       time.Second * time.Duration(conf.idleConnTimeout),
   301  		}
   302  
   303  		if conf.proxyUrl != "" {
   304  			proxyUrl, err := url.Parse(conf.proxyUrl)
   305  			if err != nil {
   306  				return err
   307  			}
   308  			conf.transport.Proxy = http.ProxyURL(proxyUrl)
   309  		}
   310  
   311  		tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
   312  		if conf.sslVerify && conf.pemCerts != nil {
   313  			pool := x509.NewCertPool()
   314  			pool.AppendCertsFromPEM(conf.pemCerts)
   315  			tlsConfig.RootCAs = pool
   316  		}
   317  
   318  		conf.transport.TLSClientConfig = tlsConfig
   319  	}
   320  
   321  	return nil
   322  }
   323  
   324  func checkRedirectFunc(_ *http.Request, _ []*http.Request) error {
   325  	return http.ErrUseLastResponse
   326  }
   327  
   328  func DummyQueryEscape(s string) string {
   329  	return s
   330  }
   331  
   332  func (conf *config) prepareBaseURL(bucketName string) (requestURL string, canonicalizedURL string) {
   333  	urlHolder := conf.urlHolder
   334  	if conf.cname {
   335  		requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
   336  		if conf.signature == "v4" {
   337  			canonicalizedURL = "/"
   338  		} else {
   339  			canonicalizedURL = "/" + urlHolder.host + "/"
   340  		}
   341  	} else {
   342  		if bucketName == "" {
   343  			requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
   344  			canonicalizedURL = "/"
   345  		} else {
   346  			if conf.pathStyle {
   347  				requestURL = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
   348  				canonicalizedURL = "/" + bucketName
   349  			} else {
   350  				requestURL = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
   351  				if conf.signature == "v2" || conf.signature == "OBS" {
   352  					canonicalizedURL = "/" + bucketName + "/"
   353  				} else {
   354  					canonicalizedURL = "/"
   355  				}
   356  			}
   357  		}
   358  	}
   359  	return
   360  }
   361  
   362  func (conf *config) prepareObjectKey(escape bool, objectKey string, escapeFunc func(s string) string) (encodeObjectKey string) {
   363  	if escape {
   364  		tempKey := []rune(objectKey)
   365  		result := make([]string, 0, len(tempKey))
   366  		for _, value := range tempKey {
   367  			if string(value) == "/" {
   368  				result = append(result, string(value))
   369  			} else {
   370  				if string(value) == " " {
   371  					result = append(result, url.PathEscape(string(value)))
   372  				} else {
   373  					result = append(result, url.QueryEscape(string(value)))
   374  				}
   375  			}
   376  		}
   377  		encodeObjectKey = strings.Join(result, "")
   378  	} else {
   379  		encodeObjectKey = escapeFunc(objectKey)
   380  	}
   381  	return
   382  }
   383  
   384  func (conf *config) prepareEscapeFunc(escape bool) (escapeFunc func(s string) string) {
   385  	if escape {
   386  		return url.QueryEscape
   387  	}
   388  	return DummyQueryEscape
   389  }
   390  
   391  func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestUrl string, canonicalizedUrl string) {
   392  	requestUrl, canonicalizedUrl = conf.prepareBaseURL(bucketName)
   393  
   394  	if objectKey != "" {
   395  		encodeObjectKey := conf.prepareObjectKey(escape, objectKey, conf.prepareEscapeFunc(escape))
   396  		requestUrl += "/" + encodeObjectKey
   397  		if !strings.HasSuffix(canonicalizedUrl, "/") {
   398  			canonicalizedUrl += "/"
   399  		}
   400  		canonicalizedUrl += encodeObjectKey
   401  	}
   402  
   403  	keys := make([]string, 0, len(params))
   404  	for key := range params {
   405  		keys = append(keys, strings.TrimSpace(key))
   406  	}
   407  	sort.Strings(keys)
   408  	i := 0
   409  
   410  	for index, key := range keys {
   411  		if index == 0 {
   412  			requestUrl += "?"
   413  		} else {
   414  			requestUrl += "&"
   415  		}
   416  		_key := url.QueryEscape(key)
   417  		requestUrl += _key
   418  
   419  		_value := params[key]
   420  		if conf.signature == "v4" {
   421  			requestUrl += "=" + url.QueryEscape(_value)
   422  		} else {
   423  			if _value != "" {
   424  				requestUrl += "=" + url.QueryEscape(_value)
   425  				_value = "=" + _value
   426  			} else {
   427  				_value = ""
   428  			}
   429  			lowerKey := strings.ToLower(key)
   430  			_, ok := allowedResourceParameterNames[lowerKey]
   431  			prefixHeader := HEADER_PREFIX
   432  			isObs := conf.signature == SignatureObs
   433  			if isObs {
   434  				prefixHeader = HEADER_PREFIX_OBS
   435  			}
   436  			ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
   437  			if ok {
   438  				if i == 0 {
   439  					canonicalizedUrl += "?"
   440  				} else {
   441  					canonicalizedUrl += "&"
   442  				}
   443  				canonicalizedUrl += getQueryUrl(_key, _value)
   444  				i++
   445  			}
   446  		}
   447  	}
   448  	return
   449  }
   450  
   451  func getQueryUrl(key, value string) string {
   452  	queryUrl := ""
   453  	queryUrl += key
   454  	queryUrl += value
   455  	return queryUrl
   456  }