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