bucket-cache.go 6.9 KB

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