update vendor

This commit is contained in:
deepzz0
2017-07-11 23:50:01 +08:00
parent e1ec5cd08a
commit c18d9c0bef
107 changed files with 8347 additions and 126 deletions

67
vendor/github.com/qiniu/api.v7/kodocli/doc.go generated vendored Normal file
View 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
View 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
View 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)
}
// ----------------------------------------------------------

View 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
View 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
}
// ----------------------------------------------------------