bucket-cache.go (7032B)
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 "context" 22 "net" 23 "net/http" 24 "net/url" 25 "path" 26 "sync" 27 28 "github.com/minio/minio-go/v7/pkg/credentials" 29 "github.com/minio/minio-go/v7/pkg/s3utils" 30 "github.com/minio/minio-go/v7/pkg/signer" 31 ) 32 33 // bucketLocationCache - Provides simple mechanism to hold bucket 34 // locations in memory. 35 type bucketLocationCache struct { 36 // mutex is used for handling the concurrent 37 // read/write requests for cache. 38 sync.RWMutex 39 40 // items holds the cached bucket locations. 41 items map[string]string 42 } 43 44 // newBucketLocationCache - Provides a new bucket location cache to be 45 // used internally with the client object. 46 func newBucketLocationCache() *bucketLocationCache { 47 return &bucketLocationCache{ 48 items: make(map[string]string), 49 } 50 } 51 52 // Get - Returns a value of a given key if it exists. 53 func (r *bucketLocationCache) Get(bucketName string) (location string, ok bool) { 54 r.RLock() 55 defer r.RUnlock() 56 location, ok = r.items[bucketName] 57 return 58 } 59 60 // Set - Will persist a value into cache. 61 func (r *bucketLocationCache) Set(bucketName string, location string) { 62 r.Lock() 63 defer r.Unlock() 64 r.items[bucketName] = location 65 } 66 67 // Delete - Deletes a bucket name from cache. 68 func (r *bucketLocationCache) Delete(bucketName string) { 69 r.Lock() 70 defer r.Unlock() 71 delete(r.items, bucketName) 72 } 73 74 // GetBucketLocation - get location for the bucket name from location cache, if not 75 // fetch freshly by making a new request. 76 func (c *Client) GetBucketLocation(ctx context.Context, bucketName string) (string, error) { 77 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 78 return "", err 79 } 80 return c.getBucketLocation(ctx, bucketName) 81 } 82 83 // getBucketLocation - Get location for the bucketName from location map cache, if not 84 // fetch freshly by making a new request. 85 func (c *Client) getBucketLocation(ctx context.Context, bucketName string) (string, error) { 86 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 87 return "", err 88 } 89 90 // Region set then no need to fetch bucket location. 91 if c.region != "" { 92 return c.region, nil 93 } 94 95 if location, ok := c.bucketLocCache.Get(bucketName); ok { 96 return location, nil 97 } 98 99 // Initialize a new request. 100 req, err := c.getBucketLocationRequest(ctx, bucketName) 101 if err != nil { 102 return "", err 103 } 104 105 // Initiate the request. 106 resp, err := c.do(req) 107 defer closeResponse(resp) 108 if err != nil { 109 return "", err 110 } 111 location, err := processBucketLocationResponse(resp, bucketName) 112 if err != nil { 113 return "", err 114 } 115 c.bucketLocCache.Set(bucketName, location) 116 return location, nil 117 } 118 119 // processes the getBucketLocation http response from the server. 120 func processBucketLocationResponse(resp *http.Response, bucketName string) (bucketLocation string, err error) { 121 if resp != nil { 122 if resp.StatusCode != http.StatusOK { 123 err = httpRespToErrorResponse(resp, bucketName, "") 124 errResp := ToErrorResponse(err) 125 // For access denied error, it could be an anonymous 126 // request. Move forward and let the top level callers 127 // succeed if possible based on their policy. 128 switch errResp.Code { 129 case "NotImplemented": 130 switch errResp.Server { 131 case "AmazonSnowball": 132 return "snowball", nil 133 case "cloudflare": 134 return "us-east-1", nil 135 } 136 case "AuthorizationHeaderMalformed": 137 fallthrough 138 case "InvalidRegion": 139 fallthrough 140 case "AccessDenied": 141 if errResp.Region == "" { 142 return "us-east-1", nil 143 } 144 return errResp.Region, nil 145 } 146 return "", err 147 } 148 } 149 150 // Extract location. 151 var locationConstraint string 152 err = xmlDecoder(resp.Body, &locationConstraint) 153 if err != nil { 154 return "", err 155 } 156 157 location := locationConstraint 158 // Location is empty will be 'us-east-1'. 159 if location == "" { 160 location = "us-east-1" 161 } 162 163 // Location can be 'EU' convert it to meaningful 'eu-west-1'. 164 if location == "EU" { 165 location = "eu-west-1" 166 } 167 168 // Save the location into cache. 169 170 // Return. 171 return location, nil 172 } 173 174 // getBucketLocationRequest - Wrapper creates a new getBucketLocation request. 175 func (c *Client) getBucketLocationRequest(ctx context.Context, bucketName string) (*http.Request, error) { 176 // Set location query. 177 urlValues := make(url.Values) 178 urlValues.Set("location", "") 179 180 // Set get bucket location always as path style. 181 targetURL := *c.endpointURL 182 183 // as it works in makeTargetURL method from api.go file 184 if h, p, err := net.SplitHostPort(targetURL.Host); err == nil { 185 if targetURL.Scheme == "http" && p == "80" || targetURL.Scheme == "https" && p == "443" { 186 targetURL.Host = h 187 if ip := net.ParseIP(h); ip != nil && ip.To16() != nil { 188 targetURL.Host = "[" + h + "]" 189 } 190 } 191 } 192 193 isVirtualStyle := c.isVirtualHostStyleRequest(targetURL, bucketName) 194 195 var urlStr string 196 197 if isVirtualStyle { 198 urlStr = c.endpointURL.Scheme + "://" + bucketName + "." + targetURL.Host + "/?location" 199 } else { 200 targetURL.Path = path.Join(bucketName, "") + "/" 201 targetURL.RawQuery = urlValues.Encode() 202 urlStr = targetURL.String() 203 } 204 205 // Get a new HTTP request for the method. 206 req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil) 207 if err != nil { 208 return nil, err 209 } 210 211 // Set UserAgent for the request. 212 c.setUserAgent(req) 213 214 // Get credentials from the configured credentials provider. 215 value, err := c.credsProvider.Get() 216 if err != nil { 217 return nil, err 218 } 219 220 var ( 221 signerType = value.SignerType 222 accessKeyID = value.AccessKeyID 223 secretAccessKey = value.SecretAccessKey 224 sessionToken = value.SessionToken 225 ) 226 227 // Custom signer set then override the behavior. 228 if c.overrideSignerType != credentials.SignatureDefault { 229 signerType = c.overrideSignerType 230 } 231 232 // If signerType returned by credentials helper is anonymous, 233 // then do not sign regardless of signerType override. 234 if value.SignerType == credentials.SignatureAnonymous { 235 signerType = credentials.SignatureAnonymous 236 } 237 238 if signerType.IsAnonymous() { 239 return req, nil 240 } 241 242 if signerType.IsV2() { 243 req = signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualStyle) 244 return req, nil 245 } 246 247 // Set sha256 sum for signature calculation only with signature version '4'. 248 contentSha256 := emptySHA256Hex 249 if c.secure { 250 contentSha256 = unsignedPayload 251 } 252 253 req.Header.Set("X-Amz-Content-Sha256", contentSha256) 254 req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1") 255 return req, nil 256 }