github.com/minio/minio-go/v6@v6.0.57/bucket-cache.go (about) 1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-2017 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package minio 19 20 import ( 21 "net" 22 "net/http" 23 "net/url" 24 "path" 25 "sync" 26 27 "github.com/minio/minio-go/v6/pkg/credentials" 28 "github.com/minio/minio-go/v6/pkg/s3utils" 29 "github.com/minio/minio-go/v6/pkg/signer" 30 ) 31 32 // bucketLocationCache - Provides simple mechanism to hold bucket 33 // locations in memory. 34 type bucketLocationCache struct { 35 // mutex is used for handling the concurrent 36 // read/write requests for cache. 37 sync.RWMutex 38 39 // items holds the cached bucket locations. 40 items map[string]string 41 } 42 43 // newBucketLocationCache - Provides a new bucket location cache to be 44 // used internally with the client object. 45 func newBucketLocationCache() *bucketLocationCache { 46 return &bucketLocationCache{ 47 items: make(map[string]string), 48 } 49 } 50 51 // Get - Returns a value of a given key if it exists. 52 func (r *bucketLocationCache) Get(bucketName string) (location string, ok bool) { 53 r.RLock() 54 defer r.RUnlock() 55 location, ok = r.items[bucketName] 56 return 57 } 58 59 // Set - Will persist a value into cache. 60 func (r *bucketLocationCache) Set(bucketName string, location string) { 61 r.Lock() 62 defer r.Unlock() 63 r.items[bucketName] = location 64 } 65 66 // Delete - Deletes a bucket name from cache. 67 func (r *bucketLocationCache) Delete(bucketName string) { 68 r.Lock() 69 defer r.Unlock() 70 delete(r.items, bucketName) 71 } 72 73 // GetBucketLocation - get location for the bucket name from location cache, if not 74 // fetch freshly by making a new request. 75 func (c Client) GetBucketLocation(bucketName string) (string, error) { 76 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 77 return "", err 78 } 79 return c.getBucketLocation(bucketName) 80 } 81 82 // getBucketLocation - Get location for the bucketName from location map cache, if not 83 // fetch freshly by making a new request. 84 func (c Client) getBucketLocation(bucketName string) (string, error) { 85 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 86 return "", err 87 } 88 89 // Region set then no need to fetch bucket location. 90 if c.region != "" { 91 return c.region, nil 92 } 93 94 if location, ok := c.bucketLocCache.Get(bucketName); ok { 95 return location, nil 96 } 97 98 // Initialize a new request. 99 req, err := c.getBucketLocationRequest(bucketName) 100 if err != nil { 101 return "", err 102 } 103 104 // Initiate the request. 105 resp, err := c.do(req) 106 defer closeResponse(resp) 107 if err != nil { 108 return "", err 109 } 110 location, err := processBucketLocationResponse(resp, bucketName) 111 if err != nil { 112 return "", err 113 } 114 c.bucketLocCache.Set(bucketName, location) 115 return location, nil 116 } 117 118 // processes the getBucketLocation http response from the server. 119 func processBucketLocationResponse(resp *http.Response, bucketName string) (bucketLocation string, err error) { 120 if resp != nil { 121 if resp.StatusCode != http.StatusOK { 122 err = httpRespToErrorResponse(resp, bucketName, "") 123 errResp := ToErrorResponse(err) 124 // For access denied error, it could be an anonymous 125 // request. Move forward and let the top level callers 126 // succeed if possible based on their policy. 127 switch errResp.Code { 128 case "NotImplemented": 129 if errResp.Server == "AmazonSnowball" { 130 return "snowball", nil 131 } 132 case "AuthorizationHeaderMalformed": 133 fallthrough 134 case "InvalidRegion": 135 fallthrough 136 case "AccessDenied": 137 if errResp.Region == "" { 138 return "us-east-1", nil 139 } 140 return errResp.Region, nil 141 } 142 return "", err 143 } 144 } 145 146 // Extract location. 147 var locationConstraint string 148 err = xmlDecoder(resp.Body, &locationConstraint) 149 if err != nil { 150 return "", err 151 } 152 153 location := locationConstraint 154 // Location is empty will be 'us-east-1'. 155 if location == "" { 156 location = "us-east-1" 157 } 158 159 // Location can be 'EU' convert it to meaningful 'eu-west-1'. 160 if location == "EU" { 161 location = "eu-west-1" 162 } 163 164 // Save the location into cache. 165 166 // Return. 167 return location, nil 168 } 169 170 // getBucketLocationRequest - Wrapper creates a new getBucketLocation request. 171 func (c Client) getBucketLocationRequest(bucketName string) (*http.Request, error) { 172 // Set location query. 173 urlValues := make(url.Values) 174 urlValues.Set("location", "") 175 176 // Set get bucket location always as path style. 177 targetURL := *c.endpointURL 178 179 // as it works in makeTargetURL method from api.go file 180 if h, p, err := net.SplitHostPort(targetURL.Host); err == nil { 181 if targetURL.Scheme == "http" && p == "80" || targetURL.Scheme == "https" && p == "443" { 182 targetURL.Host = h 183 } 184 } 185 186 isVirtualHost := s3utils.IsVirtualHostSupported(targetURL, bucketName) 187 188 var urlStr string 189 190 //only support Aliyun OSS for virtual hosted path, compatible Amazon & Google Endpoint 191 if isVirtualHost && s3utils.IsAliyunOSSEndpoint(targetURL) { 192 urlStr = c.endpointURL.Scheme + "://" + bucketName + "." + targetURL.Host + "/?location" 193 } else { 194 targetURL.Path = path.Join(bucketName, "") + "/" 195 targetURL.RawQuery = urlValues.Encode() 196 urlStr = targetURL.String() 197 } 198 199 // Get a new HTTP request for the method. 200 req, err := http.NewRequest("GET", urlStr, nil) 201 if err != nil { 202 return nil, err 203 } 204 205 // Set UserAgent for the request. 206 c.setUserAgent(req) 207 208 // Get credentials from the configured credentials provider. 209 value, err := c.credsProvider.Get() 210 if err != nil { 211 return nil, err 212 } 213 214 var ( 215 signerType = value.SignerType 216 accessKeyID = value.AccessKeyID 217 secretAccessKey = value.SecretAccessKey 218 sessionToken = value.SessionToken 219 ) 220 221 // Custom signer set then override the behavior. 222 if c.overrideSignerType != credentials.SignatureDefault { 223 signerType = c.overrideSignerType 224 } 225 226 // If signerType returned by credentials helper is anonymous, 227 // then do not sign regardless of signerType override. 228 if value.SignerType == credentials.SignatureAnonymous { 229 signerType = credentials.SignatureAnonymous 230 } 231 232 if signerType.IsAnonymous() { 233 return req, nil 234 } 235 236 if signerType.IsV2() { 237 // Get Bucket Location calls should be always path style 238 isVirtualHost := false 239 req = signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualHost) 240 return req, nil 241 } 242 243 // Set sha256 sum for signature calculation only with signature version '4'. 244 contentSha256 := emptySHA256Hex 245 if c.secure { 246 contentSha256 = unsignedPayload 247 } 248 249 req.Header.Set("X-Amz-Content-Sha256", contentSha256) 250 req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1") 251 return req, nil 252 }