github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/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 "os" 25 "sort" 26 "strconv" 27 "strings" 28 "sync" 29 "time" 30 31 "golang.org/x/net/http/httpproxy" 32 ) 33 34 type urlHolder struct { 35 scheme string 36 host string 37 port int 38 } 39 40 type config struct { 41 securityProviders []securityProvider 42 urlHolder *urlHolder 43 pathStyle bool 44 cname bool 45 sslVerify bool 46 disableKeepAlive bool 47 endpoint string 48 signature SignatureType 49 region string 50 connectTimeout int 51 socketTimeout int 52 headerTimeout int 53 idleConnTimeout int 54 finalTimeout int 55 maxRetryCount int 56 proxyURL string 57 noProxyURL string 58 proxyFromEnv bool 59 maxConnsPerHost int 60 pemCerts []byte 61 transport *http.Transport 62 roundTripper http.RoundTripper 63 httpClient *http.Client 64 ctx context.Context 65 maxRedirectCount int 66 userAgent string 67 enableCompression bool 68 progressListener ProgressListener 69 customProxyOnce sync.Once 70 customProxyFuncValue func(*url.URL) (*url.URL, error) 71 } 72 73 func (conf *config) String() string { 74 return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+ 75 "\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+ 76 "\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, maxRedirectCount:%d]", 77 conf.endpoint, conf.signature, conf.pathStyle, conf.region, 78 conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout, 79 conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.maxRedirectCount, 80 ) 81 } 82 83 type configurer func(conf *config) 84 85 func WithSecurityProviders(sps ...securityProvider) configurer { 86 return func(conf *config) { 87 for _, sp := range sps { 88 if sp != nil { 89 conf.securityProviders = append(conf.securityProviders, sp) 90 } 91 } 92 } 93 } 94 95 // WithSslVerify is a wrapper for WithSslVerifyAndPemCerts. 96 func WithSslVerify(sslVerify bool) configurer { 97 return WithSslVerifyAndPemCerts(sslVerify, nil) 98 } 99 100 // WithSslVerifyAndPemCerts is a configurer for ObsClient to set conf.sslVerify and conf.pemCerts. 101 func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer { 102 return func(conf *config) { 103 conf.sslVerify = sslVerify 104 conf.pemCerts = pemCerts 105 } 106 } 107 108 // WithHeaderTimeout is a configurer for ObsClient to set the timeout period of obtaining the response headers. 109 func WithHeaderTimeout(headerTimeout int) configurer { 110 return func(conf *config) { 111 conf.headerTimeout = headerTimeout 112 } 113 } 114 115 // WithProxyUrl is a configurer for ObsClient to set HTTP proxy. 116 func WithProxyUrl(proxyURL string) configurer { 117 return func(conf *config) { 118 conf.proxyURL = proxyURL 119 } 120 } 121 122 // WithNoProxyUrl is a configurer for ObsClient to set HTTP no_proxy. 123 func WithNoProxyUrl(noProxyURL string) configurer { 124 return func(conf *config) { 125 conf.noProxyURL = noProxyURL 126 } 127 } 128 129 // WithProxyFromEnv is a configurer for ObsClient to get proxy from evironment. 130 func WithProxyFromEnv(proxyFromEnv bool) configurer { 131 return func(conf *config) { 132 conf.proxyFromEnv = proxyFromEnv 133 } 134 } 135 136 // WithMaxConnections is a configurer for ObsClient to set the maximum number of idle HTTP connections. 137 func WithMaxConnections(maxConnsPerHost int) configurer { 138 return func(conf *config) { 139 conf.maxConnsPerHost = maxConnsPerHost 140 } 141 } 142 143 // WithPathStyle is a configurer for ObsClient. 144 func WithPathStyle(pathStyle bool) configurer { 145 return func(conf *config) { 146 conf.pathStyle = pathStyle 147 } 148 } 149 150 // WithSignature is a configurer for ObsClient. 151 func WithSignature(signature SignatureType) configurer { 152 return func(conf *config) { 153 conf.signature = signature 154 } 155 } 156 157 // WithRegion is a configurer for ObsClient. 158 func WithRegion(region string) configurer { 159 return func(conf *config) { 160 conf.region = region 161 } 162 } 163 164 // WithConnectTimeout is a configurer for ObsClient to set timeout period for establishing 165 // an http/https connection, in seconds. 166 func WithConnectTimeout(connectTimeout int) configurer { 167 return func(conf *config) { 168 conf.connectTimeout = connectTimeout 169 } 170 } 171 172 // WithSocketTimeout is a configurer for ObsClient to set the timeout duration for transmitting data at 173 // the socket layer, in seconds. 174 func WithSocketTimeout(socketTimeout int) configurer { 175 return func(conf *config) { 176 conf.socketTimeout = socketTimeout 177 } 178 } 179 180 // WithIdleConnTimeout is a configurer for ObsClient to set the timeout period of an idle HTTP connection 181 // in the connection pool, in seconds. 182 func WithIdleConnTimeout(idleConnTimeout int) configurer { 183 return func(conf *config) { 184 conf.idleConnTimeout = idleConnTimeout 185 } 186 } 187 188 // WithMaxRetryCount is a configurer for ObsClient to set the maximum number of retries when an HTTP/HTTPS connection is abnormal. 189 func WithMaxRetryCount(maxRetryCount int) configurer { 190 return func(conf *config) { 191 conf.maxRetryCount = maxRetryCount 192 } 193 } 194 195 // WithSecurityToken is a configurer for ObsClient to set the security token in the temporary access keys. 196 func WithSecurityToken(securityToken string) configurer { 197 return func(conf *config) { 198 for _, sp := range conf.securityProviders { 199 if bsp, ok := sp.(*BasicSecurityProvider); ok { 200 sh := bsp.getSecurity() 201 bsp.refresh(sh.ak, sh.sk, securityToken) 202 break 203 } 204 } 205 } 206 } 207 208 // WithHttpTransport is a configurer for ObsClient to set the customized http Transport. 209 func WithHttpTransport(transport *http.Transport) configurer { 210 return func(conf *config) { 211 conf.transport = transport 212 } 213 } 214 215 func WithHttpClient(httpClient *http.Client) configurer { 216 return func(conf *config) { 217 conf.httpClient = httpClient 218 } 219 } 220 221 // WithRequestContext is a configurer for ObsClient to set the context for each HTTP request. 222 func WithRequestContext(ctx context.Context) configurer { 223 return func(conf *config) { 224 conf.ctx = ctx 225 } 226 } 227 228 // WithCustomDomainName is a configurer for ObsClient. 229 func WithCustomDomainName(cname bool) configurer { 230 return func(conf *config) { 231 conf.cname = cname 232 } 233 } 234 235 // WithDisableKeepAlive is a configurer for ObsClient to disable the keep-alive for http. 236 func WithDisableKeepAlive(disableKeepAlive bool) configurer { 237 return func(conf *config) { 238 conf.disableKeepAlive = disableKeepAlive 239 } 240 } 241 242 // WithMaxRedirectCount is a configurer for ObsClient to set the maximum number of times that the request is redirected. 243 func WithMaxRedirectCount(maxRedirectCount int) configurer { 244 return func(conf *config) { 245 conf.maxRedirectCount = maxRedirectCount 246 } 247 } 248 249 // WithUserAgent is a configurer for ObsClient to set the User-Agent. 250 func WithUserAgent(userAgent string) configurer { 251 return func(conf *config) { 252 conf.userAgent = userAgent 253 } 254 } 255 256 // WithEnableCompression is a configurer for ObsClient to set the Transport.DisableCompression. 257 func WithEnableCompression(enableCompression bool) configurer { 258 return func(conf *config) { 259 conf.enableCompression = enableCompression 260 } 261 } 262 263 func (conf *config) prepareConfig() { 264 if conf.connectTimeout <= 0 { 265 conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT 266 } 267 268 if conf.socketTimeout <= 0 { 269 conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT 270 } 271 272 conf.finalTimeout = conf.socketTimeout * 10 273 274 if conf.headerTimeout <= 0 { 275 conf.headerTimeout = DEFAULT_HEADER_TIMEOUT 276 } 277 278 if conf.idleConnTimeout < 0 { 279 conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT 280 } 281 282 if conf.maxRetryCount < 0 { 283 conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT 284 } 285 286 if conf.maxConnsPerHost <= 0 { 287 conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST 288 } 289 290 if conf.maxRedirectCount < 0 { 291 conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT 292 } 293 294 if conf.pathStyle && conf.signature == SignatureObs { 295 conf.signature = SignatureV2 296 } 297 } 298 299 func (conf *config) initConfigWithDefault() error { 300 conf.endpoint = strings.TrimSpace(conf.endpoint) 301 if conf.endpoint == "" { 302 return errors.New("endpoint is not set") 303 } 304 305 if index := strings.Index(conf.endpoint, "?"); index > 0 { 306 conf.endpoint = conf.endpoint[:index] 307 } 308 309 for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 { 310 conf.endpoint = conf.endpoint[:len(conf.endpoint)-1] 311 } 312 313 if conf.signature == "" { 314 conf.signature = DEFAULT_SIGNATURE 315 } 316 317 urlHolder := &urlHolder{} 318 var address string 319 if strings.HasPrefix(conf.endpoint, "https://") { 320 urlHolder.scheme = "https" 321 address = conf.endpoint[len("https://"):] 322 } else if strings.HasPrefix(conf.endpoint, "http://") { 323 urlHolder.scheme = "http" 324 address = conf.endpoint[len("http://"):] 325 } else { 326 urlHolder.scheme = "https" 327 address = conf.endpoint 328 } 329 330 addr := strings.Split(address, ":") 331 if len(addr) == 2 { 332 if port, err := strconv.Atoi(addr[1]); err == nil { 333 urlHolder.port = port 334 } 335 } 336 urlHolder.host = addr[0] 337 if urlHolder.port == 0 { 338 if urlHolder.scheme == "https" { 339 urlHolder.port = 443 340 } else { 341 urlHolder.port = 80 342 } 343 } 344 345 if IsIP(urlHolder.host) { 346 conf.pathStyle = true 347 } 348 349 conf.urlHolder = urlHolder 350 351 conf.region = strings.TrimSpace(conf.region) 352 if conf.region == "" { 353 conf.region = DEFAULT_REGION 354 } 355 356 conf.prepareConfig() 357 conf.proxyURL = strings.TrimSpace(conf.proxyURL) 358 return nil 359 } 360 361 func (conf *config) getTransport() error { 362 if conf.transport == nil { 363 conf.transport = &http.Transport{ 364 Dial: func(network, addr string) (net.Conn, error) { 365 conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout)) 366 if err != nil { 367 return nil, err 368 } 369 return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil 370 }, 371 MaxIdleConns: conf.maxConnsPerHost, 372 MaxIdleConnsPerHost: conf.maxConnsPerHost, 373 ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout), 374 IdleConnTimeout: time.Second * time.Duration(conf.idleConnTimeout), 375 DisableKeepAlives: conf.disableKeepAlive, 376 } 377 if conf.proxyURL != "" { 378 conf.transport.Proxy = conf.customProxyFromEnvironment 379 } else if conf.proxyFromEnv { 380 conf.transport.Proxy = http.ProxyFromEnvironment 381 } 382 383 tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify} 384 if conf.sslVerify && conf.pemCerts != nil { 385 pool := x509.NewCertPool() 386 pool.AppendCertsFromPEM(conf.pemCerts) 387 tlsConfig.RootCAs = pool 388 } 389 390 conf.transport.TLSClientConfig = tlsConfig 391 conf.transport.DisableCompression = !conf.enableCompression 392 } 393 394 return nil 395 } 396 397 func (conf *config) customProxyFromEnvironment(req *http.Request) (*url.URL, error) { 398 url, err := conf.customProxyFunc()(req.URL) 399 return url, err 400 } 401 402 func (conf *config) customProxyFunc() func(*url.URL) (*url.URL, error) { 403 conf.customProxyOnce.Do(func() { 404 customhttpproxy := &httpproxy.Config{ 405 HTTPProxy: conf.proxyURL, 406 HTTPSProxy: conf.proxyURL, 407 NoProxy: conf.noProxyURL, 408 CGI: os.Getenv("REQUEST_METHOD") != "", 409 } 410 conf.customProxyFuncValue = customhttpproxy.ProxyFunc() 411 }) 412 return conf.customProxyFuncValue 413 } 414 415 func checkRedirectFunc(req *http.Request, via []*http.Request) error { 416 return http.ErrUseLastResponse 417 } 418 419 // DummyQueryEscape return the input string. 420 func DummyQueryEscape(s string) string { 421 return s 422 } 423 424 func (conf *config) prepareBaseURL(bucketName string) (requestURL string, canonicalizedURL string) { 425 urlHolder := conf.urlHolder 426 if conf.cname { 427 requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port) 428 if conf.signature == "v4" { 429 canonicalizedURL = "/" 430 } else { 431 canonicalizedURL = "/" + urlHolder.host + "/" 432 } 433 } else { 434 if bucketName == "" { 435 requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port) 436 canonicalizedURL = "/" 437 } else { 438 if conf.pathStyle { 439 requestURL = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName) 440 canonicalizedURL = "/" + bucketName 441 } else { 442 requestURL = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port) 443 if conf.signature == "v2" || conf.signature == "OBS" { 444 canonicalizedURL = "/" + bucketName + "/" 445 } else { 446 canonicalizedURL = "/" 447 } 448 } 449 } 450 } 451 return 452 } 453 454 func (conf *config) prepareObjectKey(escape bool, objectKey string, escapeFunc func(s string) string) (encodeObjectKey string) { 455 if escape { 456 tempKey := []rune(objectKey) 457 result := make([]string, 0, len(tempKey)) 458 for _, value := range tempKey { 459 if string(value) == "/" { 460 result = append(result, string(value)) 461 } else { 462 if string(value) == " " { 463 result = append(result, url.PathEscape(string(value))) 464 } else { 465 result = append(result, url.QueryEscape(string(value))) 466 } 467 } 468 } 469 encodeObjectKey = strings.Join(result, "") 470 } else { 471 encodeObjectKey = escapeFunc(objectKey) 472 } 473 return 474 } 475 476 func (conf *config) prepareEscapeFunc(escape bool) (escapeFunc func(s string) string) { 477 if escape { 478 return url.QueryEscape 479 } 480 return DummyQueryEscape 481 } 482 483 func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestURL string, canonicalizedURL string) { 484 485 requestURL, canonicalizedURL = conf.prepareBaseURL(bucketName) 486 var escapeFunc func(s string) string 487 escapeFunc = conf.prepareEscapeFunc(escape) 488 489 if objectKey != "" { 490 var encodeObjectKey string 491 encodeObjectKey = conf.prepareObjectKey(escape, objectKey, escapeFunc) 492 requestURL += "/" + encodeObjectKey 493 if !strings.HasSuffix(canonicalizedURL, "/") { 494 canonicalizedURL += "/" 495 } 496 canonicalizedURL += encodeObjectKey 497 } 498 499 keys := make([]string, 0, len(params)) 500 for key := range params { 501 keys = append(keys, strings.TrimSpace(key)) 502 } 503 sort.Strings(keys) 504 i := 0 505 506 for index, key := range keys { 507 if index == 0 { 508 requestURL += "?" 509 } else { 510 requestURL += "&" 511 } 512 _key := url.QueryEscape(key) 513 requestURL += _key 514 515 _value := params[key] 516 if conf.signature == "v4" { 517 requestURL += "=" + url.QueryEscape(_value) 518 } else { 519 if _value != "" { 520 requestURL += "=" + url.QueryEscape(_value) 521 _value = "=" + _value 522 } else { 523 _value = "" 524 } 525 lowerKey := strings.ToLower(key) 526 _, ok := allowedResourceParameterNames[lowerKey] 527 prefixHeader := HEADER_PREFIX 528 isObs := conf.signature == SignatureObs 529 if isObs { 530 prefixHeader = HEADER_PREFIX_OBS 531 } 532 ok = ok || strings.HasPrefix(lowerKey, prefixHeader) 533 if ok { 534 if i == 0 { 535 canonicalizedURL += "?" 536 } else { 537 canonicalizedURL += "&" 538 } 539 canonicalizedURL += getQueryURL(_key, _value) 540 i++ 541 } 542 } 543 } 544 return 545 } 546 547 func getQueryURL(key, value string) string { 548 queryURL := "" 549 queryURL += key 550 queryURL += value 551 return queryURL 552 } 553 554 func (obsClient ObsClient) getProgressListener(extensions []extensionOptions) ProgressListener { 555 for _, extension := range extensions { 556 if configure, ok := extension.(extensionProgressListener); ok { 557 return configure() 558 } 559 } 560 return nil 561 }