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 }