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  }