mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-23 06:32:28 +08:00
update vendor
This commit is contained in:
67
vendor/github.com/qiniu/api.v7/kodocli/doc.go
generated
vendored
Normal file
67
vendor/github.com/qiniu/api.v7/kodocli/doc.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7/kodocli 提供了在客户端调用七牛云存储部分服务的能力
|
||||
|
||||
客户端,严谨说是非可信环境,主要是指在用户端执行的环境,比如:Android/iOS 设备、Windows/Mac/Linux 桌面环境、也包括浏览器(如果浏览器能够执行 Go 语言代码的话)。
|
||||
|
||||
注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,会影响您的数据安全。
|
||||
|
||||
第一个问题是如何上传文件。因为是在非可信环境,所以我们首先是要授予它有上传文件的能力。答案是给它颁发上传凭证。假设服务端也是 Go 语言,那么:
|
||||
|
||||
import "github.com/qiniu/api.v7/kodo"
|
||||
|
||||
kodo.SetMac("your-access-key", "your-secret-key") // 配置 AccessKey/SecretKey
|
||||
|
||||
zone := kodo.ZoneZ0
|
||||
c := kodo.New(zone, nil) // 创建一个 Client 对象
|
||||
|
||||
bucket := "your-bucket-name"
|
||||
key := "foo/bar.jpg"
|
||||
policy := &kodo.PutPolicy{
|
||||
Scope: bucket + ":" + key, // 上传文件的限制条件,这里限制只能上传一个名为 "foo/bar.jpg" 的文件
|
||||
Expires: 3600, // 这是限制上传凭证(uptoken)的过期时长,3600 是一小时
|
||||
...
|
||||
}
|
||||
uptoken := c.MakeUptoken(policy) // 生成上传凭证
|
||||
|
||||
生成上传凭证之后,通过某种方式将 uptoken 发送到客户端。这样客户端就可以上传文件了:
|
||||
|
||||
zone := 0
|
||||
uploader := kodocli.NewUploader(zone, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
key := "foo/bar.jpg"
|
||||
localFile := "/your/local/image/file.jpg"
|
||||
err := uploader.PutFile(ctx, nil, uptoken, key, localFile, nil)
|
||||
if err != nil {
|
||||
... // 上传文件失败处理
|
||||
return
|
||||
}
|
||||
|
||||
注意,如果客户端上传的 key 不是 uptoken 所要求的 "foo/bar.jpg",那么上传就会被拒绝。
|
||||
|
||||
如果我们希望一个 uptoken 可以上传多个文件,那么服务端颁发 uptoken 的代码需要调整下:
|
||||
|
||||
bucket := "your-bucket-name"
|
||||
policy := &kodo.PutPolicy{
|
||||
Scope: bucket, // 上传文件的限制条件,这里是只限制了要上传到 "your-bucket-name" 空间
|
||||
Expires: 3600, // 这是限制上传凭证(uptoken)的过期时长,3600 是一小时
|
||||
...
|
||||
}
|
||||
uptoken := c.MakeUptoken(policy)
|
||||
|
||||
颁发这样的 uptoken 给客户端,客户端就可以用它上传任意名字(key)的文件,前提是服务器上还没有同名的文件。
|
||||
|
||||
特别需要注意的是,这种情况下服务端会拒绝已经有同名文件的上传请求,而不是覆盖服务器上已有的文件。这是出于数据安全的考虑。我们并不非常推荐 uptoken 复用的做法,除了注意文件名可能冲突外,还需要注意 uptoken 是有时效的,过期就需要重新向服务器申请新的上传凭证。
|
||||
|
||||
搞定文件的上传后,第二个问题是如何下载文件。如果是公开文件,我们只需要:
|
||||
|
||||
import "net/http"
|
||||
|
||||
domain := "domain-of-your-bucket.com" // 您的空间绑定的域名,这个可以在七牛的Portal中查到
|
||||
baseUrl := kodocli.MakeBaseUrl(domain, "foo/bar.jpg") // 得到下载 url
|
||||
resp, err := http.Get(baseUrl)
|
||||
...
|
||||
|
||||
但是对于私有空间,事情要复杂一些,需要客户端向您的业务服务器申请下载该文件。业务服务器确认该用户有权访问,则返回一个临时有效的 privateUrl。具体如何生成 privateUrl,可以看服务端 SDK 相关的文档介绍。
|
||||
*/
|
||||
package kodocli
|
||||
114
vendor/github.com/qiniu/api.v7/kodocli/main.go
generated
vendored
Normal file
114
vendor/github.com/qiniu/api.v7/kodocli/main.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/api.v7/conf"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
"github.com/qiniu/x/url.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type zoneConfig struct {
|
||||
UpHosts []string
|
||||
}
|
||||
|
||||
var zones = []zoneConfig{
|
||||
// z0:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://upload.qiniu.com",
|
||||
"http://up.qiniu.com",
|
||||
"-H up.qiniu.com http://183.136.139.16",
|
||||
},
|
||||
},
|
||||
// z1:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://upload-z1.qiniu.com",
|
||||
"http://up-z1.qiniu.com",
|
||||
"-H up-z1.qiniu.com http://106.38.227.27",
|
||||
},
|
||||
},
|
||||
// z2 华南机房:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://up-z2.qiniu.com",
|
||||
"http://upload-z2.qiniu.com",
|
||||
},
|
||||
},
|
||||
// na0 北美机房:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://up-na0.qiniu.com",
|
||||
"http://upload-na0.qiniu.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type UploadConfig struct {
|
||||
UpHosts []string
|
||||
Transport http.RoundTripper
|
||||
|
||||
APIHost string
|
||||
Scheme string
|
||||
}
|
||||
|
||||
type Uploader struct {
|
||||
Conn rpc.Client
|
||||
UpHosts []string
|
||||
ApiCli *api.Client
|
||||
}
|
||||
|
||||
func NewUploader(zone int, cfg *UploadConfig) (p Uploader) {
|
||||
|
||||
var uc UploadConfig
|
||||
if cfg != nil {
|
||||
uc = *cfg
|
||||
}
|
||||
if uc.Scheme != "https" {
|
||||
uc.Scheme = "http"
|
||||
}
|
||||
if uc.APIHost == "" {
|
||||
uc.APIHost = api.DefaultApiHost
|
||||
}
|
||||
if len(uc.UpHosts) == 0 {
|
||||
if zone > 0 && zone < len(zones) {
|
||||
uc.UpHosts = zones[zone].UpHosts
|
||||
}
|
||||
}
|
||||
|
||||
p.UpHosts = uc.UpHosts
|
||||
p.Conn.Client = &http.Client{Transport: uc.Transport}
|
||||
p.ApiCli = api.NewClient(uc.APIHost, uc.Scheme)
|
||||
return
|
||||
}
|
||||
|
||||
func NewUploaderWithoutZone(cfg *UploadConfig) (p Uploader) {
|
||||
return NewUploader(-1, cfg)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 根据空间(Bucket)的域名,以及文件的 key,获得 baseUrl。
|
||||
// 如果空间是 public 的,那么通过 baseUrl 可以直接下载文件内容。
|
||||
// 如果空间是 private 的,那么需要对 baseUrl 进行私有签名得到一个临时有效的 privateUrl 进行下载。
|
||||
//
|
||||
func MakeBaseUrl(domain, key string) (baseUrl string) {
|
||||
return "http://" + domain + "/" + url.Escape(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 设置使用这个SDK的应用程序名。userApp 必须满足 [A-Za-z0-9_\ \-\.]*
|
||||
//
|
||||
func SetAppName(userApp string) error {
|
||||
|
||||
return conf.SetAppName(userApp)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
328
vendor/github.com/qiniu/api.v7/kodocli/resumable.go
generated
vendored
Normal file
328
vendor/github.com/qiniu/api.v7/kodocli/resumable.go
generated
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/qiniu/x/xlog.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
var (
|
||||
ErrInvalidPutProgress = errors.New("invalid put progress")
|
||||
ErrPutFailed = errors.New("resumable put failed")
|
||||
ErrUnmatchedChecksum = errors.New("unmatched checksum")
|
||||
ErrBadToken = errors.New("invalid token")
|
||||
)
|
||||
|
||||
const (
|
||||
InvalidCtx = 701 // UP: 无效的上下文(bput),可能情况:Ctx非法或者已经被淘汰(太久未使用)
|
||||
)
|
||||
|
||||
const (
|
||||
defaultWorkers = 4
|
||||
defaultChunkSize = 1 * 1024 * 1024 // 1M
|
||||
defaultTryTimes = 3
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
TaskQsize int // 可选。任务队列大小。为 0 表示取 Workers * 4。
|
||||
Workers int // 并行 Goroutine 数目。
|
||||
ChunkSize int // 默认的Chunk大小,不设定则为1M
|
||||
TryTimes int // 默认的尝试次数,不设定则为3
|
||||
}
|
||||
|
||||
var settings = Settings{
|
||||
TaskQsize: defaultWorkers * 4,
|
||||
Workers: defaultWorkers,
|
||||
ChunkSize: defaultChunkSize,
|
||||
TryTimes: defaultTryTimes,
|
||||
}
|
||||
|
||||
func SetSettings(v *Settings) {
|
||||
|
||||
settings = *v
|
||||
if settings.Workers == 0 {
|
||||
settings.Workers = defaultWorkers
|
||||
}
|
||||
if settings.TaskQsize == 0 {
|
||||
settings.TaskQsize = settings.Workers * 4
|
||||
}
|
||||
if settings.ChunkSize == 0 {
|
||||
settings.ChunkSize = defaultChunkSize
|
||||
}
|
||||
if settings.TryTimes == 0 {
|
||||
settings.TryTimes = defaultTryTimes
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
var tasks chan func()
|
||||
|
||||
func worker(tasks chan func()) {
|
||||
for {
|
||||
task := <-tasks
|
||||
task()
|
||||
}
|
||||
}
|
||||
|
||||
func initWorkers() {
|
||||
|
||||
tasks = make(chan func(), settings.TaskQsize)
|
||||
for i := 0; i < settings.Workers; i++ {
|
||||
go worker(tasks)
|
||||
}
|
||||
}
|
||||
|
||||
func notifyNil(blkIdx int, blkSize int, ret *BlkputRet) {}
|
||||
func notifyErrNil(blkIdx int, blkSize int, err error) {}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const (
|
||||
blockBits = 22
|
||||
blockMask = (1 << blockBits) - 1
|
||||
)
|
||||
|
||||
func BlockCount(fsize int64) int {
|
||||
return int((fsize + blockMask) >> blockBits)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type BlkputRet struct {
|
||||
Ctx string `json:"ctx"`
|
||||
Checksum string `json:"checksum"`
|
||||
Crc32 uint32 `json:"crc32"`
|
||||
Offset uint32 `json:"offset"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
type RputExtra struct {
|
||||
Params map[string]string // 可选。用户自定义参数,以"x:"开头 否则忽略
|
||||
MimeType string // 可选。
|
||||
ChunkSize int // 可选。每次上传的Chunk大小
|
||||
TryTimes int // 可选。尝试次数
|
||||
Progresses []BlkputRet // 可选。上传进度
|
||||
Notify func(blkIdx int, blkSize int, ret *BlkputRet) // 可选。进度提示(注意多个block是并行传输的)
|
||||
NotifyErr func(blkIdx int, blkSize int, err error)
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Policy struct {
|
||||
Scope string `json:"scope"`
|
||||
UpHosts []string `json:"uphosts"`
|
||||
}
|
||||
|
||||
func unmarshal(uptoken string, uptokenPolicy *Policy) (err error) {
|
||||
parts := strings.Split(uptoken, ":")
|
||||
if len(parts) != 3 {
|
||||
err = ErrBadToken
|
||||
return
|
||||
}
|
||||
b, err := base64.URLEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(b, uptokenPolicy)
|
||||
}
|
||||
|
||||
func (p Uploader) getUpHostFromToken(uptoken string) (uphosts []string, err error) {
|
||||
if len(p.UpHosts) != 0 {
|
||||
uphosts = p.UpHosts
|
||||
return
|
||||
}
|
||||
ak := strings.Split(uptoken, ":")[0]
|
||||
uptokenPolicy := Policy{}
|
||||
err = unmarshal(uptoken, &uptokenPolicy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(uptokenPolicy.UpHosts) == 0 {
|
||||
bucketName := strings.Split(uptokenPolicy.Scope, ":")[0]
|
||||
bucketInfo, err1 := p.ApiCli.GetBucketInfo(ak, bucketName)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
uphosts = bucketInfo.UpHosts
|
||||
} else {
|
||||
uphosts = uptokenPolicy.UpHosts
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 上传一个文件,支持断点续传和分块上传。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// f 是文件内容的访问接口。考虑到需要支持分块上传和断点续传,要的是 io.ReaderAt 接口,而不是 io.Reader。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) Rput(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, f io.ReaderAt, fsize int64, extra *RputExtra) error {
|
||||
|
||||
return p.rput(ctx, ret, uptoken, key, true, f, fsize, extra)
|
||||
}
|
||||
|
||||
// 上传一个文件,支持断点续传和分块上传。文件的访问路径(key)自动生成。
|
||||
// 如果 uptoken 中设置了 SaveKey,那么按 SaveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// f 是文件内容的访问接口。考虑到需要支持分块上传和断点续传,要的是 io.ReaderAt 接口,而不是 io.Reader。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) RputWithoutKey(
|
||||
ctx Context, ret interface{}, uptoken string, f io.ReaderAt, fsize int64, extra *RputExtra) error {
|
||||
|
||||
return p.rput(ctx, ret, uptoken, "", false, f, fsize, extra)
|
||||
}
|
||||
|
||||
// 上传一个文件,支持断点续传和分块上传。
|
||||
// 和 Rput 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.ReaderAt 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) RputFile(
|
||||
ctx Context, ret interface{}, uptoken, key, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
return p.rputFile(ctx, ret, uptoken, key, true, localFile, extra)
|
||||
}
|
||||
|
||||
// 上传一个文件,支持断点续传和分块上传。文件的访问路径(key)自动生成。
|
||||
// 如果 uptoken 中设置了 SaveKey,那么按 SaveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
// 和 RputWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.ReaderAt 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) RputFileWithoutKey(
|
||||
ctx Context, ret interface{}, uptoken, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
return p.rputFile(ctx, ret, uptoken, "", false, localFile, extra)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) rput(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, f io.ReaderAt, fsize int64, extra *RputExtra) error {
|
||||
|
||||
once.Do(initWorkers)
|
||||
|
||||
log := xlog.NewWith(ctx)
|
||||
blockCnt := BlockCount(fsize)
|
||||
|
||||
if extra == nil {
|
||||
extra = new(RputExtra)
|
||||
}
|
||||
if extra.Progresses == nil {
|
||||
extra.Progresses = make([]BlkputRet, blockCnt)
|
||||
} else if len(extra.Progresses) != blockCnt {
|
||||
return ErrInvalidPutProgress
|
||||
}
|
||||
|
||||
if extra.ChunkSize == 0 {
|
||||
extra.ChunkSize = settings.ChunkSize
|
||||
}
|
||||
if extra.TryTimes == 0 {
|
||||
extra.TryTimes = settings.TryTimes
|
||||
}
|
||||
if extra.Notify == nil {
|
||||
extra.Notify = notifyNil
|
||||
}
|
||||
if extra.NotifyErr == nil {
|
||||
extra.NotifyErr = notifyErrNil
|
||||
}
|
||||
uphosts, err := p.getUpHostFromToken(uptoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(blockCnt)
|
||||
|
||||
last := blockCnt - 1
|
||||
blkSize := 1 << blockBits
|
||||
nfails := 0
|
||||
p.Conn.Client = newUptokenClient(uptoken, p.Conn.Transport)
|
||||
|
||||
for i := 0; i < blockCnt; i++ {
|
||||
blkIdx := i
|
||||
blkSize1 := blkSize
|
||||
if i == last {
|
||||
offbase := int64(blkIdx) << blockBits
|
||||
blkSize1 = int(fsize - offbase)
|
||||
}
|
||||
task := func() {
|
||||
defer wg.Done()
|
||||
tryTimes := extra.TryTimes
|
||||
lzRetry:
|
||||
err := p.resumableBput(ctx, uphosts, &extra.Progresses[blkIdx], f, blkIdx, blkSize1, extra)
|
||||
if err != nil {
|
||||
if tryTimes > 1 {
|
||||
tryTimes--
|
||||
log.Info("resumable.Put retrying ...", blkIdx, "reason:", err)
|
||||
goto lzRetry
|
||||
}
|
||||
log.Warn("resumable.Put", blkIdx, "failed:", err)
|
||||
extra.NotifyErr(blkIdx, blkSize1, err)
|
||||
nfails++
|
||||
}
|
||||
}
|
||||
tasks <- task
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if nfails != 0 {
|
||||
return ErrPutFailed
|
||||
}
|
||||
|
||||
return p.mkfile(ctx, uphosts, ret, key, hasKey, fsize, extra)
|
||||
}
|
||||
|
||||
func (p Uploader) rputFile(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
f, err := os.Open(localFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return p.rput(ctx, ret, uptoken, key, hasKey, f, fi.Size(), extra)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
172
vendor/github.com/qiniu/api.v7/kodocli/resumable_base.go
generated
vendored
Normal file
172
vendor/github.com/qiniu/api.v7/kodocli/resumable_base.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/qiniu/x/bytes.v7"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
"github.com/qiniu/x/xlog.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type uptokenTransport struct {
|
||||
token string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *uptokenTransport) NestedObject() interface{} {
|
||||
return t.Transport
|
||||
}
|
||||
|
||||
func (t *uptokenTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
req.Header.Set("Authorization", t.token)
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func newUptokenTransport(token string, transport http.RoundTripper) *uptokenTransport {
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
return &uptokenTransport{"UpToken " + token, transport}
|
||||
}
|
||||
|
||||
func newUptokenClient(token string, transport http.RoundTripper) *http.Client {
|
||||
t := newUptokenTransport(token, transport)
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) mkblk(
|
||||
ctx Context, uphosts []string, ret *BlkputRet, blockSize int, body io.Reader, size int) error {
|
||||
|
||||
url := uphosts[0] + "/mkblk/" + strconv.Itoa(blockSize)
|
||||
return p.Conn.CallWith(ctx, ret, "POST", url, "application/octet-stream", body, size)
|
||||
}
|
||||
|
||||
func (p Uploader) bput(
|
||||
ctx Context, ret *BlkputRet, body io.Reader, size int) error {
|
||||
|
||||
url := ret.Host + "/bput/" + ret.Ctx + "/" + strconv.FormatUint(uint64(ret.Offset), 10)
|
||||
return p.Conn.CallWith(ctx, ret, "POST", url, "application/octet-stream", body, size)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) resumableBput(
|
||||
ctx Context, uphosts []string, ret *BlkputRet, f io.ReaderAt, blkIdx, blkSize int, extra *RputExtra) (err error) {
|
||||
|
||||
log := xlog.NewWith(ctx)
|
||||
h := crc32.NewIEEE()
|
||||
offbase := int64(blkIdx) << blockBits
|
||||
chunkSize := extra.ChunkSize
|
||||
|
||||
var bodyLength int
|
||||
|
||||
if ret.Ctx == "" {
|
||||
|
||||
if chunkSize < blkSize {
|
||||
bodyLength = chunkSize
|
||||
} else {
|
||||
bodyLength = blkSize
|
||||
}
|
||||
|
||||
body1 := io.NewSectionReader(f, offbase, int64(bodyLength))
|
||||
body := io.TeeReader(body1, h)
|
||||
|
||||
err = p.mkblk(ctx, uphosts, ret, blkSize, body, bodyLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ret.Crc32 != h.Sum32() || int(ret.Offset) != bodyLength {
|
||||
err = ErrUnmatchedChecksum
|
||||
return
|
||||
}
|
||||
extra.Notify(blkIdx, blkSize, ret)
|
||||
}
|
||||
|
||||
for int(ret.Offset) < blkSize {
|
||||
|
||||
if chunkSize < blkSize-int(ret.Offset) {
|
||||
bodyLength = chunkSize
|
||||
} else {
|
||||
bodyLength = blkSize - int(ret.Offset)
|
||||
}
|
||||
|
||||
tryTimes := extra.TryTimes
|
||||
|
||||
lzRetry:
|
||||
h.Reset()
|
||||
body1 := io.NewSectionReader(f, offbase+int64(ret.Offset), int64(bodyLength))
|
||||
body := io.TeeReader(body1, h)
|
||||
|
||||
err = p.bput(ctx, ret, body, bodyLength)
|
||||
if err == nil {
|
||||
if ret.Crc32 == h.Sum32() {
|
||||
extra.Notify(blkIdx, blkSize, ret)
|
||||
continue
|
||||
}
|
||||
log.Warn("ResumableBlockput: invalid checksum, retry")
|
||||
err = ErrUnmatchedChecksum
|
||||
} else {
|
||||
if ei, ok := err.(*rpc.ErrorInfo); ok && ei.Code == InvalidCtx {
|
||||
ret.Ctx = "" // reset
|
||||
log.Warn("ResumableBlockput: invalid ctx, please retry")
|
||||
return
|
||||
}
|
||||
log.Warn("ResumableBlockput: bput failed -", err)
|
||||
}
|
||||
if tryTimes > 1 {
|
||||
tryTimes--
|
||||
log.Info("ResumableBlockput retrying ...")
|
||||
goto lzRetry
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) mkfile(
|
||||
ctx Context, uphosts []string, ret interface{}, key string, hasKey bool, fsize int64, extra *RputExtra) (err error) {
|
||||
|
||||
url := uphosts[0] + "/mkfile/" + strconv.FormatInt(fsize, 10)
|
||||
|
||||
if extra.MimeType != "" {
|
||||
url += "/mimeType/" + encode(extra.MimeType)
|
||||
}
|
||||
if hasKey {
|
||||
url += "/key/" + encode(key)
|
||||
}
|
||||
for k, v := range extra.Params {
|
||||
url += fmt.Sprintf("/%s/%s", k, encode(v))
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 176*len(extra.Progresses))
|
||||
for _, prog := range extra.Progresses {
|
||||
buf = append(buf, prog.Ctx...)
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
if len(buf) > 0 {
|
||||
buf = buf[:len(buf)-1]
|
||||
}
|
||||
|
||||
return p.Conn.CallWith(
|
||||
ctx, ret, "POST", url, "application/octet-stream", bytes.NewReader(buf), len(buf))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func encode(raw string) string {
|
||||
return base64.URLEncoding.EncodeToString([]byte(raw))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
282
vendor/github.com/qiniu/api.v7/kodocli/upload.go
generated
vendored
Normal file
282
vendor/github.com/qiniu/api.v7/kodocli/upload.go
generated
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "context"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const (
|
||||
DontCheckCrc = 0
|
||||
CalcAndCheckCrc = 1
|
||||
CheckCrc = 2
|
||||
)
|
||||
|
||||
// 上传的额外可选项
|
||||
//
|
||||
type PutExtra struct {
|
||||
// 可选,用户自定义参数,必须以 "x:" 开头。若不以x:开头,则忽略。
|
||||
Params map[string]string
|
||||
|
||||
// 可选,当为 "" 时候,服务端自动判断。
|
||||
MimeType string
|
||||
|
||||
Crc32 uint32
|
||||
|
||||
// CheckCrc == 0 (DontCheckCrc): 表示不进行 crc32 校验
|
||||
// CheckCrc == 1 (CalcAndCheckCrc): 对于 Put 等同于 CheckCrc = 2;对于 PutFile 会自动计算 crc32 值
|
||||
// CheckCrc == 2 (CheckCrc): 表示进行 crc32 校验,且 crc32 值就是上面的 Crc32 变量
|
||||
CheckCrc uint32
|
||||
|
||||
// 上传事件:进度通知。这个事件的回调函数应该尽可能快地结束。
|
||||
OnProgress func(fsize, uploaded int64)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 如果 uptoken 没有指定 ReturnBody,那么返回值是标准的 PutRet 结构
|
||||
//
|
||||
type PutRet struct {
|
||||
Hash string `json:"hash"`
|
||||
PersistentId string `json:"persistentId"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 上传一个文件。
|
||||
// 和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) PutFile(
|
||||
ctx Context, ret interface{}, uptoken, key, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
return p.putFile(ctx, ret, uptoken, key, true, localFile, extra)
|
||||
}
|
||||
|
||||
// 上传一个文件。文件的访问路径(key)自动生成。
|
||||
// 如果 uptoken 中设置了 SaveKey,那么按 SaveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
// 和 RputWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) PutFileWithoutKey(
|
||||
ctx Context, ret interface{}, uptoken, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
return p.putFile(ctx, ret, uptoken, "", false, localFile, extra)
|
||||
}
|
||||
|
||||
func (p Uploader) putFile(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
f, err := os.Open(localFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fsize := fi.Size()
|
||||
|
||||
if extra != nil && extra.CheckCrc == 1 {
|
||||
extra.Crc32, err = getFileCrc32(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return p.put(ctx, ret, uptoken, key, hasKey, f, fsize, extra, filepath.Base(localFile))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 上传一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) Put(
|
||||
ctx Context, ret interface{}, uptoken, key string, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
return p.put(ctx, ret, uptoken, key, true, data, size, extra, path.Base(key))
|
||||
}
|
||||
|
||||
// 上传一个文件。文件的访问路径(key)自动生成。
|
||||
// 如果 uptoken 中设置了 SaveKey,那么按 SaveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) PutWithoutKey(
|
||||
ctx Context, ret interface{}, uptoken string, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
return p.put(ctx, ret, uptoken, "", false, data, size, extra, "filename")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
var defaultPutExtra PutExtra
|
||||
|
||||
func (p Uploader) put(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, data io.Reader, size int64, extra *PutExtra, fileName string) error {
|
||||
|
||||
uphosts, err := p.getUpHostFromToken(uptoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var b bytes.Buffer
|
||||
writer := multipart.NewWriter(&b)
|
||||
|
||||
if extra == nil {
|
||||
extra = &defaultPutExtra
|
||||
}
|
||||
|
||||
if extra.OnProgress != nil {
|
||||
data = &readerWithProgress{reader: data, fsize: size, onProgress: extra.OnProgress}
|
||||
}
|
||||
|
||||
err = writeMultipart(writer, uptoken, key, hasKey, extra, fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastLine := fmt.Sprintf("\r\n--%s--\r\n", writer.Boundary())
|
||||
r := strings.NewReader(lastLine)
|
||||
|
||||
bodyLen := int64(-1)
|
||||
if size >= 0 {
|
||||
bodyLen = int64(b.Len()) + size + int64(len(lastLine))
|
||||
}
|
||||
mr := io.MultiReader(&b, data, r)
|
||||
|
||||
contentType := writer.FormDataContentType()
|
||||
err = p.Conn.CallWith64(ctx, ret, "POST", uphosts[0], contentType, mr, bodyLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if extra.OnProgress != nil {
|
||||
extra.OnProgress(size, size)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type readerWithProgress struct {
|
||||
reader io.Reader
|
||||
uploaded int64
|
||||
fsize int64
|
||||
onProgress func(fsize, uploaded int64)
|
||||
}
|
||||
|
||||
func (p *readerWithProgress) Read(b []byte) (n int, err error) {
|
||||
|
||||
if p.uploaded > 0 {
|
||||
p.onProgress(p.fsize, p.uploaded)
|
||||
}
|
||||
|
||||
n, err = p.reader.Read(b)
|
||||
p.uploaded += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func writeMultipart(
|
||||
writer *multipart.Writer, uptoken, key string, hasKey bool, extra *PutExtra, fileName string) (err error) {
|
||||
|
||||
//token
|
||||
if err = writer.WriteField("token", uptoken); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//key
|
||||
if hasKey {
|
||||
if err = writer.WriteField("key", key); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//extra.Params
|
||||
if extra.Params != nil {
|
||||
for k, v := range extra.Params {
|
||||
if strings.HasPrefix(k, "x:") {
|
||||
err = writer.WriteField(k, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//extra.CheckCrc
|
||||
if extra.CheckCrc != 0 {
|
||||
err = writer.WriteField("crc32", strconv.FormatInt(int64(extra.Crc32), 10))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//file
|
||||
head := make(textproto.MIMEHeader)
|
||||
head.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, escapeQuotes(fileName)))
|
||||
if extra.MimeType != "" {
|
||||
head.Set("Content-Type", extra.MimeType)
|
||||
}
|
||||
|
||||
_, err = writer.CreatePart(head)
|
||||
return err
|
||||
}
|
||||
|
||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
|
||||
func escapeQuotes(s string) string {
|
||||
return quoteEscaper.Replace(s)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func getFileCrc32(f *os.File) (uint32, error) {
|
||||
|
||||
h := crc32.NewIEEE()
|
||||
_, err := io.Copy(h, f)
|
||||
f.Seek(0, 0)
|
||||
|
||||
return h.Sum32(), err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
Reference in New Issue
Block a user