github.com/qiniu/go-sdk/v7@v7.27.0/storage/bucket_get.go (about)

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/qiniu/go-sdk/v7/storagev2/downloader"
    15  	"github.com/qiniu/go-sdk/v7/storagev2/region"
    16  )
    17  
    18  type GetObjectInput struct {
    19  	Context         context.Context // 下载所用的 Context
    20  	DownloadDomains []string        // 下载域名列表,如果不填则使用默认源站域名,下载域名可以接受直接填写<HOST>,或是 <protocol>://<HOST> 的格式,如果设置了 protocol 则忽略 UseHttps 的设置;当前仅使用第一个域名
    21  	PresignUrl      bool            // 下载域名是否需要签名,如果使用源站域名则总是签名
    22  	Range           string          // 获取范围,格式同 HTTP 协议的 Range Header
    23  	TrafficLimit    uint64          // 下载单链限速,单位:bit/s;范围:819200 - 838860800(即800Kb/s - 800Mb/s),如果超出该范围将返回 400 错误
    24  }
    25  
    26  type GetObjectOutput struct {
    27  	ContentType   string            // 获取 MIME TYPE
    28  	ContentLength int64             // 获取返回的数据量,如果是 -1 表示未知
    29  	ETag          string            // 获取对象的 Etag
    30  	Metadata      map[string]string // 获取自定义元数据
    31  	LastModified  time.Time         // 获取对象最后一次修改时间
    32  	Body          io.ReadCloser     // 获取对象数据
    33  }
    34  
    35  var _ io.ReadCloser = (*GetObjectOutput)(nil)
    36  
    37  func (g *GetObjectOutput) Read(p []byte) (n int, err error) {
    38  	if g.Body == nil {
    39  		return 0, errors.New("read: body is empty")
    40  	}
    41  	return g.Body.Read(p)
    42  }
    43  
    44  func (g *GetObjectOutput) Close() error {
    45  	if g.Body == nil {
    46  		return errors.New("close: body is empty")
    47  	}
    48  	return g.Body.Close()
    49  }
    50  
    51  type (
    52  	trafficLimitDownloadURLsProvider struct {
    53  		base         downloader.DownloadURLsProvider
    54  		trafficLimit uint64
    55  	}
    56  	trafficLimitURLsIter struct {
    57  		iter         downloader.URLsIter
    58  		trafficLimit uint64
    59  	}
    60  )
    61  
    62  func (p trafficLimitURLsIter) Peek(u *url.URL) (bool, error) {
    63  	if ok, err := p.iter.Peek(u); err != nil {
    64  		return ok, err
    65  	} else if !ok {
    66  		return false, nil
    67  	} else {
    68  		if u.RawQuery != "" {
    69  			u.RawQuery += "&"
    70  		}
    71  		u.RawQuery += fmt.Sprintf("X-Qiniu-Traffic-Limit=%d", p.trafficLimit)
    72  		return true, nil
    73  	}
    74  }
    75  
    76  func (p trafficLimitURLsIter) Next() {
    77  	p.iter.Next()
    78  }
    79  
    80  func (p trafficLimitURLsIter) Reset() {
    81  	p.iter.Reset()
    82  }
    83  
    84  func (p trafficLimitURLsIter) Clone() downloader.URLsIter {
    85  	return trafficLimitURLsIter{p.iter.Clone(), p.trafficLimit}
    86  }
    87  
    88  func (p trafficLimitDownloadURLsProvider) GetURLsIter(ctx context.Context, objectName string, options *downloader.GenerateOptions) (downloader.URLsIter, error) {
    89  	if urlsIter, err := p.base.GetURLsIter(ctx, objectName, options); err != nil {
    90  		return nil, err
    91  	} else {
    92  		return trafficLimitURLsIter{urlsIter, p.trafficLimit}, nil
    93  	}
    94  }
    95  
    96  // Get
    97  //
    98  //	@Description: 下载文件
    99  //	@receiver m BucketManager
   100  //	@param bucket 文件所在 bucket
   101  //	@param key 文件的 key
   102  //	@param options 下载可选配置
   103  //	@return *GetObjectOutput 响应,注:GetObjectOutput 和 error 可能同时存在,有 GetObjectOutput 时请尝试 close
   104  //	@return error 请求错误信息
   105  func (m *BucketManager) Get(bucket, key string, options *GetObjectInput) (*GetObjectOutput, error) {
   106  	if options == nil {
   107  		options = &GetObjectInput{}
   108  	}
   109  	ctx := options.Context
   110  	if ctx == nil {
   111  		ctx = context.Background()
   112  	}
   113  	bucketHosts, err := getUcEndpointProvider(m.Cfg.UseHTTPS, nil).GetEndpoints(ctx)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	var accessKey string
   118  	if m.Mac != nil {
   119  		accessKey = m.Mac.AccessKey
   120  	}
   121  	urlsProvider := downloader.NewDefaultSrcURLsProvider(accessKey, &downloader.DefaultSrcURLsProviderOptions{
   122  		BucketRegionsQueryOptions: region.BucketRegionsQueryOptions{},
   123  		BucketHosts:               bucketHosts,
   124  	})
   125  	urlsProvider = m.applyPresignOnUrlsProvider(m.applyTrafficLimitOnUrlsProvider(urlsProvider, options.TrafficLimit))
   126  	if len(options.DownloadDomains) > 0 {
   127  		staticDomainBasedURLsProvider := downloader.NewStaticDomainBasedURLsProvider(options.DownloadDomains)
   128  		staticDomainBasedURLsProvider = m.applyTrafficLimitOnUrlsProvider(staticDomainBasedURLsProvider, options.TrafficLimit)
   129  		if options.PresignUrl {
   130  			staticDomainBasedURLsProvider = m.applyPresignOnUrlsProvider(staticDomainBasedURLsProvider)
   131  		}
   132  		urlsProvider = downloader.CombineDownloadURLsProviders(staticDomainBasedURLsProvider, urlsProvider)
   133  	}
   134  	reqHeaders := make(http.Header)
   135  	if options.Range != "" {
   136  		reqHeaders.Set("Range", options.Range)
   137  	}
   138  	var (
   139  		getObjectOutput GetObjectOutput
   140  		// headerChan 和 errChan 只用于在当前调用周期内同步首次 header / error 事件,
   141  		// 不需要跨调用复用,也不需要显式关闭;goroutine 退出后,这两个 channel 由 GC 回收。
   142  		headerChan = make(chan struct{}, 1)
   143  		errChan    = make(chan error, 1)
   144  	)
   145  
   146  	objectOptions := downloader.ObjectOptions{
   147  		DestinationDownloadOptions: downloader.DestinationDownloadOptions{
   148  			Header: reqHeaders,
   149  			OnResponseHeader: func(h http.Header) {
   150  				getObjectOutput.ContentType = h.Get("Content-Type")
   151  				getObjectOutput.ETag = parseEtag(h.Get("ETag"))
   152  
   153  				if cl := h.Get("Content-Length"); cl != "" {
   154  					if n, e := strconv.ParseInt(cl, 10, 64); e == nil {
   155  						getObjectOutput.ContentLength = n
   156  					} else {
   157  						getObjectOutput.ContentLength = -1
   158  					}
   159  				} else {
   160  					getObjectOutput.ContentLength = -1 // 分块传输等无 Content-Length 时表示未知
   161  				}
   162  
   163  				lm := h.Get("Last-Modified")
   164  				if len(lm) > 0 {
   165  					if t, e := time.Parse(time.RFC1123, lm); e == nil {
   166  						getObjectOutput.LastModified = t
   167  					}
   168  				}
   169  
   170  				metaData := make(map[string]string)
   171  				for k, v := range h {
   172  					if len(v) > 0 && strings.HasPrefix(strings.ToLower(k), "x-qn-meta-") {
   173  						metaData[k] = v[0]
   174  					}
   175  				}
   176  				getObjectOutput.Metadata = metaData
   177  
   178  				// 填充完成后再发送,保证调用者收到返回时 getObjectOutput 已就绪;非阻塞避免 send on closed channel。
   179  				select {
   180  				case headerChan <- struct{}{}:
   181  				default:
   182  				}
   183  			},
   184  		},
   185  		GenerateOptions: downloader.GenerateOptions{
   186  			BucketName:          bucket,
   187  			UseInsecureProtocol: !m.Cfg.UseHTTPS,
   188  		},
   189  		DownloadURLsProvider: urlsProvider,
   190  	}
   191  
   192  	pipeR, pipeW := io.Pipe()
   193  	getObjectOutput.Body = pipeR
   194  	go func() {
   195  		_, dErr := m.downloadManager.DownloadToWriter(ctx, key, pipeW, &objectOptions)
   196  		// ContentLength 已在 OnResponseHeader 中从响应头设置,此处不再写入,避免与走 header 返回的调用者产生数据竞争。
   197  		// 只尝试发送一次错误结果,不阻塞,不 close channel,避免并发竞态。
   198  		select {
   199  		case errChan <- dErr:
   200  		default:
   201  		}
   202  		_ = pipeW.CloseWithError(dErr)
   203  	}()
   204  
   205  	select {
   206  	case <-headerChan:
   207  		return &getObjectOutput, nil
   208  	case err := <-errChan:
   209  		return &getObjectOutput, err
   210  	case <-ctx.Done():
   211  		return nil, ctx.Err()
   212  	}
   213  }
   214  
   215  func (m *BucketManager) applyTrafficLimitOnUrlsProvider(urlsProvider downloader.DownloadURLsProvider, trafficLimit uint64) downloader.DownloadURLsProvider {
   216  	if trafficLimit > 0 {
   217  		urlsProvider = trafficLimitDownloadURLsProvider{urlsProvider, trafficLimit}
   218  	}
   219  	return urlsProvider
   220  }
   221  
   222  func (m *BucketManager) applyPresignOnUrlsProvider(urlsProvider downloader.DownloadURLsProvider) downloader.DownloadURLsProvider {
   223  	signOptions := downloader.SignOptions{TTL: 3 * time.Minute}
   224  	return downloader.SignURLsProvider(urlsProvider, downloader.NewCredentialsSigner(m.Mac), &signOptions)
   225  }