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 }