mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-08 15:52:25 +08:00
update vendor
This commit is contained in:
24
vendor/github.com/qiniu/api.v7/.gitignore
generated
vendored
Normal file
24
vendor/github.com/qiniu/api.v7/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
19
vendor/github.com/qiniu/api.v7/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/qiniu/api.v7/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- master
|
||||
env:
|
||||
global:
|
||||
- QINIU_KODO_TEST=1
|
||||
- QINIU_ACCESS_KEY="QWYn5TFQsLLU1pL5MFEmX3s5DmHdUThav9WyOWOm"
|
||||
- QINIU_SECRET_KEY="Bxckh6FA-Fbs9Yt3i3cbKVK22UPBmAOHJcL95pGz"
|
||||
- QINIU_TEST_BUCKET="gosdk"
|
||||
- QINIU_TEST_DOMAIN="gosdk.qiniudn.com"
|
||||
|
||||
install:
|
||||
- export QINIU_SRC=$HOME/gopath/src
|
||||
- mkdir -p $QINIU_SRC/github.com/qiniu
|
||||
- export TRAVIS_BUILD_DIR=$QINIU_SRC/github.com/qiniu/api.v7
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- go get github.com/qiniu/x
|
||||
39
vendor/github.com/qiniu/api.v7/CHANGELOG.md
generated
vendored
Normal file
39
vendor/github.com/qiniu/api.v7/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
#Changelog
|
||||
|
||||
## 7.1.0 (2016-6-22)
|
||||
|
||||
### 增加
|
||||
* 增加多机房相关功能
|
||||
|
||||
## 7.0.5 (2015-11-20)
|
||||
|
||||
### 增加
|
||||
* add delimiter support to Bucket.List
|
||||
* 增加回调校验
|
||||
|
||||
## 7.0.4 (2015-09-03)
|
||||
|
||||
### 增加
|
||||
* 上传返回参数PutRet增加PersistentId,用于获取上传对应的fop操作的id
|
||||
|
||||
### 修复
|
||||
* token 覆盖问题
|
||||
|
||||
## 7.0.3 (2015-07-11)
|
||||
|
||||
### 增加
|
||||
* support NestedObject
|
||||
|
||||
## 7.0.2 (2015-07-7-10)
|
||||
|
||||
### 增加
|
||||
* 增加跨空间移动文件(Bucket.MoveEx)
|
||||
|
||||
## 7.0.1 (2015-07-7-10)
|
||||
|
||||
### 增加
|
||||
* 完善 PutPolicy:支持 MimeLimit、CallbackHost、CallbackFetchKey、 CallbackBodyType、 Checksum
|
||||
|
||||
## 7.0.0 (2016-06-29)
|
||||
|
||||
* 重构,初始版本
|
||||
25
vendor/github.com/qiniu/api.v7/README.md
generated
vendored
Normal file
25
vendor/github.com/qiniu/api.v7/README.md
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
github.com/qiniu/api.v7 (Qiniu Go SDK v7.x)
|
||||
===============
|
||||
|
||||
[](https://travis-ci.org/qiniu/api.v7) [](https://godoc.org/github.com/qiniu/api.v7)
|
||||
|
||||
[](http://qiniu.com/)
|
||||
|
||||
# 下载
|
||||
|
||||
```
|
||||
go get -u github.com/qiniu/api.v7
|
||||
```
|
||||
如果碰到golang.org/x/net/context 不能下载,请把 http://devtools.qiniu.com/golang.org.x.net.context.tgz 下载到代码目录下并解压到src目录,或者直接下载全部 http://devtools.qiniu.com/qiniu_api_v7.tgz。
|
||||
|
||||
# 使用文档
|
||||
|
||||
## KODO Blob Storage (七牛对象存储)
|
||||
|
||||
* [github.com/qiniu/api.v7/kodo](http://godoc.org/github.com/qiniu/api.v7/kodo)
|
||||
* [github.com/qiniu/api.v7/kodocli](http://godoc.org/github.com/qiniu/api.v7/kodocli)
|
||||
|
||||
如果您是在业务服务器(服务器端)调用七牛云存储的服务,请使用 [github.com/qiniu/api.v7/kodo](http://godoc.org/github.com/qiniu/api.v7/kodo)。
|
||||
|
||||
如果您是在客户端(比如:Android/iOS 设备、Windows/Mac/Linux 桌面环境)调用七牛云存储的服务,请使用 [github.com/qiniu/api.v7/kodocli](http://godoc.org/github.com/qiniu/api.v7/kodocli)。注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,会影响您的数据安全。
|
||||
|
||||
99
vendor/github.com/qiniu/api.v7/api/api.go
generated
vendored
Normal file
99
vendor/github.com/qiniu/api.v7/api/api.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
const DefaultApiHost string = "http://uc.qbox.me"
|
||||
|
||||
var (
|
||||
bucketMu sync.RWMutex
|
||||
bucketCache = make(map[string]BucketInfo)
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*rpc.Client
|
||||
host string
|
||||
scheme string
|
||||
}
|
||||
|
||||
func NewClient(host string, scheme string) *Client {
|
||||
if host == "" {
|
||||
host = DefaultApiHost
|
||||
}
|
||||
client := rpc.DefaultClient
|
||||
return &Client{&client, host, scheme}
|
||||
}
|
||||
|
||||
type BucketInfo struct {
|
||||
UpHosts []string `json:"up"`
|
||||
IoHost string `json:"io"`
|
||||
Expire int64 `json:"expire"` // expire == 0 means no expire
|
||||
}
|
||||
|
||||
func (p *Client) GetBucketInfo(ak, bucketName string) (ret BucketInfo, err error) {
|
||||
key := ak + ":" + bucketName + ":" + p.scheme
|
||||
bucketMu.RLock()
|
||||
bucketInfo, ok := bucketCache[key]
|
||||
bucketMu.RUnlock()
|
||||
if ok && (bucketInfo.Expire == 0 || bucketInfo.Expire > time.Now().Unix()) {
|
||||
ret = bucketInfo
|
||||
return
|
||||
}
|
||||
hostInfo, err := p.bucketHosts(ak, bucketName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret.Expire = time.Now().Unix() + hostInfo.Ttl
|
||||
if p.scheme == "https" {
|
||||
ret.UpHosts = hostInfo.Https["up"]
|
||||
if iohosts, ok := hostInfo.Https["io"]; ok && len(iohosts) != 0 {
|
||||
ret.IoHost = iohosts[0]
|
||||
}
|
||||
} else {
|
||||
ret.UpHosts = hostInfo.Http["up"]
|
||||
if iohosts, ok := hostInfo.Http["io"]; ok && len(iohosts) != 0 {
|
||||
ret.IoHost = iohosts[0]
|
||||
}
|
||||
}
|
||||
bucketMu.Lock()
|
||||
bucketCache[key] = ret
|
||||
bucketMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
type HostsInfo struct {
|
||||
Ttl int64 `json:"ttl"`
|
||||
Http map[string][]string `json:"http"`
|
||||
Https map[string][]string `json:"https"`
|
||||
}
|
||||
|
||||
/*
|
||||
请求包:
|
||||
GET /v1/query?ak=<ak>&&bucket=<bucket>
|
||||
返回包:
|
||||
200 OK {
|
||||
"ttl": <ttl>, // 有效时间
|
||||
"http": {
|
||||
"up": [],
|
||||
"io": [], // 当bucket为global时,我们不需要iohost, io缺省
|
||||
},
|
||||
"https": {
|
||||
"up": [],
|
||||
"io": [], // 当bucket为global时,我们不需要iohost, io缺省
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (p *Client) bucketHosts(ak, bucket string) (info HostsInfo, err error) {
|
||||
ctx := Background()
|
||||
err = p.CallWithForm(ctx, &info, "GET", p.host+"/v1/query", map[string][]string{
|
||||
"ak": []string{ak},
|
||||
"bucket": []string{bucket},
|
||||
})
|
||||
return
|
||||
}
|
||||
171
vendor/github.com/qiniu/api.v7/auth/qbox/qbox_auth.go
generated
vendored
Normal file
171
vendor/github.com/qiniu/api.v7/auth/qbox/qbox_auth.go
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
package qbox
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
. "github.com/qiniu/api.v7/conf"
|
||||
"github.com/qiniu/x/bytes.v7/seekable"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Mac struct {
|
||||
AccessKey string
|
||||
SecretKey []byte
|
||||
}
|
||||
|
||||
func NewMac(accessKey, secretKey string) (mac *Mac) {
|
||||
|
||||
if accessKey == "" {
|
||||
accessKey, secretKey = ACCESS_KEY, SECRET_KEY
|
||||
}
|
||||
return &Mac{accessKey, []byte(secretKey)}
|
||||
}
|
||||
|
||||
func (mac *Mac) Sign(data []byte) (token string) {
|
||||
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
h.Write(data)
|
||||
|
||||
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
return mac.AccessKey + ":" + sign[:27]
|
||||
}
|
||||
|
||||
func (mac *Mac) SignWithData(b []byte) (token string) {
|
||||
|
||||
blen := base64.URLEncoding.EncodedLen(len(b))
|
||||
|
||||
key := mac.AccessKey
|
||||
nkey := len(key)
|
||||
ret := make([]byte, nkey+30+blen)
|
||||
|
||||
base64.URLEncoding.Encode(ret[nkey+30:], b)
|
||||
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
h.Write(ret[nkey+30:])
|
||||
digest := h.Sum(nil)
|
||||
|
||||
copy(ret, key)
|
||||
ret[nkey] = ':'
|
||||
base64.URLEncoding.Encode(ret[nkey+1:], digest)
|
||||
ret[nkey+29] = ':'
|
||||
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err error) {
|
||||
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
|
||||
u := req.URL
|
||||
data := u.Path
|
||||
if u.RawQuery != "" {
|
||||
data += "?" + u.RawQuery
|
||||
}
|
||||
io.WriteString(h, data+"\n")
|
||||
|
||||
if incbody {
|
||||
s2, err2 := seekable.New(req)
|
||||
if err2 != nil {
|
||||
return "", err2
|
||||
}
|
||||
h.Write(s2.Bytes())
|
||||
}
|
||||
|
||||
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
token = mac.AccessKey + ":" + sign
|
||||
return
|
||||
}
|
||||
|
||||
func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
|
||||
|
||||
auth := req.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
token, err := mac.SignRequest(req, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return auth == "QBox "+token, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
func Sign(mac *Mac, data []byte) string {
|
||||
|
||||
if mac == nil {
|
||||
mac = NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
}
|
||||
return mac.Sign(data)
|
||||
}
|
||||
|
||||
func SignWithData(mac *Mac, data []byte) string {
|
||||
|
||||
if mac == nil {
|
||||
mac = NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
}
|
||||
return mac.SignWithData(data)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
type Transport struct {
|
||||
mac Mac
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func incBody(req *http.Request) bool {
|
||||
|
||||
if req.Body == nil {
|
||||
return false
|
||||
}
|
||||
if ct, ok := req.Header["Content-Type"]; ok {
|
||||
switch ct[0] {
|
||||
case "application/x-www-form-urlencoded":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Transport) NestedObject() interface{} {
|
||||
|
||||
return t.Transport
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
|
||||
token, err := t.mac.SignRequest(req, incBody(req))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Authorization", "QBox "+token)
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func NewTransport(mac *Mac, transport http.RoundTripper) *Transport {
|
||||
|
||||
if mac == nil {
|
||||
mac = NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
}
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
t := &Transport{mac: *mac, Transport: transport}
|
||||
return t
|
||||
}
|
||||
|
||||
func NewClient(mac *Mac, transport http.RoundTripper) *http.Client {
|
||||
|
||||
t := NewTransport(mac, transport)
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
35
vendor/github.com/qiniu/api.v7/cdn/anti_leech.go
generated
vendored
Normal file
35
vendor/github.com/qiniu/api.v7/cdn/anti_leech.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateTimestampAntileechURL 构建带时间戳防盗链的链接
|
||||
// encryptKey 七牛防盗链key
|
||||
func CreateTimestampAntileechURL(urlStr string, encryptKey string, durationInSeconds int64) (antileechURL string, err error) {
|
||||
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
expireTime := time.Now().Add(time.Second * time.Duration(durationInSeconds)).Unix()
|
||||
toSignStr := fmt.Sprintf("%s%s%x", encryptKey, u.EscapedPath(), expireTime)
|
||||
signedStr := fmt.Sprintf("%x", md5.Sum([]byte(toSignStr)))
|
||||
|
||||
q := url.Values{}
|
||||
q.Add("sign", signedStr)
|
||||
q.Add("t", fmt.Sprintf("%x", expireTime))
|
||||
|
||||
if u.RawQuery == "" {
|
||||
antileechURL = u.String() + "?" + q.Encode()
|
||||
} else {
|
||||
|
||||
antileechURL = u.String() + "&" + q.Encode()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
37
vendor/github.com/qiniu/api.v7/cdn/anti_leech_test.go
generated
vendored
Normal file
37
vendor/github.com/qiniu/api.v7/cdn/anti_leech_test.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateTimestampAntiLeech(t *testing.T) {
|
||||
type args struct {
|
||||
urlStr string
|
||||
encryptKey string
|
||||
durationInSeconds int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "antileech_1",
|
||||
args: args{
|
||||
urlStr: "http://www.abc.com/abc.jpg?stat",
|
||||
encryptKey: "abc",
|
||||
durationInSeconds: 20,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := CreateTimestampAntileechURL(tt.args.urlStr, tt.args.encryptKey, tt.args.durationInSeconds)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CreateTimestampAntiLeech() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
260
vendor/github.com/qiniu/api.v7/cdn/api.go
generated
vendored
Normal file
260
vendor/github.com/qiniu/api.v7/cdn/api.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
. "github.com/qiniu/api.v7/conf"
|
||||
)
|
||||
|
||||
var (
|
||||
FUSION_HOST = "http://fusion.qiniuapi.com"
|
||||
)
|
||||
|
||||
/* TrafficReqBody
|
||||
|
||||
批量查询带宽/流量 请求内容
|
||||
|
||||
StartDate string 开始日期,例如:2016-07-01
|
||||
EndDate string 结束日期,例如:2016-07-03
|
||||
Granularity string 粒度,取值:5min / hour /day
|
||||
Domains string 域名列表,以 ;分割
|
||||
*/
|
||||
type TrafficReqBody struct {
|
||||
StartDate string `json:"startDate"`
|
||||
EndDate string `json:"endDate"`
|
||||
Granularity string `json:"granularity"`
|
||||
Domains string `json:"domains"`
|
||||
}
|
||||
|
||||
// TrafficResp
|
||||
// 带宽/流量查询响应内容
|
||||
type TrafficResp struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Time []string `json:"time,omitempty"`
|
||||
Data map[string]TrafficRespData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// TrafficRespData
|
||||
// 带宽/流量数据
|
||||
type TrafficRespData struct {
|
||||
DomainChina []int `json:"china"`
|
||||
DomainOversea []int `json:"oversea"`
|
||||
}
|
||||
|
||||
/*
|
||||
// BandWidth
|
||||
|
||||
获取域名访问带宽数据
|
||||
http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
|
||||
|
||||
StartDate string 必须 开始日期,例如:2016-07-01
|
||||
EndDate string 必须 结束日期,例如:2016-07-03
|
||||
Granularity string 必须 粒度,取值:5min / hour /day
|
||||
Domains []string 必须 域名列表
|
||||
*/
|
||||
func GetBandWidthData(startDate, endDate, granularity string, domainList []string) (bandwidthData TrafficResp, err error) {
|
||||
|
||||
domains := strings.Join(domainList, ";")
|
||||
reqBody := TrafficReqBody{
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
Granularity: granularity,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/bandwidth", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
umErr := json.Unmarshal(resData, &bandwidthData)
|
||||
if umErr != nil {
|
||||
err = umErr
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* Flux
|
||||
|
||||
获取域名访问流量数据
|
||||
http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
|
||||
|
||||
StartDate string 必须 开始日期,例如:2016-07-01
|
||||
EndDate string 必须 结束日期,例如:2016-07-03
|
||||
Granularity string 必须 粒度,取值:5min / hour /day
|
||||
Domains []string 必须 域名列表
|
||||
*/
|
||||
func GetFluxData(startDate, endDate, granularity string, domainList []string) (fluxData TrafficResp, err error) {
|
||||
|
||||
domains := strings.Join(domainList, ";")
|
||||
reqBody := TrafficReqBody{
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
Granularity: granularity,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/flux", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
umErr := json.Unmarshal(resData, &fluxData)
|
||||
if umErr != nil {
|
||||
err = umErr
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RefreshReq
|
||||
// 缓存刷新请求内容
|
||||
type RefreshReq struct {
|
||||
Urls []string `json:"urls"`
|
||||
Dirs []string `json:"dirs"`
|
||||
}
|
||||
|
||||
// RefreshResp
|
||||
// 缓存刷新响应内容
|
||||
type RefreshResp struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
InvalidUrls []string `json:"invalidUrls,omitempty"`
|
||||
InvalidDirs []string `json:"invalidDirs,omitempty"`
|
||||
UrlQuotaDay int `json:"urlQuotaDay,omitempty"`
|
||||
UrlSurplusDay int `json:"urlSurplusDay,omitempty"`
|
||||
DirQuotaDay int `json:"dirQuotaDay,omitempty"`
|
||||
DirSurplusDay int `json:"dirSurplusDay,omitempty"`
|
||||
}
|
||||
|
||||
/* RefreshUrlsAndDirs
|
||||
|
||||
刷新链接列表,每次最多不可以超过100条链接
|
||||
http://developer.qiniu.com/article/fusion/api/refresh.html
|
||||
|
||||
urls 要刷新的单个url列表,总数不超过100条;单个url,即一个具体的url,例如:http://bar.foo.com/index.html
|
||||
dirs 要刷新的目录url列表,总数不超过10条;目录dir,即表示一个目录级的url,例如:http://bar.foo.com/dir/,也支持在尾部使用通配符,例如:http://bar.foo.com/dir/*
|
||||
*/
|
||||
func RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
|
||||
|
||||
reqBody := RefreshReq{
|
||||
Urls: urls,
|
||||
Dirs: dirs,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/refresh", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
umErr := json.Unmarshal(resData, &result)
|
||||
if umErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RefreshUrls
|
||||
// 刷新文件
|
||||
func RefreshUrls(urls []string) (result RefreshResp, err error) {
|
||||
return RefreshUrlsAndDirs(urls, nil)
|
||||
}
|
||||
|
||||
// RefreshDirs
|
||||
// 刷新目录
|
||||
func RefreshDirs(dirs []string) (result RefreshResp, err error) {
|
||||
return RefreshUrlsAndDirs(nil, dirs)
|
||||
}
|
||||
|
||||
// PrefetchReq
|
||||
// 文件预取请求内容
|
||||
type PrefetchReq struct {
|
||||
Urls []string `json:"urls"`
|
||||
}
|
||||
|
||||
// PrefetchResp
|
||||
// 文件预取响应内容
|
||||
type PrefetchResp struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
InvalidUrls []string `json:"invalidUrls,omitempty"`
|
||||
QuotaDay int `json:"quotaDay,omitempty"`
|
||||
SurplusDay int `json:"surplusDay,omitempty"`
|
||||
}
|
||||
|
||||
// PrefetchUrls
|
||||
// 预取文件链接,每次最多不可以超过100条
|
||||
// http://developer.qiniu.com/article/fusion/api/prefetch.html
|
||||
func PrefetchUrls(urls []string) (result PrefetchResp, err error) {
|
||||
|
||||
reqBody := PrefetchReq{
|
||||
Urls: urls,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/prefetch", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
umErr := json.Unmarshal(resData, &result)
|
||||
if umErr != nil {
|
||||
err = umErr
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RequestWithBody
|
||||
// 带body对api发出请求并且返回response body
|
||||
func postRequest(path string, body interface{}) (resData []byte, err error) {
|
||||
|
||||
urlStr := fmt.Sprintf("%s/%s", FUSION_HOST, path)
|
||||
|
||||
reqData, _ := json.Marshal(body)
|
||||
req, reqErr := http.NewRequest("POST", urlStr, bytes.NewReader(reqData))
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
accessToken, signErr := mac.SignRequest(req, false)
|
||||
if signErr != nil {
|
||||
err = signErr
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "QBox "+accessToken)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, httpErr := http.DefaultClient.Do(req)
|
||||
if httpErr != nil {
|
||||
err = httpErr
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
resData, ioErr := ioutil.ReadAll(resp.Body)
|
||||
if ioErr != nil {
|
||||
err = ioErr
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
243
vendor/github.com/qiniu/api.v7/cdn/api_test.go
generated
vendored
Normal file
243
vendor/github.com/qiniu/api.v7/cdn/api_test.go
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/kodo"
|
||||
)
|
||||
|
||||
var (
|
||||
ak = os.Getenv("QINIU_ACCESS_KEY")
|
||||
sk = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
testBucketName = os.Getenv("QINIU_TEST_BUCKET")
|
||||
|
||||
testDate = time.Now().AddDate(0, 0, -3).Format("2006-01-02")
|
||||
bucket = newBucket()
|
||||
client *kodo.Client
|
||||
testKey = "fusionTest"
|
||||
testURL string
|
||||
)
|
||||
|
||||
func init() {
|
||||
kodo.SetMac(ak, sk)
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
testKey += strconv.Itoa(rand.Int())
|
||||
bucket.PutFile(nil, nil, testKey, "doc.go", nil)
|
||||
testURL = domain + "/" + testKey
|
||||
|
||||
}
|
||||
|
||||
func TestGetBandWidthData(t *testing.T) {
|
||||
type args struct {
|
||||
startDate string
|
||||
endDate string
|
||||
granularity string
|
||||
domainList []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantTraffic TrafficResp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "BandWidthTest_1",
|
||||
args: args{
|
||||
testDate,
|
||||
testDate,
|
||||
"5min",
|
||||
[]string{domain},
|
||||
},
|
||||
},
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := GetBandWidthData(tt.args.startDate, tt.args.endDate, tt.args.granularity, tt.args.domainList)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetBandWidthData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFluxData(t *testing.T) {
|
||||
type args struct {
|
||||
startDate string
|
||||
endDate string
|
||||
granularity string
|
||||
domainList []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantTraffic TrafficResp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "BandWidthTest_1",
|
||||
args: args{
|
||||
testDate,
|
||||
testDate,
|
||||
"5min",
|
||||
[]string{domain},
|
||||
},
|
||||
},
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := GetFluxData(tt.args.startDate, tt.args.endDate, tt.args.granularity, tt.args.domainList)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetFluxData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshUrlsAndDirs(t *testing.T) {
|
||||
kodo.SetMac(ak, sk)
|
||||
|
||||
type args struct {
|
||||
urls []string
|
||||
dirs []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult RefreshResp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "refresh_test_1",
|
||||
args: args{
|
||||
urls: []string{testURL},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := RefreshUrlsAndDirs(tt.args.urls, tt.args.dirs)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RefreshUrlsAndDirs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshUrls(t *testing.T) {
|
||||
type args struct {
|
||||
urls []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult RefreshResp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "refresh_test_1",
|
||||
args: args{
|
||||
urls: []string{testURL},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := RefreshUrls(tt.args.urls)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RefreshUrls() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshDirs(t *testing.T) {
|
||||
type args struct {
|
||||
dirs []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult RefreshResp
|
||||
wantErr bool
|
||||
}{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotResult, err := RefreshDirs(tt.args.dirs)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RefreshDirs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotResult, tt.wantResult) {
|
||||
t.Errorf("RefreshDirs() = %v, want %v", gotResult, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefetchUrls(t *testing.T) {
|
||||
type args struct {
|
||||
urls []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult PrefetchResp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "refresh_test_1",
|
||||
args: args{
|
||||
urls: []string{testURL},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := PrefetchUrls(tt.args.urls)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PrefetchUrls() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newBucket() (bucket kodo.Bucket) {
|
||||
|
||||
ak := os.Getenv("QINIU_ACCESS_KEY")
|
||||
sk := os.Getenv("QINIU_SECRET_KEY")
|
||||
if ak == "" || sk == "" {
|
||||
panic("require ACCESS_KEY & SECRET_KEY")
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
|
||||
testBucketName = os.Getenv("QINIU_TEST_BUCKET")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
if testBucketName == "" || domain == "" {
|
||||
panic("require test env")
|
||||
}
|
||||
client = kodo.NewWithoutZone(nil)
|
||||
|
||||
return client.Bucket(testBucketName)
|
||||
}
|
||||
25
vendor/github.com/qiniu/api.v7/cdn/doc.go
generated
vendored
Normal file
25
vendor/github.com/qiniu/api.v7/cdn/doc.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7/cdn 提供了七牛CDN的API功能
|
||||
首先,我们要配置下 AccessKey/SecretKey,
|
||||
import "github.com/qiniu/api.v7/kodo"
|
||||
|
||||
kodo.SetMac("ak", "sk")
|
||||
设置了AccessKey/SecretKey 就可以使用cdn的各类功能
|
||||
|
||||
比如我们要生成一个带时间戳防盗链的链接:
|
||||
q :=url.Values{}// url.Values 请求参数
|
||||
link, err := cdn.CreateTimestampAntileechURL(""http://www.qiniu.com/abc/bcc/aa-s.mp4?x=2&y=3", "encryptedkey", 20)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(link)
|
||||
|
||||
又或者我们要列出CDN日志及其下载地址:
|
||||
resp, err := cdn.GetCdnLogList("2016-12-26", "x-mas.com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(resp)
|
||||
|
||||
*/
|
||||
package cdn
|
||||
92
vendor/github.com/qiniu/api.v7/cdn/logs.go
generated
vendored
Normal file
92
vendor/github.com/qiniu/api.v7/cdn/logs.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
)
|
||||
|
||||
const (
|
||||
LIST_LOG_API = "http://fusion.qiniuapi.com/v2/tune/log/list"
|
||||
)
|
||||
|
||||
// ListLogRequest 日志下载请求内容
|
||||
type ListLogRequest struct {
|
||||
Day string `json:"day"`
|
||||
Domains string `json:"domains"`
|
||||
}
|
||||
|
||||
// ListLogResult 日志下载相应内容
|
||||
type ListLogResult struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data map[string][]LogDomainInfo `json:"data"`
|
||||
}
|
||||
|
||||
// LogDomainInfo 日志下载信息
|
||||
type LogDomainInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
ModifiedTime int64 `json:"mtime"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// GetCdnLogList 获取CDN域名访问日志的下载链接
|
||||
// http://developer.qiniu.com/article/fusion/api/log.html
|
||||
func GetCdnLogList(date, domains string) (domainLogs []LogDomainInfo, err error) {
|
||||
|
||||
//new log query request
|
||||
logReq := ListLogRequest{
|
||||
Day: date,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
logReqBytes, _ := json.Marshal(&logReq)
|
||||
req, reqErr := http.NewRequest("POST", LIST_LOG_API, bytes.NewReader(logReqBytes))
|
||||
if reqErr != nil {
|
||||
err = fmt.Errorf("New request error, %s", reqErr)
|
||||
return
|
||||
}
|
||||
|
||||
mac := qbox.NewMac("", "")
|
||||
token, signErr := mac.SignRequest(req, false)
|
||||
if signErr != nil {
|
||||
err = signErr
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "QBox "+token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, respErr := http.DefaultClient.Do(req)
|
||||
if respErr != nil {
|
||||
err = fmt.Errorf("Get response error, %s", respErr)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
listLogResult := ListLogResult{}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
if decodeErr := decoder.Decode(&listLogResult); decodeErr != nil {
|
||||
err = fmt.Errorf("Parse response error, %s", decodeErr)
|
||||
return
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("Get log list error, %d %s", listLogResult.Code, listLogResult.Error)
|
||||
return
|
||||
}
|
||||
|
||||
domainItems := strings.Split(domains, ";")
|
||||
|
||||
for _, domain := range domainItems {
|
||||
for _, v := range listLogResult.Data[domain] {
|
||||
domainLogs = append(domainLogs, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
48
vendor/github.com/qiniu/api.v7/cdn/logs_test.go
generated
vendored
Normal file
48
vendor/github.com/qiniu/api.v7/cdn/logs_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/qiniu/api.v7/kodo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kodo.SetMac(ak, sk)
|
||||
}
|
||||
|
||||
func TestGetCdnLogList(t *testing.T) {
|
||||
type args struct {
|
||||
date string
|
||||
domains string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantDomainLogs []LogDomainInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "getCdnLogListTest",
|
||||
args: args{
|
||||
date: testDate,
|
||||
domains: domain,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotDomainLogs, err := GetCdnLogList(tt.args.date, tt.args.domains)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetCdnLogList() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
fmt.Println(domain, gotDomainLogs)
|
||||
if !reflect.DeepEqual(gotDomainLogs, tt.wantDomainLogs) {
|
||||
t.Errorf("GetCdnLogList() = %v, want %v", gotDomainLogs, tt.wantDomainLogs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/qiniu/api.v7/conf/conf.go
generated
vendored
Normal file
38
vendor/github.com/qiniu/api.v7/conf/conf.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/qiniu/x/ctype.v7"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var version = "7.1.0"
|
||||
|
||||
var ACCESS_KEY string
|
||||
var SECRET_KEY string
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const (
|
||||
ctypeAppName = ctype.ALPHA | ctype.DIGIT | ctype.UNDERLINE | ctype.SPACE_BAR | ctype.SUB | ctype.DOT
|
||||
)
|
||||
|
||||
// userApp should be [A-Za-z0-9_\ \-\.]*
|
||||
//
|
||||
func SetAppName(userApp string) error {
|
||||
if userApp != "" && !ctype.IsType(ctypeAppName, userApp) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
rpc.UserAgent = fmt.Sprintf(
|
||||
"QiniuGo/%s (%s; %s; %s) %s", version, runtime.GOOS, runtime.GOARCH, userApp, runtime.Version())
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetAppName("")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
35
vendor/github.com/qiniu/api.v7/conf/conf_test.go
generated
vendored
Normal file
35
vendor/github.com/qiniu/api.v7/conf/conf_test.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
func TestUA(t *testing.T) {
|
||||
err := SetAppName("")
|
||||
if err != nil {
|
||||
t.Fatal("expect no error")
|
||||
}
|
||||
err = SetAppName("错误的UA")
|
||||
if err == nil {
|
||||
t.Fatal("expect an invalid ua format")
|
||||
}
|
||||
err = SetAppName("Test0-_.")
|
||||
if err != nil {
|
||||
t.Fatal("expect no error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
str := "tesT0.-_"
|
||||
SetAppName(str)
|
||||
v := rpc.UserAgent
|
||||
if !strings.Contains(v, str) {
|
||||
t.Fatal("should include user")
|
||||
}
|
||||
if !strings.HasPrefix(v, "QiniuGo/"+version) {
|
||||
t.Fatal("invalid format")
|
||||
}
|
||||
}
|
||||
23
vendor/github.com/qiniu/api.v7/doc.go
generated
vendored
Normal file
23
vendor/github.com/qiniu/api.v7/doc.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7 是七牛 Go 语言 SDK v7.x 版本
|
||||
|
||||
七牛对象存储,我们取了一个好听的名字,叫 KODO Blob Storage。要使用它,你主要和以下两个包打交道:
|
||||
|
||||
import "github.com/qiniu/api.v7/kodo"
|
||||
import "github.com/qiniu/api.v7/kodocli"
|
||||
|
||||
如果您是在业务服务器(服务器端)调用七牛云存储的服务,请使用 github.com/qiniu/api.v7/kodo。
|
||||
|
||||
如果您是在客户端(比如:Android/iOS 设备、Windows/Mac/Linux 桌面环境)调用七牛云存储的服务,请使用 github.com/qiniu/api.v7/kodocli。
|
||||
注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,
|
||||
会影响您的数据安全。
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
_ "github.com/qiniu/api.v7/auth/qbox"
|
||||
_ "github.com/qiniu/api.v7/cdn"
|
||||
_ "github.com/qiniu/api.v7/conf"
|
||||
_ "github.com/qiniu/api.v7/kodo"
|
||||
_ "github.com/qiniu/api.v7/kodocli"
|
||||
)
|
||||
435
vendor/github.com/qiniu/api.v7/kodo/bucket.go
generated
vendored
Normal file
435
vendor/github.com/qiniu/api.v7/kodo/bucket.go
generated
vendored
Normal file
@@ -0,0 +1,435 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/x/log.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Batch 批量操作。
|
||||
//
|
||||
func (p *Client) Batch(ctx Context, ret interface{}, op []string) (err error) {
|
||||
|
||||
return p.CallWithForm(ctx, ret, "POST", p.RSHost+"/batch", map[string][]string{"op": op})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Bucket struct {
|
||||
api.BucketInfo
|
||||
Conn *Client
|
||||
Name string
|
||||
}
|
||||
|
||||
// Buckets 获取所有地区的所有空间(bucket)
|
||||
//
|
||||
// shared 是否获取所有授权获得空间,true为包含授权空间
|
||||
//
|
||||
func (p *Client) Buckets(ctx Context, shared bool) (buckets []string, err error) {
|
||||
if shared {
|
||||
err = p.Call(ctx, &buckets, "POST", p.RSHost+"/buckets?shared=trye")
|
||||
} else {
|
||||
err = p.Call(ctx, &buckets, "POST", p.RSHost+"/buckets")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Bucket 取七牛空间(bucket)的对象实例。
|
||||
//
|
||||
// name 是创建该七牛空间(bucket)时采用的名称。
|
||||
//
|
||||
func (p *Client) Bucket(name string) Bucket {
|
||||
b, err := p.BucketWithSafe(name)
|
||||
if err != nil {
|
||||
log.Errorf("Bucket(%s) failed: %+v", name, err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// BucketWithSafe 确认空间存在并获取七牛空间(bucket)的对象实例。
|
||||
func (p *Client) BucketWithSafe(name string) (Bucket, error) {
|
||||
var info api.BucketInfo
|
||||
if len(p.UpHosts) == 0 {
|
||||
var err error
|
||||
info, err = p.apiCli.GetBucketInfo(p.mac.AccessKey, name)
|
||||
if err != nil {
|
||||
return Bucket{}, err
|
||||
}
|
||||
} else {
|
||||
info.IoHost = p.IoHost
|
||||
info.UpHosts = p.UpHosts
|
||||
}
|
||||
return Bucket{info, p, name}, nil
|
||||
}
|
||||
|
||||
// Entry 资源元信息
|
||||
type Entry struct {
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Type int `json:"type"`
|
||||
EndUser string `json:"endUser"`
|
||||
}
|
||||
|
||||
// Stat 取文件属性。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要访问的文件的访问路径。
|
||||
//
|
||||
func (p Bucket) Stat(ctx Context, key string) (entry Entry, err error) {
|
||||
err = p.Conn.Call(ctx, &entry, "POST", p.Conn.RSHost+URIStat(p.Name, key))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要删除的文件的访问路径。
|
||||
//
|
||||
func (p Bucket) Delete(ctx Context, key string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIDelete(p.Name, key))
|
||||
}
|
||||
|
||||
// Move 移动一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// keySrc 是要移动的文件的旧路径。
|
||||
// keyDest 是要移动的文件的新路径。
|
||||
//
|
||||
func (p Bucket) Move(ctx Context, keySrc, keyDest string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIMove(p.Name, keySrc, p.Name, keyDest))
|
||||
}
|
||||
|
||||
// MoveEx 跨空间(bucket)移动一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// keySrc 是要移动的文件的旧路径。
|
||||
// bucketDest 是文件的目标空间。
|
||||
// keyDest 是要移动的文件的新路径。
|
||||
//
|
||||
func (p Bucket) MoveEx(ctx Context, keySrc, bucketDest, keyDest string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIMove(p.Name, keySrc, bucketDest, keyDest))
|
||||
}
|
||||
|
||||
// Copy 复制一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// keySrc 是要复制的文件的源路径。
|
||||
// keyDest 是要复制的文件的目标路径。
|
||||
//
|
||||
func (p Bucket) Copy(ctx Context, keySrc, keyDest string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URICopy(p.Name, keySrc, p.Name, keyDest))
|
||||
}
|
||||
|
||||
// ChangeMime 修改文件的MIME类型。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要修改的文件的访问路径。
|
||||
// mime 是要设置的新MIME类型。
|
||||
//
|
||||
func (p Bucket) ChangeMime(ctx Context, key, mime string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIChangeMime(p.Name, key, mime))
|
||||
}
|
||||
|
||||
// ChangeType 修改文件的存储类型。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要修改的文件的访问路径。
|
||||
// fileType 是要设置的新存储类型。0 表示标准存储;1 表示低频存储。
|
||||
//
|
||||
func (p Bucket) ChangeType(ctx Context, key string, fileType int) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIChangeType(p.Name, key, fileType))
|
||||
}
|
||||
|
||||
// Fetch 从网上抓取一个资源并存储到七牛空间(bucket)中。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要存储的文件的访问路径。如果文件已经存在则覆盖。
|
||||
// url 是要抓取的资源的URL。
|
||||
//
|
||||
func (p Bucket) Fetch(ctx Context, key string, url string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.IoHost+uriFetch(p.Name, key, url))
|
||||
}
|
||||
|
||||
// DeleteAfterDays 更新文件生命周期
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要更新的文件的访问路径。
|
||||
// deleteAfterDays 设置为0表示取消 lifecycle
|
||||
//
|
||||
func (p Bucket) DeleteAfterDays(ctx Context, key string, days int) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIDeleteAfterDays(p.Name, key, days))
|
||||
}
|
||||
|
||||
// Image 设置镜像源
|
||||
//
|
||||
// srcSiteURL 镜像源的访问域名。必须设置为形如 `http://source.com/` 或 `http://114.114.114.114/` 的字符串
|
||||
// host 回源时使用的 Host 头部值
|
||||
//
|
||||
// 镜像源地址支持两种格式:
|
||||
// 格式 1:`http(s)://绑定域名/源站资源相对路径`
|
||||
// 格式 2:`http(s)://绑定 IP/源站资源相对路径`
|
||||
//
|
||||
func (p Bucket) Image(ctx Context, srcSiteURL, host string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", "http://pu.qbox.me:10200"+URIImage(p.Name, srcSiteURL, host))
|
||||
}
|
||||
|
||||
// UnImage 取消镜像源
|
||||
//
|
||||
func (p Bucket) UnImage(ctx Context) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", "http://pu.qbox.me:10200"+URIUnImage(p.Name))
|
||||
}
|
||||
|
||||
// Prefetch 镜像资源更新
|
||||
//
|
||||
// key 被抓取资源名称
|
||||
//
|
||||
func (p Bucket) Prefetch(ctx Context, key string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.IoHost+URIPrefetch(p.Name, key))
|
||||
}
|
||||
|
||||
// PfopResult pfop返回信息
|
||||
type PfopResult struct {
|
||||
PersistentID string `json:"persistentId,omitempty"`
|
||||
}
|
||||
|
||||
// FopRet 持久化云处理结果
|
||||
type FopRet struct {
|
||||
ID string `json:"id"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
InputBucket string `json:"inputBucket,omitempty"`
|
||||
InputKey string `json:"inputKey,omitempty"`
|
||||
Pipeline string `json:"pipeline,omitempty"`
|
||||
Reqid string `json:"reqid,omitempty"`
|
||||
Items []FopResult
|
||||
}
|
||||
|
||||
// FopResult 云处理操作列表,包含每个云处理操作的状态信息
|
||||
type FopResult struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Keys []string `json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
// Pfop 持久化数据处理
|
||||
//
|
||||
// bucket 资源空间
|
||||
// key 源资源名
|
||||
// fops 云处理操作列表,用`;``分隔,如:`avthumb/flv;saveas/cWJ1Y2tldDpxa2V5`,是将上传的视频文件转码成flv格式后存储为 qbucket:qkey ,其中 cWJ1Y2tldDpxa2V5 是 qbucket:qkey 的URL安全的Base64编码结果。
|
||||
// notifyURL 处理结果通知接收 URL,七牛将会向你设置的 URL 发起 Content-Type: application/json 的 POST 请求。
|
||||
// pipeline 为空则表示使用公用队列,处理速度比较慢。建议指定私有队列,转码的时候使用独立的计算资源。
|
||||
// force 强制执行数据处理。当服务端发现 fops 指定的数据处理结果已经存在,那就认为已经处理成功,避免重复处理浪费资源。本字段设为 `true`,则可强制执行数据处理并覆盖原结果。
|
||||
//
|
||||
func (p *Client) Pfop(ctx Context, bucket, key, fops, notifyURL, pipeline string, force bool) (persistentID string, err error) {
|
||||
pfopParams := map[string][]string{
|
||||
"bucket": []string{bucket},
|
||||
"key": []string{key},
|
||||
"fops": []string{fops},
|
||||
}
|
||||
if notifyURL != "" {
|
||||
pfopParams["notifyURL"] = []string{notifyURL}
|
||||
}
|
||||
if pipeline != "" {
|
||||
pfopParams["pipeline"] = []string{pipeline}
|
||||
}
|
||||
if force {
|
||||
pfopParams["force"] = []string{"1"}
|
||||
}
|
||||
var ret PfopResult
|
||||
err = p.CallWithForm(ctx, &ret, "POST", "http://api.qiniu.com/pfop/", pfopParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
persistentID = ret.PersistentID
|
||||
return
|
||||
}
|
||||
|
||||
// Prefop 持久化处理状态查询
|
||||
func (p *Client) Prefop(ctx Context, persistentID string) (ret FopRet, err error) {
|
||||
err = p.Call(ctx, &ret, "GET", "http://api.qiniu.com/status/get/prefop?id="+persistentID)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// ListItem List借口返回结果
|
||||
type ListItem struct {
|
||||
Key string `json:"key"`
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
EndUser string `json:"endUser"`
|
||||
}
|
||||
|
||||
// List 首次请求,请将 marker 设置为 ""。
|
||||
// 无论 err 值如何,均应该先看 entries 是否有内容。
|
||||
// 如果后续没有更多数据,err 返回 EOF,markerOut 返回 ""(但不通过该特征来判断是否结束)。
|
||||
//
|
||||
func (p Bucket) List(
|
||||
ctx Context, prefix, delimiter, marker string, limit int) (entries []ListItem, commonPrefixes []string, markerOut string, err error) {
|
||||
|
||||
listUrl := p.makeListURL(prefix, delimiter, marker, limit)
|
||||
|
||||
var listRet struct {
|
||||
Marker string `json:"marker"`
|
||||
Items []ListItem `json:"items"`
|
||||
Prefixes []string `json:"commonPrefixes"`
|
||||
}
|
||||
err = p.Conn.Call(ctx, &listRet, "POST", listUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if listRet.Marker == "" {
|
||||
return listRet.Items, listRet.Prefixes, "", io.EOF
|
||||
}
|
||||
return listRet.Items, listRet.Prefixes, listRet.Marker, nil
|
||||
}
|
||||
|
||||
func (p Bucket) makeListURL(prefix, delimiter, marker string, limit int) string {
|
||||
|
||||
query := make(url.Values)
|
||||
query.Add("bucket", p.Name)
|
||||
if prefix != "" {
|
||||
query.Add("prefix", prefix)
|
||||
}
|
||||
if delimiter != "" {
|
||||
query.Add("delimiter", delimiter)
|
||||
}
|
||||
if marker != "" {
|
||||
query.Add("marker", marker)
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Add("limit", strconv.FormatInt(int64(limit), 10))
|
||||
}
|
||||
return p.Conn.RSFHost + "/list?" + query.Encode()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type BatchStatItemRet struct {
|
||||
Data Entry `json:"data"`
|
||||
Error string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
// BatchStat 批量取文件属性
|
||||
func (p Bucket) BatchStat(ctx Context, keys ...string) (ret []BatchStatItemRet, err error) {
|
||||
|
||||
b := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
b[i] = URIStat(p.Name, key)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
type BatchItemRet struct {
|
||||
Error string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除
|
||||
func (p Bucket) BatchDelete(ctx Context, keys ...string) (ret []BatchItemRet, err error) {
|
||||
|
||||
b := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
b[i] = URIDelete(p.Name, key)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
Src string
|
||||
Dest string
|
||||
}
|
||||
|
||||
// BatchMove 批量移动文件
|
||||
func (p Bucket) BatchMove(ctx Context, entries ...KeyPair) (ret []BatchItemRet, err error) {
|
||||
|
||||
b := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
b[i] = URIMove(p.Name, e.Src, p.Name, e.Dest)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
// BatchCopy 批量复制文件
|
||||
func (p Bucket) BatchCopy(ctx Context, entries ...KeyPair) (ret []BatchItemRet, err error) {
|
||||
|
||||
b := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
b[i] = URICopy(p.Name, e.Src, p.Name, e.Dest)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func encodeURI(uri string) string {
|
||||
return base64.URLEncoding.EncodeToString([]byte(uri))
|
||||
}
|
||||
|
||||
func uriFetch(bucket, key, url string) string {
|
||||
return "/fetch/" + encodeURI(url) + "/to/" + encodeURI(bucket+":"+key)
|
||||
}
|
||||
|
||||
func URIDelete(bucket, key string) string {
|
||||
return "/delete/" + encodeURI(bucket+":"+key)
|
||||
}
|
||||
|
||||
func URIStat(bucket, key string) string {
|
||||
return "/stat/" + encodeURI(bucket+":"+key)
|
||||
}
|
||||
|
||||
func URICopy(bucketSrc, keySrc, bucketDest, keyDest string) string {
|
||||
return "/copy/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
|
||||
}
|
||||
|
||||
func URIMove(bucketSrc, keySrc, bucketDest, keyDest string) string {
|
||||
return "/move/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
|
||||
}
|
||||
|
||||
func URIChangeMime(bucket, key, mime string) string {
|
||||
return "/chgm/" + encodeURI(bucket+":"+key) + "/mime/" + encodeURI(mime)
|
||||
}
|
||||
|
||||
func URIChangeType(bucket, key string, fileType int) string {
|
||||
return "/chtype/" + encodeURI(bucket+":"+key) + "/type/" + strconv.Itoa(fileType)
|
||||
}
|
||||
|
||||
func URIDeleteAfterDays(bucket, key string, days int) string {
|
||||
return fmt.Sprintf("/deleteAfterDays/%s/%d", encodeURI(bucket+":"+key), days)
|
||||
}
|
||||
|
||||
func URIImage(bucket, srcSiteURL, host string) string {
|
||||
return fmt.Sprintf("/image/%s/from/%s/host/%s", bucket, encodeURI(srcSiteURL), encodeURI(host))
|
||||
}
|
||||
|
||||
func URIUnImage(bucket string) string {
|
||||
return fmt.Sprintf("/unimage/%s", bucket)
|
||||
}
|
||||
|
||||
func URIPrefetch(bucket, key string) string {
|
||||
return fmt.Sprintf("/prefetch/%s", encodeURI(bucket+":"+key))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
167
vendor/github.com/qiniu/api.v7/kodo/bucket_test.go
generated
vendored
Normal file
167
vendor/github.com/qiniu/api.v7/kodo/bucket_test.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
batchTestKey = "abatch"
|
||||
batchTestNewKey1 = "abatch/newkey1"
|
||||
batchTestNewKey2 = "abatch/newkey2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
batchTestKey += strconv.Itoa(rand.Int())
|
||||
batchTestNewKey1 += strconv.Itoa(rand.Int())
|
||||
batchTestNewKey2 += strconv.Itoa(rand.Int())
|
||||
// 删除 可能存在的 key
|
||||
bucket.BatchDelete(nil, batchTestKey, batchTestNewKey1, batchTestNewKey2)
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
//上传一个文件用用于测试
|
||||
err := upFile("bucket_test.go", batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, batchTestKey)
|
||||
|
||||
testBatchStat(t)
|
||||
testBatchCopy(t)
|
||||
testBatchMove(t)
|
||||
testBatchDelete(t)
|
||||
testBatch(t)
|
||||
testClient_MakeUptokenBucket(t)
|
||||
testDeleteAfterDays(t)
|
||||
}
|
||||
|
||||
func testBatchStat(t *testing.T) {
|
||||
|
||||
rets, err := bucket.BatchStat(nil, batchTestKey, batchTestKey, batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal("bucket.BatchStat failed:", err)
|
||||
}
|
||||
|
||||
if len(rets) != 3 {
|
||||
t.Fatal("BatchStat failed: len(rets) = ", 3)
|
||||
}
|
||||
|
||||
stat, err := bucket.Stat(nil, batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal("bucket.Stat failed:", err)
|
||||
}
|
||||
|
||||
if rets[0].Data != stat || rets[1].Data != stat || rets[2].Data != stat {
|
||||
t.Fatal("BatchStat failed : returns err")
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchMove(t *testing.T) {
|
||||
|
||||
stat0, err := bucket.Stat(nil, batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal("BathMove get stat failed:", err)
|
||||
}
|
||||
|
||||
_, err = bucket.BatchMove(nil, KeyPair{batchTestKey, batchTestNewKey1}, KeyPair{batchTestNewKey1, batchTestNewKey2})
|
||||
if err != nil {
|
||||
t.Fatal("bucket.BatchMove failed:", err)
|
||||
}
|
||||
defer bucket.Move(nil, batchTestNewKey2, batchTestKey)
|
||||
|
||||
stat1, err := bucket.Stat(nil, batchTestNewKey2)
|
||||
if err != nil {
|
||||
t.Fatal("BathMove get stat failed:", err)
|
||||
}
|
||||
|
||||
if stat0.Hash != stat1.Hash {
|
||||
t.Fatal("BatchMove failed : Move err", stat0, stat1)
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchCopy(t *testing.T) {
|
||||
|
||||
_, err := bucket.BatchCopy(nil, KeyPair{batchTestKey, batchTestNewKey1}, KeyPair{batchTestKey, batchTestNewKey2})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, batchTestNewKey1)
|
||||
defer bucket.Delete(nil, batchTestNewKey2)
|
||||
|
||||
stat0, _ := bucket.Stat(nil, batchTestKey)
|
||||
stat1, _ := bucket.Stat(nil, batchTestNewKey1)
|
||||
stat2, _ := bucket.Stat(nil, batchTestNewKey2)
|
||||
if stat0.Hash != stat1.Hash || stat0.Hash != stat2.Hash {
|
||||
t.Fatal("BatchCopy failed : Copy err")
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchDelete(t *testing.T) {
|
||||
|
||||
bucket.Copy(nil, batchTestKey, batchTestNewKey1)
|
||||
bucket.Copy(nil, batchTestKey, batchTestNewKey2)
|
||||
|
||||
_, err := bucket.BatchDelete(nil, batchTestNewKey1, batchTestNewKey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err1 := bucket.Stat(nil, batchTestNewKey1)
|
||||
_, err2 := bucket.Stat(nil, batchTestNewKey2)
|
||||
|
||||
//这里 err1 != nil,否则文件没被成功删除
|
||||
if err1 == nil || err2 == nil {
|
||||
t.Fatal("BatchDelete failed : File do not delete")
|
||||
}
|
||||
}
|
||||
|
||||
func testBatch(t *testing.T) {
|
||||
|
||||
ops := []string{
|
||||
URICopy(bucketName, batchTestKey, bucketName, batchTestNewKey1),
|
||||
URIDelete(bucketName, batchTestKey),
|
||||
URIMove(bucketName, batchTestNewKey1, bucketName, batchTestKey),
|
||||
}
|
||||
|
||||
var rets []BatchItemRet
|
||||
err := client.Batch(nil, &rets, ops)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rets) != 3 {
|
||||
t.Fatal("len(rets) != 3")
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteAfterDays(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err := bucket.DeleteAfterDays(ctx, batchTestNewKey1, 5)
|
||||
if err == nil {
|
||||
t.Fatal("Expect an error")
|
||||
}
|
||||
|
||||
bucket.Copy(ctx, batchTestKey, batchTestNewKey1)
|
||||
|
||||
err = bucket.DeleteAfterDays(ctx, batchTestNewKey1, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
52
vendor/github.com/qiniu/api.v7/kodo/doc.go
generated
vendored
Normal file
52
vendor/github.com/qiniu/api.v7/kodo/doc.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7/kodo 提供了在您的业务服务器(服务端)调用七牛云存储服务的能力
|
||||
|
||||
首先,我们要配置下 AccessKey/SecretKey,这可以在七牛 Portal 中查到:
|
||||
|
||||
kodo.SetMac("your-access-key", "your-secret-key")
|
||||
|
||||
然后我们创建一个 Client 对象:
|
||||
|
||||
zone := kodo.ZoneZ0 // 您空间(Bucket)所在的区域
|
||||
c := kodo.New(zone, nil) // 用默认配置创建 Client
|
||||
|
||||
有了 Client,你就可以操作您的空间(Bucket)了,比如我们要上传一个文件:
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
bucket := c.Bucket("your-bucket-name")
|
||||
ctx := context.Background()
|
||||
...
|
||||
localFile := "/your/local/image/file.jpg"
|
||||
err := bucket.PutFile(ctx, nil, "foo/bar.jpg", localFile, nil)
|
||||
if err != nil {
|
||||
... // 上传文件失败处理
|
||||
return
|
||||
}
|
||||
// 上传文件成功
|
||||
// 这时登录七牛Portal,在 your-bucket-name 空间就可以看到一个 foo/bar.jpg 的文件了
|
||||
|
||||
当然,除了上传文件,各种空间(Bucket)相关的操作都可以有,最常见自然是增删改查了:
|
||||
|
||||
entry, err := bucket.Stat(ctx, "foo/bar.jpg") // 看看空间中是否存在某个文件,其属性是什么
|
||||
bucket.Delete(ctx, "foo/bar.jpg") // 删除空间中的某个文件
|
||||
bucket.ChangeMime(ctx, "foo/bar.jpg", "image/jpeg") // 修改某个文件的 MIME 属性
|
||||
bucket.Move(ctx, "foo/bar.jpg", "new-name.jpg") // 移动文件
|
||||
bucket.Copy(ctx, "foo/bar.jpg", "new-copy-file.jpg") // 复制文件
|
||||
|
||||
等等... 请问怎么下载文件?如果是公开文件,我们只需要:
|
||||
|
||||
import "net/http"
|
||||
|
||||
domain := "domain-of-your-bucket.com" // 您的空间绑定的域名,这个可以在七牛的Portal中查到
|
||||
baseUrl := kodo.MakeBaseUrl(domain, "foo/bar.jpg") // 得到下载 url
|
||||
resp, err := http.Get(baseUrl)
|
||||
...
|
||||
|
||||
但是对于私有空间,事情要复杂一些,访问上面的 baseUrl 会被拒绝。我们需要多做一步:
|
||||
|
||||
privateUrl := c.MakePrivateUrl(baseUrl, nil) // 用默认的下载策略去生成私有下载的 url
|
||||
resp, err := http.Get(privateUrl)
|
||||
...
|
||||
*/
|
||||
package kodo
|
||||
154
vendor/github.com/qiniu/api.v7/kodo/main.go
generated
vendored
Normal file
154
vendor/github.com/qiniu/api.v7/kodo/main.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/conf"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type zoneConfig struct {
|
||||
IoHost string
|
||||
UpHosts []string
|
||||
}
|
||||
|
||||
const (
|
||||
// ZoneZ0 华东机房
|
||||
ZoneZ0 = iota
|
||||
// ZoneZ1 华北机房
|
||||
ZoneZ1
|
||||
// ZoneZ2 华南机房
|
||||
ZoneZ2
|
||||
// ZoneNa0 北美机房
|
||||
ZoneNa0
|
||||
)
|
||||
|
||||
var zones = []zoneConfig{
|
||||
// z0 华东机房:
|
||||
{
|
||||
IoHost: "http://iovip.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up.qiniu.com",
|
||||
"http://upload.qiniu.com",
|
||||
"-H up.qiniu.com http://183.136.139.16",
|
||||
},
|
||||
},
|
||||
// z1 华北机房:
|
||||
{
|
||||
IoHost: "http://iovip-z1.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up-z1.qiniu.com",
|
||||
"http://upload-z1.qiniu.com",
|
||||
"-H up-z1.qiniu.com http://106.38.227.27",
|
||||
},
|
||||
},
|
||||
// z2 华南机房:
|
||||
{
|
||||
IoHost: "http://iovip-z2.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up-z2.qiniu.com",
|
||||
"http://upload-z2.qiniu.com",
|
||||
},
|
||||
},
|
||||
// na0 北美机房:
|
||||
{
|
||||
IoHost: "http://iovip-na0.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up-na0.qiniu.com",
|
||||
"http://upload-na0.qiniu.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
defaultRsHost = "http://rs.qbox.me"
|
||||
defaultRsfHost = "http://rsf.qbox.me"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
RSHost string
|
||||
RSFHost string
|
||||
APIHost string
|
||||
Scheme string
|
||||
IoHost string
|
||||
UpHosts []string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Client struct {
|
||||
rpc.Client
|
||||
mac *qbox.Mac
|
||||
Config
|
||||
|
||||
apiCli *api.Client
|
||||
}
|
||||
|
||||
func New(zone int, cfg *Config) (p *Client) {
|
||||
|
||||
p = new(Client)
|
||||
if cfg != nil {
|
||||
p.Config = *cfg
|
||||
}
|
||||
|
||||
p.mac = qbox.NewMac(p.AccessKey, p.SecretKey)
|
||||
p.Client = rpc.Client{qbox.NewClient(p.mac, p.Transport)}
|
||||
|
||||
if p.RSHost == "" {
|
||||
p.RSHost = defaultRsHost
|
||||
}
|
||||
if p.RSFHost == "" {
|
||||
p.RSFHost = defaultRsfHost
|
||||
}
|
||||
if p.Scheme != "https" {
|
||||
p.Scheme = "http"
|
||||
}
|
||||
if p.APIHost == "" {
|
||||
p.APIHost = api.DefaultApiHost
|
||||
}
|
||||
p.apiCli = api.NewClient(p.APIHost, p.Scheme)
|
||||
|
||||
if zone < 0 || zone >= len(zones) {
|
||||
return
|
||||
}
|
||||
if len(p.UpHosts) == 0 {
|
||||
p.UpHosts = zones[zone].UpHosts
|
||||
}
|
||||
if p.IoHost == "" {
|
||||
p.IoHost = zones[zone].IoHost
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewWithoutZone(cfg *Config) (p *Client) {
|
||||
return New(-1, cfg)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 设置全局默认的 ACCESS_KEY, SECRET_KEY 变量。
|
||||
//
|
||||
func SetMac(accessKey, secretKey string) {
|
||||
|
||||
conf.ACCESS_KEY, conf.SECRET_KEY = accessKey, secretKey
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 设置使用这个SDK的应用程序名。userApp 必须满足 [A-Za-z0-9_\ \-\.]*
|
||||
//
|
||||
func SetAppName(userApp string) error {
|
||||
|
||||
return conf.SetAppName(userApp)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
151
vendor/github.com/qiniu/api.v7/kodo/main_test.go
generated
vendored
Normal file
151
vendor/github.com/qiniu/api.v7/kodo/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
key = "aa"
|
||||
keyFetch = "afetch"
|
||||
newkey1 = "bbbb"
|
||||
newkey2 = "cccc"
|
||||
fetchURL = "http://www-static.u.qiniucdn.com/public/v1645/img/css-sprite.png"
|
||||
bucketName string
|
||||
domain string
|
||||
client *Client
|
||||
bucket = newBucket()
|
||||
|
||||
QINIU_KODO_TEST string
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
key += strconv.Itoa(rand.Int())
|
||||
keyFetch += strconv.Itoa(rand.Int())
|
||||
newkey1 += strconv.Itoa(rand.Int())
|
||||
newkey2 += strconv.Itoa(rand.Int())
|
||||
bucket.BatchDelete(nil, key, keyFetch, newkey1, newkey2)
|
||||
}
|
||||
|
||||
func newBucket() (bucket Bucket) {
|
||||
|
||||
QINIU_KODO_TEST = os.Getenv("QINIU_KODO_TEST")
|
||||
if skipTest() {
|
||||
println("[INFO] QINIU_KODO_TEST: skipping to test github.com/qiniu/api.v7")
|
||||
return
|
||||
}
|
||||
|
||||
ak := os.Getenv("QINIU_ACCESS_KEY")
|
||||
sk := os.Getenv("QINIU_SECRET_KEY")
|
||||
if ak == "" || sk == "" {
|
||||
panic("require ACCESS_KEY & SECRET_KEY")
|
||||
}
|
||||
SetMac(ak, sk)
|
||||
|
||||
bucketName = os.Getenv("QINIU_TEST_BUCKET")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
if bucketName == "" || domain == "" {
|
||||
panic("require test env")
|
||||
}
|
||||
client = NewWithoutZone(nil)
|
||||
|
||||
return client.Bucket(bucketName)
|
||||
}
|
||||
|
||||
func skipTest() bool {
|
||||
|
||||
return QINIU_KODO_TEST == ""
|
||||
}
|
||||
|
||||
func upFile(localFile, key string) error {
|
||||
|
||||
return bucket.PutFile(nil, nil, key, localFile, nil)
|
||||
}
|
||||
|
||||
func TestFetch(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
err := bucket.Fetch(nil, keyFetch, fetchURL)
|
||||
if err != nil {
|
||||
t.Fatal("bucket.Fetch failed:", err)
|
||||
}
|
||||
|
||||
entry, err := bucket.Stat(nil, keyFetch)
|
||||
if err != nil || entry.MimeType != "image/png" {
|
||||
t.Fatal("bucket.Fetch: Stat failed -", err, "entry:", entry)
|
||||
}
|
||||
fmt.Println(entry)
|
||||
}
|
||||
|
||||
func TestEntry(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
//上传一个文件用用于测试
|
||||
err := upFile("doc.go", key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, key)
|
||||
|
||||
einfo, err := bucket.Stat(nil, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mime := "text/plain"
|
||||
err = bucket.ChangeMime(nil, key, mime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
einfo, err = bucket.Stat(nil, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if einfo.MimeType != mime {
|
||||
t.Fatal("mime type did not change")
|
||||
}
|
||||
|
||||
err = bucket.Copy(nil, key, newkey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enewinfo, err := bucket.Stat(nil, newkey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if einfo.Hash != enewinfo.Hash {
|
||||
t.Fatal("invalid entryinfo:", einfo, enewinfo)
|
||||
}
|
||||
err = bucket.Move(nil, newkey1, newkey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enewinfo2, err := bucket.Stat(nil, newkey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if enewinfo.Hash != enewinfo2.Hash {
|
||||
t.Fatal("invalid entryinfo:", enewinfo, enewinfo2)
|
||||
}
|
||||
|
||||
err = bucket.Delete(nil, newkey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
119
vendor/github.com/qiniu/api.v7/kodo/token.go
generated
vendored
Normal file
119
vendor/github.com/qiniu/api.v7/kodo/token.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/x/url.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 根据空间(Bucket)的域名,以及文件的 key,获得 baseUrl。
|
||||
// 如果空间是 public 的,那么通过 baseUrl 可以直接下载文件内容。
|
||||
// 如果空间是 private 的,那么需要对 baseUrl 进行私有签名得到一个临时有效的 privateUrl 进行下载。
|
||||
//
|
||||
func MakeBaseUrl(domain, key string) (baseUrl string) {
|
||||
|
||||
return "http://" + domain + "/" + url.Escape(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type GetPolicy struct {
|
||||
Expires uint32
|
||||
}
|
||||
|
||||
func (p *Client) MakePrivateUrl(baseUrl string, policy *GetPolicy) (privateUrl string) {
|
||||
|
||||
var expires int64
|
||||
if policy == nil || policy.Expires == 0 {
|
||||
expires = 3600
|
||||
} else {
|
||||
expires = int64(policy.Expires)
|
||||
}
|
||||
deadline := time.Now().Unix() + expires
|
||||
|
||||
if strings.Contains(baseUrl, "?") {
|
||||
baseUrl += "&e="
|
||||
} else {
|
||||
baseUrl += "?e="
|
||||
}
|
||||
baseUrl += strconv.FormatInt(deadline, 10)
|
||||
|
||||
token := qbox.Sign(p.mac, []byte(baseUrl))
|
||||
return baseUrl + "&token=" + token
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
type PutPolicy struct {
|
||||
Scope string `json:"scope"`
|
||||
Expires uint32 `json:"deadline"` // 截止时间(以秒为单位)
|
||||
InsertOnly uint16 `json:"insertOnly,omitempty"` // 若非0, 即使Scope为 Bucket:Key 的形式也是insert only
|
||||
DetectMime uint8 `json:"detectMime,omitempty"` // 若非0, 则服务端根据内容自动确定 MimeType
|
||||
CallbackFetchKey uint8 `json:"callbackFetchKey,omitempty"`
|
||||
FsizeLimit int64 `json:"fsizeLimit,omitempty"`
|
||||
MimeLimit string `json:"mimeLimit,omitempty"`
|
||||
SaveKey string `json:"saveKey,omitempty"`
|
||||
CallbackUrl string `json:"callbackUrl,omitempty"`
|
||||
CallbackHost string `json:"callbackHost,omitempty"`
|
||||
CallbackBody string `json:"callbackBody,omitempty"`
|
||||
CallbackBodyType string `json:"callbackBodyType,omitempty"`
|
||||
ReturnUrl string `json:"returnUrl,omitempty"`
|
||||
ReturnBody string `json:"returnBody,omitempty"`
|
||||
PersistentOps string `json:"persistentOps,omitempty"`
|
||||
PersistentNotifyUrl string `json:"persistentNotifyUrl,omitempty"`
|
||||
PersistentPipeline string `json:"persistentPipeline,omitempty"`
|
||||
AsyncOps string `json:"asyncOps,omitempty"`
|
||||
EndUser string `json:"endUser,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"` // 格式:<HashName>:<HexHashValue>,目前支持 MD5/SHA1。
|
||||
UpHosts []string `json:"uphosts,omitempty"`
|
||||
DeleteAfterDays int `json:"deleteAfterDays,omitempty"`
|
||||
FileType int `json:"fileType,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Client) MakeUptoken(policy *PutPolicy) string {
|
||||
token, err := p.MakeUptokenWithSafe(policy)
|
||||
if err != nil {
|
||||
fmt.Errorf("makeuptoken failed: policy: %+v, error: %+v", policy, err)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *Client) MakeUptokenWithSafe(policy *PutPolicy) (token string, err error) {
|
||||
var rr = *policy
|
||||
if len(rr.UpHosts) == 0 {
|
||||
bucketName := getBucketNameFromPutPolicy(policy)
|
||||
bucketInfo, err1 := p.GetBucketInfo(bucketName)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
rr.UpHosts = bucketInfo.UpHosts
|
||||
}
|
||||
if rr.Expires == 0 {
|
||||
rr.Expires = 3600
|
||||
}
|
||||
rr.Expires += uint32(time.Now().Unix())
|
||||
b, _ := json.Marshal(&rr)
|
||||
token = qbox.SignWithData(p.mac, b)
|
||||
return
|
||||
}
|
||||
|
||||
func getBucketNameFromPutPolicy(policy *PutPolicy) (bucketName string) {
|
||||
scope := policy.Scope
|
||||
bucketName = strings.Split(scope, ":")[0]
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Client) GetBucketInfo(bucketName string) (bucketInfo api.BucketInfo, err error) {
|
||||
return p.apiCli.GetBucketInfo(p.mac.AccessKey, bucketName)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
70
vendor/github.com/qiniu/api.v7/kodo/token_test.go
generated
vendored
Normal file
70
vendor/github.com/qiniu/api.v7/kodo/token_test.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
// 删除 可能存在的 key
|
||||
bucket.BatchDelete(nil, key)
|
||||
}
|
||||
|
||||
func TestGetPrivateUrl(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
// 上传一个文件用用于测试
|
||||
err := upFile("token.go", key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, key)
|
||||
|
||||
baseUrl := MakeBaseUrl(domain, key)
|
||||
privateUrl := client.MakePrivateUrl(baseUrl, nil)
|
||||
|
||||
resp, err := http.Get(privateUrl)
|
||||
if err != nil {
|
||||
t.Fatal("http.Get failed:", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
h := sha1.New()
|
||||
io.Copy(h, resp.Body)
|
||||
etagExpected := base64.URLEncoding.EncodeToString(h.Sum([]byte{'\x16'}))
|
||||
|
||||
etag := resp.Header.Get("Etag")
|
||||
if etag[1:len(etag)-1] != etagExpected {
|
||||
t.Fatal("http.Get etag failed:", etag, etagExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func testClient_MakeUptokenBucket(t *testing.T) {
|
||||
c := New(0, nil)
|
||||
token := c.MakeUptoken(&PutPolicy{
|
||||
Scope: "gosdk",
|
||||
Expires: 3600,
|
||||
})
|
||||
if token == "" {
|
||||
t.Fatal("nil token")
|
||||
}
|
||||
|
||||
token, err := c.MakeUptokenWithSafe(&PutPolicy{
|
||||
Scope: "NotExistBucket",
|
||||
Expires: 3600,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("make up token fail")
|
||||
}
|
||||
}
|
||||
179
vendor/github.com/qiniu/api.v7/kodo/upload.go
generated
vendored
Normal file
179
vendor/github.com/qiniu/api.v7/kodo/upload.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/api.v7/kodocli"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
type PutExtra kodocli.PutExtra
|
||||
type RputExtra kodocli.RputExtra
|
||||
type PutRet kodocli.PutRet
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Bucket) makeUptoken(key string) string {
|
||||
|
||||
policy := &PutPolicy{
|
||||
Scope: p.Name + ":" + key,
|
||||
Expires: 3600,
|
||||
UpHosts: p.UpHosts,
|
||||
}
|
||||
return p.Conn.MakeUptoken(policy)
|
||||
}
|
||||
|
||||
func (p Bucket) makeUptokenWithoutKey() string {
|
||||
|
||||
policy := &PutPolicy{
|
||||
Scope: p.Name,
|
||||
Expires: 3600,
|
||||
UpHosts: p.UpHosts,
|
||||
}
|
||||
return p.Conn.MakeUptoken(policy)
|
||||
}
|
||||
|
||||
func (p Bucket) makeUploader() kodocli.Uploader {
|
||||
|
||||
c := &http.Client{Transport: p.Conn.Transport}
|
||||
return kodocli.Uploader{Conn: rpc.Client{c}, UpHosts: p.UpHosts, ApiCli: p.Conn.apiCli}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Put 上传一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) Put(
|
||||
ctx Context, ret interface{}, key string, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.Put(ctx, ret, uptoken, key, data, size, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// PutWithoutKey 上传一个文件。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) PutWithoutKey(
|
||||
ctx Context, ret interface{}, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.PutWithoutKey(ctx, ret, uptoken, data, size, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// PutFile 上传一个文件。
|
||||
// 和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) PutFile(
|
||||
ctx Context, ret interface{}, key, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.PutFile(ctx, ret, uptoken, key, localFile, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// PutFileWithoutKey 上传一个文件。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
// 和 PutWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) PutFileWithoutKey(
|
||||
ctx Context, ret interface{}, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.PutFileWithoutKey(ctx, ret, uptoken, localFile, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Rput 上传一个文件,支持断点续传和分块上传。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口。考虑到需要支持分块上传和断点续传,要的是 io.ReaderAt 接口,而不是 io.Reader。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) Rput(
|
||||
ctx Context, ret interface{}, key string, data io.ReaderAt, size int64, extra *RputExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.Rput(ctx, ret, uptoken, key, data, size, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// RputWithoutKey 上传一个文件,支持断点续传和分块上传。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// data 是文件内容的访问接口。考虑到需要支持分块上传和断点续传,要的是 io.ReaderAt 接口,而不是 io.Reader。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) RputWithoutKey(
|
||||
ctx Context, ret interface{}, data io.ReaderAt, size int64, extra *RputExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.RputWithoutKey(ctx, ret, uptoken, data, size, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// RputFile 上传一个文件,支持断点续传和分块上传。
|
||||
// 和 Rput 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.ReaderAt 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) RputFile(
|
||||
ctx Context, ret interface{}, key, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.RputFile(ctx, ret, uptoken, key, localFile, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// RputFileWithoutKey 上传一个文件,支持断点续传和分块上传。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
// 和 RputWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.ReaderAt 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) RputFileWithoutKey(
|
||||
ctx Context, ret interface{}, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.RputFileWithoutKey(ctx, ret, uptoken, localFile, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
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
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
24
vendor/github.com/qiniu/x/.gitignore
generated
vendored
Normal file
24
vendor/github.com/qiniu/x/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
10
vendor/github.com/qiniu/x/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/qiniu/x/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
install:
|
||||
- export QINIU_SRC=$HOME/gopath/src
|
||||
- mkdir -p $QINIU_SRC/qiniupkg.com
|
||||
- mv $QINIU_SRC/github.com/qiniu/x $QINIU_SRC/qiniupkg.com/x
|
||||
- export TRAVIS_BUILD_DIR=$QINIU_SRC/qiniupkg.com/x
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- go get golang.org/x/net/context
|
||||
- go get github.com/stretchr/testify/assert
|
||||
|
||||
27
vendor/github.com/qiniu/x/README.md
generated
vendored
Normal file
27
vendor/github.com/qiniu/x/README.md
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
qiniupkg.com/x
|
||||
===============
|
||||
|
||||
[](https://travis-ci.org/qiniu/x) [](https://godoc.org/qiniupkg.com/x)
|
||||
|
||||
# 下载
|
||||
|
||||
```
|
||||
go get qiniupkg.com/x
|
||||
```
|
||||
|
||||
# 使用文档
|
||||
|
||||
* [qiniupkg.com/x/bytes.v7](http://godoc.org/qiniupkg.com/x/bytes.v7)
|
||||
* [qiniupkg.com/x/cmdline.v7](http://godoc.org/qiniupkg.com/x/cmdline.v7)
|
||||
* [qiniupkg.com/x/config.v7](http://godoc.org/qiniupkg.com/x/config.v7)
|
||||
* [qiniupkg.com/x/ctype.v7](http://godoc.org/qiniupkg.com/x/ctype.v7)
|
||||
* [qiniupkg.com/x/jsonutil.v7](http://godoc.org/qiniupkg.com/x/jsonutil.v7)
|
||||
* [qiniupkg.com/x/log.v7](http://godoc.org/qiniupkg.com/x/log.v7)
|
||||
* [qiniupkg.com/x/mockhttp.v7](http://godoc.org/qiniupkg.com/x/mockhttp.v7)
|
||||
* [qiniupkg.com/x/reqid.v7](http://godoc.org/qiniupkg.com/x/reqid.v7)
|
||||
* [qiniupkg.com/x/rpc.v7](http://godoc.org/qiniupkg.com/x/rpc.v7)
|
||||
* [qiniupkg.com/x/rpc.v7/gob](http://godoc.org/qiniupkg.com/x/rpc.v7/gob)
|
||||
* [qiniupkg.com/x/ts.v7](http://godoc.org/qiniupkg.com/x/ts.v7)
|
||||
* [qiniupkg.com/x/url.v7](http://godoc.org/qiniupkg.com/x/url.v7)
|
||||
* [qiniupkg.com/x/xlog.v7](http://godoc.org/qiniupkg.com/x/xlog.v7)
|
||||
|
||||
4
vendor/github.com/qiniu/x/bytes.v7/README.md
generated
vendored
Normal file
4
vendor/github.com/qiniu/x/bytes.v7/README.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
qiniupkg.com/x/bytes.v7
|
||||
=====
|
||||
|
||||
Extension module of golang bytes processing
|
||||
177
vendor/github.com/qiniu/x/bytes.v7/bytes.go
generated
vendored
Normal file
177
vendor/github.com/qiniu/x/bytes.v7/bytes.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
package bytes
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type Reader struct {
|
||||
b []byte
|
||||
off int
|
||||
}
|
||||
|
||||
func NewReader(val []byte) *Reader {
|
||||
return &Reader{val, 0}
|
||||
}
|
||||
|
||||
func (r *Reader) Len() int {
|
||||
if r.off >= len(r.b) {
|
||||
return 0
|
||||
}
|
||||
return len(r.b) - r.off
|
||||
}
|
||||
|
||||
func (r *Reader) Bytes() []byte {
|
||||
return r.b[r.off:]
|
||||
}
|
||||
|
||||
func (r *Reader) SeekToBegin() (err error) {
|
||||
r.off = 0
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) Seek(offset int64, whence int) (ret int64, err error) {
|
||||
switch whence {
|
||||
case 0:
|
||||
case 1:
|
||||
offset += int64(r.off)
|
||||
case 2:
|
||||
offset += int64(len(r.b))
|
||||
default:
|
||||
err = syscall.EINVAL
|
||||
return
|
||||
}
|
||||
if offset < 0 {
|
||||
err = syscall.EINVAL
|
||||
return
|
||||
}
|
||||
if offset >= int64(len(r.b)) {
|
||||
r.off = len(r.b)
|
||||
} else {
|
||||
r.off = int(offset)
|
||||
}
|
||||
ret = int64(r.off)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) Read(val []byte) (n int, err error) {
|
||||
n = copy(val, r.b[r.off:])
|
||||
if n == 0 && len(val) != 0 {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
r.off += n
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) Close() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type Writer struct {
|
||||
b []byte
|
||||
n int
|
||||
}
|
||||
|
||||
func NewWriter(buff []byte) *Writer {
|
||||
return &Writer{buff, 0}
|
||||
}
|
||||
|
||||
func (p *Writer) Write(val []byte) (n int, err error) {
|
||||
n = copy(p.b[p.n:], val)
|
||||
if n == 0 && len(val) > 0 {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
p.n += n
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Writer) Len() int {
|
||||
return p.n
|
||||
}
|
||||
|
||||
func (p *Writer) Bytes() []byte {
|
||||
return p.b[:p.n]
|
||||
}
|
||||
|
||||
func (p *Writer) Reset() {
|
||||
p.n = 0
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type Buffer struct {
|
||||
b []byte
|
||||
}
|
||||
|
||||
func NewBuffer() *Buffer {
|
||||
return new(Buffer)
|
||||
}
|
||||
|
||||
func (p *Buffer) ReadAt(buf []byte, off int64) (n int, err error) {
|
||||
ioff := int(off)
|
||||
if len(p.b) <= ioff {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buf, p.b[ioff:])
|
||||
if n != len(buf) {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Buffer) WriteAt(buf []byte, off int64) (n int, err error) {
|
||||
ioff := int(off)
|
||||
iend := ioff + len(buf)
|
||||
if len(p.b) < iend {
|
||||
if len(p.b) == ioff {
|
||||
p.b = append(p.b, buf...)
|
||||
return len(buf), nil
|
||||
}
|
||||
zero := make([]byte, iend-len(p.b))
|
||||
p.b = append(p.b, zero...)
|
||||
}
|
||||
copy(p.b[ioff:], buf)
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (p *Buffer) WriteStringAt(buf string, off int64) (n int, err error) {
|
||||
ioff := int(off)
|
||||
iend := ioff + len(buf)
|
||||
if len(p.b) < iend {
|
||||
if len(p.b) == ioff {
|
||||
p.b = append(p.b, buf...)
|
||||
return len(buf), nil
|
||||
}
|
||||
zero := make([]byte, iend-len(p.b))
|
||||
p.b = append(p.b, zero...)
|
||||
}
|
||||
copy(p.b[ioff:], buf)
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (p *Buffer) Truncate(fsize int64) (err error) {
|
||||
size := int(fsize)
|
||||
if len(p.b) < size {
|
||||
zero := make([]byte, size-len(p.b))
|
||||
p.b = append(p.b, zero...)
|
||||
} else {
|
||||
p.b = p.b[:size]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Buffer) Buffer() []byte {
|
||||
return p.b
|
||||
}
|
||||
|
||||
func (p *Buffer) Len() int {
|
||||
return len(p.b)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
60
vendor/github.com/qiniu/x/bytes.v7/bytes_test.go
generated
vendored
Normal file
60
vendor/github.com/qiniu/x/bytes.v7/bytes_test.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package bytes
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
func TestBuffer(t *testing.T) {
|
||||
|
||||
b := NewBuffer()
|
||||
n, err := b.WriteStringAt("Hello", 4)
|
||||
if n != 5 || err != nil {
|
||||
t.Fatal("WriteStringAt failed:", n, err)
|
||||
}
|
||||
if b.Len() != 9 {
|
||||
t.Fatal("Buffer.Len invalid (9 is required):", b.Len())
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
n, err = b.ReadAt(buf, 50)
|
||||
if n != 0 || err != io.EOF {
|
||||
t.Fatal("ReadAt failed:", n, err)
|
||||
}
|
||||
|
||||
n, err = b.ReadAt(buf, 6)
|
||||
if n != 3 || err != io.EOF || string(buf[:n]) != "llo" {
|
||||
t.Fatal("ReadAt failed:", n, err, string(buf[:n]))
|
||||
}
|
||||
|
||||
n, err = b.WriteAt([]byte("Hi h"), 1)
|
||||
if n != 4 || err != nil {
|
||||
t.Fatal("WriteAt failed:", n, err)
|
||||
}
|
||||
if b.Len() != 9 {
|
||||
t.Fatal("Buffer.Len invalid (9 is required):", b.Len())
|
||||
}
|
||||
|
||||
n, err = b.ReadAt(buf, 0)
|
||||
if n != 9 || err != io.EOF || string(buf[:n]) != "\x00Hi hello" {
|
||||
t.Fatal("ReadAt failed:", n, err)
|
||||
}
|
||||
|
||||
n, err = b.WriteStringAt("LO world!", 7)
|
||||
if n != 9 || err != nil {
|
||||
t.Fatal("WriteStringAt failed:", n, err)
|
||||
}
|
||||
if b.Len() != 16 {
|
||||
t.Fatal("Buffer.Len invalid (16 is required):", b.Len())
|
||||
}
|
||||
|
||||
buf = make([]byte, 17)
|
||||
n, err = b.ReadAt(buf, 0)
|
||||
if n != 16 || err != io.EOF || string(buf[:n]) != "\x00Hi helLO world!" {
|
||||
t.Fatal("ReadAt failed:", n, err, string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
34
vendor/github.com/qiniu/x/bytes.v7/doc.go
generated
vendored
Normal file
34
vendor/github.com/qiniu/x/bytes.v7/doc.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
包 qiniupkg.com/x/bytes.v7 提供了 byte slice 相关的功能扩展
|
||||
|
||||
NewReader 创建一个 byte slice 的只读流:
|
||||
|
||||
var slice []byte
|
||||
...
|
||||
r := bytes.NewReader(slice)
|
||||
...
|
||||
r.Seek(0, 0) // r.SeekToBegin()
|
||||
...
|
||||
|
||||
和标准库的 bytes.NewReader 不同的是,这里的 Reader 支持 Seek。
|
||||
|
||||
NewWriter 创建一个有上限容量的写流:
|
||||
|
||||
slice := make([]byte, 1024)
|
||||
w := bytes.NewWriter(slice)
|
||||
...
|
||||
writtenData := w.Bytes()
|
||||
|
||||
如果我们向 w 里面写入超过 1024 字节的数据,那么多余的数据会被丢弃。
|
||||
|
||||
NewBuffer 创建一个可随机读写的内存文件,支持 ReadAt/WriteAt 方法,而不是 Read/Write:
|
||||
|
||||
b := bytes.NewBuffer()
|
||||
b.Truncate(100)
|
||||
b.WriteAt([]byte("hello"), 100)
|
||||
slice := make([]byte, 105)
|
||||
n, err := b.ReadAt(slice, 0)
|
||||
...
|
||||
*/
|
||||
package bytes
|
||||
|
||||
54
vendor/github.com/qiniu/x/bytes.v7/replace.go
generated
vendored
Normal file
54
vendor/github.com/qiniu/x/bytes.v7/replace.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package bytes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
func ReplaceAt(b []byte, off, nsrc int, dest []byte) []byte {
|
||||
|
||||
ndelta := len(dest) - nsrc
|
||||
if ndelta < 0 {
|
||||
left := b[off+nsrc:]
|
||||
off += copy(b[off:], dest)
|
||||
off += copy(b[off:], left)
|
||||
return b[:off]
|
||||
}
|
||||
|
||||
if ndelta > 0 {
|
||||
b = append(b, dest[:ndelta]...)
|
||||
copy(b[off+len(dest):], b[off+nsrc:])
|
||||
copy(b[off:], dest)
|
||||
} else {
|
||||
copy(b[off:], dest)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func ReplaceOne(b []byte, from int, src, dest []byte) ([]byte, int) {
|
||||
|
||||
pos := bytes.Index(b[from:], src)
|
||||
if pos < 0 {
|
||||
return b, -1
|
||||
}
|
||||
|
||||
from += pos
|
||||
return ReplaceAt(b, from, len(src), dest), from + len(dest)
|
||||
}
|
||||
|
||||
func Replace(b []byte, src, dest []byte, n int) []byte {
|
||||
|
||||
from := 0
|
||||
for n != 0 {
|
||||
b, from = ReplaceOne(b, from, src, dest)
|
||||
if from < 0 {
|
||||
break
|
||||
}
|
||||
n--
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
54
vendor/github.com/qiniu/x/bytes.v7/replace_test.go
generated
vendored
Normal file
54
vendor/github.com/qiniu/x/bytes.v7/replace_test.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package bytes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type replaceCase struct {
|
||||
s string
|
||||
src string
|
||||
dest string
|
||||
n int
|
||||
}
|
||||
|
||||
func stringReplace(b string, src, dest string, n int) string {
|
||||
|
||||
return string(Replace([]byte(b), []byte(src), []byte(dest), n))
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
|
||||
cases := []replaceCase{
|
||||
{"hello, world!", "world", "xsw", -1},
|
||||
{"hello, world world world", "world", "xsw", 1},
|
||||
{"hello, world world world", "world", "xsw", 2},
|
||||
{"hello, world world world", "world", "xsw", -1},
|
||||
{"hello, xsw!", "xsw", "world", -1},
|
||||
{"hello, xsw xsw xsw", "xsw", "world", 1},
|
||||
{"hello, xsw xsw xsw", "xsw", "world", 2},
|
||||
{"hello, xsw xsw xsw", "xsw", "world", -1},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
ret := stringReplace(c.s, c.src, c.dest, c.n)
|
||||
expected := strings.Replace(c.s, c.src, c.dest, c.n)
|
||||
if ret != expected {
|
||||
t.Fatal("Replace failed:", c, "ret:", ret, "expected:", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringInsertAt(b string, off int, text string) string {
|
||||
|
||||
return string(ReplaceAt([]byte(b), off, 0, []byte(text)))
|
||||
}
|
||||
|
||||
func TestInsertAt(t *testing.T) {
|
||||
|
||||
ret := stringInsertAt("helloworld", 5, ", ")
|
||||
if ret != "hello, world" {
|
||||
t.Fatal("InsertAt failed:", ret)
|
||||
}
|
||||
}
|
||||
|
||||
63
vendor/github.com/qiniu/x/bytes.v7/seekable/seekable.go
generated
vendored
Normal file
63
vendor/github.com/qiniu/x/bytes.v7/seekable/seekable.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// This package provide a method to read and replace http.Request's body.
|
||||
package seekable
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"qiniupkg.com/x/bytes.v7"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type Seekabler interface {
|
||||
Bytes() []byte
|
||||
Read(val []byte) (n int, err error)
|
||||
SeekToBegin() error
|
||||
}
|
||||
|
||||
type SeekableCloser interface {
|
||||
Seekabler
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type readCloser struct {
|
||||
Seekabler
|
||||
io.Closer
|
||||
}
|
||||
|
||||
var ErrNoBody = errors.New("no body")
|
||||
|
||||
func New(req *http.Request) (r SeekableCloser, err error) {
|
||||
if req.Body == nil {
|
||||
return nil, ErrNoBody
|
||||
}
|
||||
var ok bool
|
||||
if r, ok = req.Body.(SeekableCloser); ok {
|
||||
return
|
||||
}
|
||||
b, err2 := ReadAll(req)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
r = bytes.NewReader(b)
|
||||
req.Body = readCloser{r, req.Body}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadAll(req *http.Request) (b []byte, err error) {
|
||||
if req.ContentLength > 0 {
|
||||
b = make([]byte, int(req.ContentLength))
|
||||
_, err = io.ReadFull(req.Body, b)
|
||||
return
|
||||
} else if req.ContentLength == 0 {
|
||||
return nil, ErrNoBody
|
||||
}
|
||||
return ioutil.ReadAll(req.Body)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
43
vendor/github.com/qiniu/x/bytes.v7/seekable/seekable_test.go
generated
vendored
Normal file
43
vendor/github.com/qiniu/x/bytes.v7/seekable/seekable_test.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package seekable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSeekable_EOFIfReqAlreadyParsed(t *testing.T) {
|
||||
body := "a=1"
|
||||
req, err := http.NewRequest("POST", "/a", bytes.NewBufferString(body))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", "3")
|
||||
req.ParseForm()
|
||||
_, err = New(req)
|
||||
assert.Equal(t, err.Error(), "EOF")
|
||||
}
|
||||
|
||||
func TestSeekable_WorkaroundForEOF(t *testing.T) {
|
||||
body := "a=1"
|
||||
req, err := http.NewRequest("POST", "/a", bytes.NewBufferString(body))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", "3")
|
||||
_, _ = New(req)
|
||||
req.ParseForm()
|
||||
assert.Equal(t, req.FormValue("a"), "1")
|
||||
_, err = New(req)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSeekable(t *testing.T) {
|
||||
body := "a=1"
|
||||
req, err := http.NewRequest("POST", "/a", bytes.NewBufferString(body))
|
||||
assert.NoError(t, err)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", "3")
|
||||
_, err = New(req)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
264
vendor/github.com/qiniu/x/cmdline.v7/cmdline.go
generated
vendored
Normal file
264
vendor/github.com/qiniu/x/cmdline.v7/cmdline.go
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
package cmdline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
. "qiniupkg.com/x/ctype.v7"
|
||||
)
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
|
||||
Shell 基础规则:
|
||||
|
||||
* 多行字符串:用 '...' 或 "..."。其中 " 会自动转义 $(var),而 ' 不会。
|
||||
* 普通字符串:用 [ \t] 分隔。转义符以 \ 开头。
|
||||
* 外部命令:`...`。
|
||||
|
||||
七牛规则:
|
||||
|
||||
* 外部命令: `...` 或 |...| 。
|
||||
* 多行字符串:用 '...' 或 ```\n...``` 或 ===\n...=== 不转义。用 "...",支持以 \ 开头的转义,也支持外部命令。
|
||||
* 普通字符串:用 [ \t] 分隔。转义符以 \ 开头,同时也支持外部命令。
|
||||
* 关于 $(var) 支持:每个命令自己执行 $(var) 的展开。不统一执行的原因是,在不同上下文需要不同的转义方式。
|
||||
|
||||
样例:
|
||||
|
||||
post http://rs.qiniu.com/delete/`base64 Bucket:Key`
|
||||
auth `qbox AccessKey SecretKey`
|
||||
ret 200
|
||||
|
||||
post http://rs.qiniu.com/batch
|
||||
auth qboxtest
|
||||
form op=/delete/`base64 Bucket:Key`&op=/delete/`base64 Bucket2:Key2`
|
||||
ret 200
|
||||
|
||||
post http://rs.qiniu.com/batch
|
||||
auth qboxtest
|
||||
form op=/delete/`base64 Bucket:Key`&op=/delete/`base64 Bucket:NotExistKey`
|
||||
ret 298
|
||||
json '[
|
||||
{"code": 200}, {"code": 612}
|
||||
]'
|
||||
equal $(code1) 200
|
||||
|
||||
// -------------------------------------------------------------------------*/
|
||||
|
||||
var (
|
||||
EOF = errors.New("end of file")
|
||||
ErrUnsupportedFeatureSubCmd = errors.New("unsupported feature: sub command")
|
||||
ErrUnsupportedFeatureMultiCmds = errors.New("unsupported feature: multi commands")
|
||||
ErrInvalidEscapeChar = errors.New("invalid escape char")
|
||||
ErrIncompleteStringExpectQuot = errors.New("incomplete string, expect \"")
|
||||
ErrIncompleteStringExpectSquot = errors.New("incomplete string, expect '")
|
||||
ErrIncompleteStringExpectBacktick = errors.New("incomplete string, expect ` or |")
|
||||
)
|
||||
|
||||
var (
|
||||
errEOL = errors.New("end of line")
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Skip(str string, typeMask uint32) string {
|
||||
|
||||
for i := 0; i < len(str); i++ {
|
||||
if !Is(typeMask, rune(str[i])) {
|
||||
return str[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Find(str string, typeMask uint32) (n int) {
|
||||
|
||||
for n = 0; n < len(str); n++ {
|
||||
if Is(typeMask, rune(str[n])) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// EOL = \r\n? | \n
|
||||
//
|
||||
func requireEOL(code string) (hasEOL bool, codeNext string) {
|
||||
|
||||
if strings.HasPrefix(code, "\r") {
|
||||
if strings.HasPrefix(code[1:], "\n") {
|
||||
return true, code[2:]
|
||||
}
|
||||
} else if !strings.HasPrefix(code, "\n") {
|
||||
return false, code
|
||||
}
|
||||
return true, code[1:]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type Parser struct {
|
||||
ExecSub func(code string) (string, error)
|
||||
Escape func(c byte) string
|
||||
comment bool
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
|
||||
return &Parser{
|
||||
ExecSub: defaultExecSub,
|
||||
Escape: defaultEscape,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultExecSub(code string) (string, error) {
|
||||
return "", ErrUnsupportedFeatureSubCmd
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
endOfLine = EOL | SEMICOLON // [\r\n;]
|
||||
blanks = SPACE_BAR | TAB
|
||||
blankAndEOLs = SPACE_BAR | TAB | endOfLine
|
||||
)
|
||||
|
||||
const (
|
||||
endMask_QuotString = RDIV | BACKTICK | OR | QUOT // [\\`|"]
|
||||
endMask_NonquotString = RDIV | BACKTICK | OR | blankAndEOLs // [\\`| \t\r\n;]
|
||||
)
|
||||
|
||||
func (p *Parser) parseString(
|
||||
code string, endMask uint32) (item string, ok bool, codeNext string, err error) {
|
||||
|
||||
codeNext = code
|
||||
for {
|
||||
n := Find(codeNext, endMask)
|
||||
if n > 0 {
|
||||
item += codeNext[:n]
|
||||
ok = true
|
||||
}
|
||||
if len(codeNext) == n {
|
||||
codeNext = ""
|
||||
if endMask == endMask_QuotString {
|
||||
err = ErrIncompleteStringExpectQuot
|
||||
} else {
|
||||
err = EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
switch codeNext[n] {
|
||||
case '\\':
|
||||
if len(codeNext) == n+1 {
|
||||
err = ErrInvalidEscapeChar
|
||||
return
|
||||
}
|
||||
item += p.Escape(codeNext[n+1])
|
||||
codeNext = codeNext[n+2:]
|
||||
case '`', '|':
|
||||
c := codeNext[n]
|
||||
codeNext = codeNext[n+1:]
|
||||
len := strings.IndexByte(codeNext, c)
|
||||
if len < 0 {
|
||||
err = ErrIncompleteStringExpectBacktick
|
||||
return
|
||||
}
|
||||
if !p.comment {
|
||||
valSub, errSub := p.ExecSub(codeNext[:len])
|
||||
if errSub != nil {
|
||||
err = errors.New("Exec `" + codeNext[:len] + "` failed: " + errSub.Error())
|
||||
return
|
||||
}
|
||||
item += valSub
|
||||
}
|
||||
codeNext = codeNext[len+1:]
|
||||
case '"':
|
||||
ok = true
|
||||
codeNext = codeNext[n+1:]
|
||||
return
|
||||
default:
|
||||
if Is(endOfLine, rune(codeNext[n])) {
|
||||
err = errEOL
|
||||
}
|
||||
codeNext = codeNext[n+1:]
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) parseItem(
|
||||
code string, skipMask uint32) (item string, ok bool, codeNext string, err error) {
|
||||
|
||||
codeNext = Skip(code, skipMask)
|
||||
if len(codeNext) == 0 {
|
||||
err = EOF
|
||||
return
|
||||
}
|
||||
|
||||
switch codeNext[0] {
|
||||
case '"':
|
||||
return p.parseString(codeNext[1:], endMask_QuotString)
|
||||
case '\'':
|
||||
codeNext = codeNext[1:]
|
||||
len := strings.IndexByte(codeNext, '\'')
|
||||
if len < 0 {
|
||||
err = ErrIncompleteStringExpectSquot
|
||||
return
|
||||
}
|
||||
return codeNext[:len], true, codeNext[len+1:], nil
|
||||
default:
|
||||
if strings.HasPrefix(codeNext, "```") || strings.HasPrefix(codeNext, "===") {
|
||||
endMark := codeNext[:3]
|
||||
_, codeNext = requireEOL(codeNext[3:])
|
||||
len := strings.Index(codeNext, endMark)
|
||||
if len < 0 {
|
||||
err = errors.New("incomplete string, expect " + endMark)
|
||||
return
|
||||
}
|
||||
return codeNext[:len], true, codeNext[len+3:], nil
|
||||
}
|
||||
return p.parseString(codeNext, endMask_NonquotString)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ParseCmd(cmdline string) (cmd []string, err error) {
|
||||
|
||||
cmd, _, err = p.ParseCode(cmdline)
|
||||
if err == EOF && len(cmd) > 0 {
|
||||
return cmd, nil
|
||||
}
|
||||
if err == nil {
|
||||
err = ErrUnsupportedFeatureMultiCmds
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) ParseCode(code string) (cmd []string, codeNext string, err error) {
|
||||
|
||||
item, ok, codeNext, err := p.parseItem(code, blankAndEOLs)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p.comment = strings.HasPrefix(item, "#")
|
||||
|
||||
cmd = append(cmd, item)
|
||||
for err == nil {
|
||||
item, ok, codeNext, err = p.parseItem(codeNext, blanks)
|
||||
if ok {
|
||||
cmd = append(cmd, item)
|
||||
}
|
||||
}
|
||||
if err == errEOL {
|
||||
err = nil
|
||||
}
|
||||
if p.comment {
|
||||
cmd = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
173
vendor/github.com/qiniu/x/cmdline.v7/cmdline_test.go
generated
vendored
Normal file
173
vendor/github.com/qiniu/x/cmdline.v7/cmdline_test.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
package cmdline
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func equalErr(err error, errExp interface{}) bool {
|
||||
|
||||
if err == nil || errExp == nil {
|
||||
return err == nil && errExp == nil
|
||||
}
|
||||
return err.Error() == errExp.(string)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestComment(t *testing.T) {
|
||||
|
||||
execSub := false
|
||||
ctx := Parser{
|
||||
ExecSub: func(code string) (string, error) {
|
||||
execSub = true
|
||||
return "[" + code + "]", nil
|
||||
},
|
||||
Escape: func(c byte) string {
|
||||
return string(c)
|
||||
},
|
||||
}
|
||||
|
||||
cmd, codeNext, err := ctx.ParseCode("#abc `calc $(a)+$(b)`")
|
||||
if err != EOF || codeNext != "" {
|
||||
t.Fatal("ParseCode: eof is expected")
|
||||
}
|
||||
if execSub {
|
||||
t.Fatal("don't execSub")
|
||||
}
|
||||
if len(cmd) != 0 {
|
||||
t.Fatal("len(cmd) != 0")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type caseParse struct {
|
||||
code string
|
||||
cmd []string
|
||||
codeNext string
|
||||
err interface{}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
||||
cases := []caseParse{
|
||||
{
|
||||
code: ";b",
|
||||
cmd: []string{"b"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: ";b;abc",
|
||||
cmd: []string{"b"},
|
||||
codeNext: "abc",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
code: "a`b`\\c",
|
||||
cmd: []string{"a[b]c"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "a`b`c 'c\\n`123`' \"c\\n\"",
|
||||
cmd: []string{"a[b]c", "c\\n`123`", "cn"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest 'mac AccessKey SecretKey'",
|
||||
cmd: []string{"auth", "qboxtest", "mac AccessKey SecretKey"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "post http://rs.qiniu.com/delete/`base64 Bucket:Key`",
|
||||
cmd: []string{"post", "http://rs.qiniu.com/delete/[base64 Bucket:Key]"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "post http://rs.qiniu.com/delete `base64 Bucket:Key`",
|
||||
cmd: []string{"post", "http://rs.qiniu.com/delete", "[base64 Bucket:Key]"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "post http://rs.qiniu.com/delete/|base64 Bucket:Key|",
|
||||
cmd: []string{"post", "http://rs.qiniu.com/delete/[base64 Bucket:Key]"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: `json '[
|
||||
{"code": 200}, {"code": 612}
|
||||
]'`,
|
||||
cmd: []string{"json", `[
|
||||
{"code": 200}, {"code": 612}
|
||||
]`},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest ```\nmac AccessKey SecretKey```",
|
||||
cmd: []string{"auth", "qboxtest", "mac AccessKey SecretKey"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest ===\nmac AccessKey SecretKey```",
|
||||
cmd: []string{"auth", "qboxtest"},
|
||||
codeNext: "mac AccessKey SecretKey```",
|
||||
err: "incomplete string, expect ===",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest ===\rmac AccessKey SecretKey===",
|
||||
cmd: []string{"auth", "qboxtest", "mac AccessKey SecretKey"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest ===\n\rmac AccessKey SecretKey===",
|
||||
cmd: []string{"auth", "qboxtest", "\rmac AccessKey SecretKey"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest ===\r\n\nmac AccessKey SecretKey===",
|
||||
cmd: []string{"auth", "qboxtest", "\nmac AccessKey SecretKey"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
{
|
||||
code: "auth qboxtest ===mac AccessKey SecretKey===",
|
||||
cmd: []string{"auth", "qboxtest", "mac AccessKey SecretKey"},
|
||||
codeNext: "",
|
||||
err: "end of file",
|
||||
},
|
||||
}
|
||||
|
||||
ctx := Parser{
|
||||
ExecSub: func(code string) (string, error) {
|
||||
return "[" + code + "]", nil
|
||||
},
|
||||
Escape: func(c byte) string {
|
||||
return string(c)
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
cmd, codeNext, err := ctx.ParseCode(c.code)
|
||||
if !equalErr(err, c.err) {
|
||||
t.Fatal("Parse failed:", c, err)
|
||||
}
|
||||
if !reflect.DeepEqual(cmd, c.cmd) || codeNext != c.codeNext {
|
||||
t.Fatal("Parse failed:", c, cmd, codeNext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
98
vendor/github.com/qiniu/x/cmdline.v7/escape.go
generated
vendored
Normal file
98
vendor/github.com/qiniu/x/cmdline.v7/escape.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package cmdline
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
escTableBaseChar = '0'
|
||||
escTableLen = ('z' - escTableBaseChar + 1)
|
||||
)
|
||||
|
||||
var escTable = []byte{
|
||||
0, // 0 [48]
|
||||
49, // 1 [49]
|
||||
50, // 2 [50]
|
||||
51, // 3 [51]
|
||||
52, // 4 [52]
|
||||
53, // 5 [53]
|
||||
54, // 6 [54]
|
||||
55, // 7 [55]
|
||||
56, // 8 [56]
|
||||
57, // 9 [57]
|
||||
58, // : [58]
|
||||
59, // ; [59]
|
||||
60, // < [60]
|
||||
61, // = [61]
|
||||
62, // > [62]
|
||||
63, // ? [63]
|
||||
64, // @ [64]
|
||||
65, // A [65]
|
||||
66, // B [66]
|
||||
67, // C [67]
|
||||
68, // D [68]
|
||||
69, // E [69]
|
||||
70, // F [70]
|
||||
71, // G [71]
|
||||
72, // H [72]
|
||||
73, // I [73]
|
||||
74, // J [74]
|
||||
75, // K [75]
|
||||
76, // L [76]
|
||||
77, // M [77]
|
||||
78, // N [78]
|
||||
79, // O [79]
|
||||
80, // P [80]
|
||||
81, // Q [81]
|
||||
82, // R [82]
|
||||
83, // S [83]
|
||||
84, // T [84]
|
||||
85, // U [85]
|
||||
86, // V [86]
|
||||
87, // W [87]
|
||||
88, // X [88]
|
||||
89, // Y [89]
|
||||
90, // Z [90]
|
||||
91, // [ [91]
|
||||
92, // \ [92]
|
||||
93, // ] [93]
|
||||
94, // ^ [94]
|
||||
95, // _ [95]
|
||||
96, // ` [96]
|
||||
97, // a [97]
|
||||
98, // b [98]
|
||||
99, // c [99]
|
||||
100, // d [100]
|
||||
101, // e [101]
|
||||
102, // f [102]
|
||||
103, // g [103]
|
||||
104, // h [104]
|
||||
105, // i [105]
|
||||
106, // j [106]
|
||||
107, // k [107]
|
||||
108, // l [108]
|
||||
109, // m [109]
|
||||
'\n', // n [110]
|
||||
111, // o [111]
|
||||
112, // p [112]
|
||||
113, // q [113]
|
||||
'\r', // r [114]
|
||||
115, // s [115]
|
||||
'\t', // t [116]
|
||||
117, // u [117]
|
||||
118, // v [118]
|
||||
119, // w [119]
|
||||
120, // x [120]
|
||||
121, // y [121]
|
||||
122, // z [122]
|
||||
123, // { [123]
|
||||
}
|
||||
|
||||
func defaultEscape(c byte) string {
|
||||
|
||||
if c - escTableBaseChar < escTableLen {
|
||||
c = escTable[c - escTableBaseChar]
|
||||
}
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
41
vendor/github.com/qiniu/x/cmdline.v7/escape_test.go
generated
vendored
Normal file
41
vendor/github.com/qiniu/x/cmdline.v7/escape_test.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package cmdline
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestEscape(t *testing.T) {
|
||||
|
||||
for i := 0; i < escTableBaseChar; i++ {
|
||||
checkEscapeChar(t, i, i)
|
||||
}
|
||||
|
||||
table := make([]int, escTableLen)
|
||||
for i := 0; i < escTableLen; i++ {
|
||||
table[i] = escTableBaseChar + i
|
||||
}
|
||||
table['0'-escTableBaseChar] = 0
|
||||
table['r'-escTableBaseChar] = '\r'
|
||||
table['t'-escTableBaseChar] = '\t'
|
||||
table['n'-escTableBaseChar] = '\n'
|
||||
for i := 0; i < escTableLen; i++ {
|
||||
checkEscapeChar(t, escTableBaseChar+i, table[i])
|
||||
}
|
||||
|
||||
for i := int(escTableBaseChar + escTableLen); i < 256; i++ {
|
||||
checkEscapeChar(t, i, i)
|
||||
}
|
||||
}
|
||||
|
||||
func checkEscapeChar(t *testing.T, i, exp int) {
|
||||
|
||||
ret := defaultEscape(byte(i))
|
||||
if ret != string(exp) {
|
||||
t.Fatal("escapeChar failed:", i)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
41
vendor/github.com/qiniu/x/config.v7/getdir.go
generated
vendored
Normal file
41
vendor/github.com/qiniu/x/config.v7/getdir.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
var homeEnvNames = [][]string{
|
||||
{"HOME"},
|
||||
{"HOMEDRIVE", "HOMEPATH"},
|
||||
}
|
||||
|
||||
var (
|
||||
ErrHomeNotFound = errors.New("$HOME not found")
|
||||
)
|
||||
|
||||
func getEnv(name []string) (v string) {
|
||||
|
||||
if len(name) == 1 {
|
||||
return os.Getenv(name[0])
|
||||
}
|
||||
for _, k := range name {
|
||||
v += os.Getenv(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetDir(app string) (dir string, err error) {
|
||||
|
||||
for _, name := range homeEnvNames {
|
||||
home := getEnv(name)
|
||||
if home == "" {
|
||||
continue
|
||||
}
|
||||
dir = home + "/." + app
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
return
|
||||
}
|
||||
return "", ErrHomeNotFound
|
||||
}
|
||||
|
||||
116
vendor/github.com/qiniu/x/config.v7/load_conf.go
generated
vendored
Normal file
116
vendor/github.com/qiniu/x/config.v7/load_conf.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
|
||||
"qiniupkg.com/x/log.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
confName *string
|
||||
)
|
||||
|
||||
func Init(cflag, app, default_conf string) {
|
||||
|
||||
confDir, _ := GetDir(app)
|
||||
confName = flag.String(cflag, confDir+"/"+default_conf, "the config file")
|
||||
}
|
||||
|
||||
func GetPath() string {
|
||||
|
||||
if confName != nil {
|
||||
return *confName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Load(conf interface{}) (err error) {
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
log.Info("Use the config file of ", *confName)
|
||||
return LoadEx(conf, *confName)
|
||||
}
|
||||
|
||||
func LoadEx(conf interface{}, confName string) (err error) {
|
||||
|
||||
data, err := ioutil.ReadFile(confName)
|
||||
if err != nil {
|
||||
log.Error("Load conf failed:", err)
|
||||
return
|
||||
}
|
||||
data = trimComments(data)
|
||||
|
||||
err = json.Unmarshal(data, conf)
|
||||
if err != nil {
|
||||
log.Error("Parse conf failed:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func LoadFile(conf interface{}, confName string) (err error) {
|
||||
|
||||
data, err := ioutil.ReadFile(confName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data = trimComments(data)
|
||||
|
||||
return json.Unmarshal(data, conf)
|
||||
}
|
||||
|
||||
func LoadBytes(conf interface{}, data []byte) (err error) {
|
||||
|
||||
return json.Unmarshal(trimComments(data), conf)
|
||||
}
|
||||
|
||||
func LoadString(conf interface{}, data string) (err error) {
|
||||
|
||||
return json.Unmarshal(trimComments([]byte(data)), conf)
|
||||
}
|
||||
|
||||
func trimComments(data []byte) (data1 []byte) {
|
||||
|
||||
var line []byte
|
||||
|
||||
data1 = data[:0]
|
||||
for {
|
||||
pos := bytes.IndexByte(data, '\n')
|
||||
if pos < 0 {
|
||||
line = data
|
||||
} else {
|
||||
line = data[:pos+1]
|
||||
}
|
||||
data1 = append(data1, trimCommentsLine(line)...)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
data = data[pos+1:]
|
||||
}
|
||||
}
|
||||
|
||||
func trimCommentsLine(line []byte) []byte {
|
||||
|
||||
n := len(line)
|
||||
quoteCount := 0
|
||||
for i := 0; i < n; i++ {
|
||||
c := line[i]
|
||||
switch c {
|
||||
case '\\':
|
||||
i++
|
||||
case '"':
|
||||
quoteCount++
|
||||
case '#':
|
||||
if (quoteCount&1) == 0 {
|
||||
return line[:i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
59
vendor/github.com/qiniu/x/config.v7/load_conf_test.go
generated
vendored
Normal file
59
vendor/github.com/qiniu/x/config.v7/load_conf_test.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"qiniupkg.com/x/config.v7"
|
||||
)
|
||||
|
||||
func TestTrimComments(t *testing.T) {
|
||||
|
||||
confData := `{
|
||||
"debug_level": 0, # 调试级别
|
||||
"rs_host": "http://localhost:15001", #RS服务
|
||||
"limit": 5, #限制数
|
||||
"retryTimes": 56,
|
||||
"quote0": "###",
|
||||
"quote": "quo\\\"\\#",
|
||||
"ant": "ant\\#" #123
|
||||
}`
|
||||
|
||||
confDataExp := `{
|
||||
"debug_level": 0,
|
||||
"rs_host": "http://localhost:15001",
|
||||
"limit": 5,
|
||||
"retryTimes": 56,
|
||||
"quote0": "###",
|
||||
"quote": "quo\\\"\\#",
|
||||
"ant": "ant\\#"
|
||||
}`
|
||||
|
||||
var (
|
||||
conf, confExp interface{}
|
||||
)
|
||||
err := config.LoadString(&conf, confData)
|
||||
if err != nil {
|
||||
t.Fatal("config.LoadString(conf) failed:", err)
|
||||
}
|
||||
err = config.LoadString(&confExp, confDataExp)
|
||||
if err != nil {
|
||||
t.Fatal("config.LoadString(confExp) failed:", err)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
t.Fatal("json.Marshal failed:", err)
|
||||
}
|
||||
|
||||
bExp, err := json.Marshal(confExp)
|
||||
if err != nil {
|
||||
t.Fatal("json.Marshal(exp) failed:", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(b, bExp) {
|
||||
t.Fatal("b != bExp")
|
||||
}
|
||||
}
|
||||
|
||||
237
vendor/github.com/qiniu/x/ctype.v7/ctype.go
generated
vendored
Normal file
237
vendor/github.com/qiniu/x/ctype.v7/ctype.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
package ctype
|
||||
|
||||
const (
|
||||
UPPER = 0x01 /* upper case letter[A-Z] */
|
||||
LOWER = 0x02 /* lower case letter[a-z] */
|
||||
DIGIT = 0x04 /* digit[0-9] */
|
||||
UNDERLINE = 0x08 /* underline[_] */
|
||||
XDIGIT = 0x10 /* xdigit[0-9a-fA-F] */
|
||||
EOL = 0x20 /* [\r\n] */
|
||||
ADD = 0x40 /* [+] */
|
||||
SUB = 0x80 /* [-] */
|
||||
MUL = 0x100 /* [*] */
|
||||
DIV = 0x200 /* [/] */
|
||||
LT = 0x400 /* [<] */
|
||||
GT = 0x800 /* [>] */
|
||||
EQ = 0x1000 /* [=] */
|
||||
RDIV = 0x2000 /* [\\], right-division, anti-slash */
|
||||
DOT = 0x4000 /* [.] */
|
||||
COLON = 0x8000 /* [:], colon */
|
||||
PERCENT = 0x10000 /* [%] */
|
||||
AND = 0x20000 /* [&] */
|
||||
OR = 0x40000 /* [|] */
|
||||
SPACE_BAR = 0x80000 /* [ ] */
|
||||
LCAP_R = 0x100000 /* [r] */
|
||||
LCAP_T = 0x200000 /* [t] */
|
||||
LCAP_N = 0x400000 /* [n] */
|
||||
LCAP_W = 0x800000 /* [w] */
|
||||
COMMA = 0x1000000 /* [,] */
|
||||
SEMICOLON = 0x2000000 /* [;] */
|
||||
TAB = 0x4000000 /* [\t] */
|
||||
QUOT = 0x8000000 /* ["] */
|
||||
BACKTICK = 0x10000000 /* [`] */
|
||||
)
|
||||
|
||||
const (
|
||||
BLANK = SPACE_BAR
|
||||
TSPACE = TAB | EOL
|
||||
SPACE = SPACE_BAR | TSPACE
|
||||
PATH_SEP = DIV | RDIV
|
||||
ALPHA = UPPER | LOWER
|
||||
SYMBOL_FIRST_CHAR = ALPHA
|
||||
SYMBOL_NEXT_CHAR = SYMBOL_FIRST_CHAR | DIGIT
|
||||
CSYMBOL_FIRST_CHAR = ALPHA | UNDERLINE
|
||||
CSYMBOL_NEXT_CHAR = CSYMBOL_FIRST_CHAR | DIGIT
|
||||
XMLSYMBOL_FIRST_CHAR = CSYMBOL_FIRST_CHAR
|
||||
XMLSYMBOL_NEXT_CHAR = CSYMBOL_NEXT_CHAR | SUB
|
||||
DOMAIN_CHAR = ALPHA | DIGIT | SUB | ADD | DOT
|
||||
BASE64 = ALPHA | DIGIT | ADD | DIV // [a-zA-Z0-9+/]
|
||||
URLSAFE_BASE64 = ALPHA | DIGIT | SUB | UNDERLINE // [a-zA-Z0-9\-_]
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
var table = []uint32{
|
||||
0, // [0]
|
||||
0, // [1]
|
||||
0, // [2]
|
||||
0, // [3]
|
||||
0, // [4]
|
||||
0, // [5]
|
||||
0, // [6]
|
||||
0, // [7]
|
||||
0, // [8]
|
||||
TAB, // [9]
|
||||
EOL, // [10]
|
||||
0, // [11]
|
||||
0, // [12]
|
||||
EOL, // [13]
|
||||
0, // [14]
|
||||
0, // [15]
|
||||
0, // [16]
|
||||
0, // [17]
|
||||
0, // [18]
|
||||
0, // [19]
|
||||
0, // [20]
|
||||
0, // [21]
|
||||
0, // [22]
|
||||
0, // [23]
|
||||
0, // [24]
|
||||
0, // [25]
|
||||
0, // [26]
|
||||
0, // [27]
|
||||
0, // [28]
|
||||
0, // [29]
|
||||
0, // [30]
|
||||
0, // [31]
|
||||
SPACE_BAR, // [32]
|
||||
0, // ! [33]
|
||||
QUOT, // " [34]
|
||||
0, // # [35]
|
||||
0, // $ [36]
|
||||
PERCENT, // % [37]
|
||||
AND, // & [38]
|
||||
0, // ' [39]
|
||||
0, // ( [40]
|
||||
0, // ) [41]
|
||||
MUL, // * [42]
|
||||
ADD, // + [43]
|
||||
COMMA, // , [44]
|
||||
SUB, // - [45]
|
||||
DOT, // . [46]
|
||||
DIV, // / [47]
|
||||
DIGIT | XDIGIT, // 0 [48]
|
||||
DIGIT | XDIGIT, // 1 [49]
|
||||
DIGIT | XDIGIT, // 2 [50]
|
||||
DIGIT | XDIGIT, // 3 [51]
|
||||
DIGIT | XDIGIT, // 4 [52]
|
||||
DIGIT | XDIGIT, // 5 [53]
|
||||
DIGIT | XDIGIT, // 6 [54]
|
||||
DIGIT | XDIGIT, // 7 [55]
|
||||
DIGIT | XDIGIT, // 8 [56]
|
||||
DIGIT | XDIGIT, // 9 [57]
|
||||
COLON, // : [58]
|
||||
SEMICOLON, // ; [59]
|
||||
LT, // < [60]
|
||||
EQ, // = [61]
|
||||
GT, // > [62]
|
||||
0, // ? [63]
|
||||
0, // @ [64]
|
||||
UPPER | XDIGIT, // A [65]
|
||||
UPPER | XDIGIT, // B [66]
|
||||
UPPER | XDIGIT, // C [67]
|
||||
UPPER | XDIGIT, // D [68]
|
||||
UPPER | XDIGIT, // E [69]
|
||||
UPPER | XDIGIT, // F [70]
|
||||
UPPER, // G [71]
|
||||
UPPER, // H [72]
|
||||
UPPER, // I [73]
|
||||
UPPER, // J [74]
|
||||
UPPER, // K [75]
|
||||
UPPER, // L [76]
|
||||
UPPER, // M [77]
|
||||
UPPER, // N [78]
|
||||
UPPER, // O [79]
|
||||
UPPER, // P [80]
|
||||
UPPER, // Q [81]
|
||||
UPPER, // R [82]
|
||||
UPPER, // S [83]
|
||||
UPPER, // T [84]
|
||||
UPPER, // U [85]
|
||||
UPPER, // V [86]
|
||||
UPPER, // W [87]
|
||||
UPPER, // X [88]
|
||||
UPPER, // Y [89]
|
||||
UPPER, // Z [90]
|
||||
0, // [ [91]
|
||||
RDIV, // \ [92]
|
||||
0, // ] [93]
|
||||
0, // ^ [94]
|
||||
UNDERLINE, // _ [95]
|
||||
BACKTICK, // ` [96]
|
||||
LOWER | XDIGIT, // a [97]
|
||||
LOWER | XDIGIT, // b [98]
|
||||
LOWER | XDIGIT, // c [99]
|
||||
LOWER | XDIGIT, // d [100]
|
||||
LOWER | XDIGIT, // e [101]
|
||||
LOWER | XDIGIT, // f [102]
|
||||
LOWER, // g [103]
|
||||
LOWER, // h [104]
|
||||
LOWER, // i [105]
|
||||
LOWER, // j [106]
|
||||
LOWER, // k [107]
|
||||
LOWER, // l [108]
|
||||
LOWER, // m [109]
|
||||
LCAP_N | LOWER, // n [110]
|
||||
LOWER, // o [111]
|
||||
LOWER, // p [112]
|
||||
LOWER, // q [113]
|
||||
LCAP_R | LOWER, // r [114]
|
||||
LOWER, // s [115]
|
||||
LCAP_T | LOWER, // t [116]
|
||||
LOWER, // u [117]
|
||||
LOWER, // v [118]
|
||||
LCAP_W | LOWER, // w [119]
|
||||
LOWER, // x [120]
|
||||
LOWER, // y [121]
|
||||
LOWER, // z [122]
|
||||
0, // { [123]
|
||||
OR, // | [124]
|
||||
0, // } [125]
|
||||
0, // ~ [126]
|
||||
0, // del [127]
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
func Is(typeMask uint32, c rune) bool {
|
||||
|
||||
if uint(c) < uint(len(table)) {
|
||||
return (typeMask & table[c]) != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsType(typeMask uint32, str string) bool {
|
||||
|
||||
if str == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range str {
|
||||
if !Is(typeMask, c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsTypeEx(typeFirst, typeNext uint32, str string) bool {
|
||||
|
||||
if str == "" {
|
||||
return false
|
||||
}
|
||||
for i, c := range str {
|
||||
if i > 0 {
|
||||
if !Is(typeNext, c) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !Is(typeFirst, c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsCSymbol(str string) bool {
|
||||
|
||||
return IsTypeEx(CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, str)
|
||||
}
|
||||
|
||||
func IsXmlSymbol(str string) bool {
|
||||
|
||||
return IsTypeEx(XMLSYMBOL_FIRST_CHAR, XMLSYMBOL_NEXT_CHAR, str)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
72
vendor/github.com/qiniu/x/ctype.v7/ctype_test.go
generated
vendored
Normal file
72
vendor/github.com/qiniu/x/ctype.v7/ctype_test.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package ctype
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
c rune
|
||||
mask uint32
|
||||
is bool
|
||||
}
|
||||
|
||||
type stringTestCase struct {
|
||||
str string
|
||||
maskFirst uint32
|
||||
maskNext uint32
|
||||
is bool
|
||||
}
|
||||
|
||||
var isCases = []testCase{
|
||||
{'-', DOMAIN_CHAR, true},
|
||||
{'.', DOMAIN_CHAR, true},
|
||||
{'_', DOMAIN_CHAR, false},
|
||||
{'+', DOMAIN_CHAR, true},
|
||||
{'a', DOMAIN_CHAR, true},
|
||||
{'A', DOMAIN_CHAR, true},
|
||||
{'0', DOMAIN_CHAR, true},
|
||||
{':', DOMAIN_CHAR, false},
|
||||
{'1', ALPHA, false},
|
||||
{'a', ALPHA, true},
|
||||
{'A', ALPHA, true},
|
||||
}
|
||||
|
||||
var strCases = []stringTestCase{
|
||||
{"", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, false},
|
||||
{"123", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, false},
|
||||
{"_", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, true},
|
||||
{"_123", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, true},
|
||||
{"x_123", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, true},
|
||||
{"x_", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, true},
|
||||
{"_x", CSYMBOL_FIRST_CHAR, CSYMBOL_NEXT_CHAR, true},
|
||||
|
||||
{"", CSYMBOL_FIRST_CHAR, CSYMBOL_FIRST_CHAR, false},
|
||||
{"x_123", CSYMBOL_FIRST_CHAR, CSYMBOL_FIRST_CHAR, false},
|
||||
{"x_", CSYMBOL_FIRST_CHAR, CSYMBOL_FIRST_CHAR, true},
|
||||
{"_x", CSYMBOL_FIRST_CHAR, CSYMBOL_FIRST_CHAR, true},
|
||||
{"_", CSYMBOL_FIRST_CHAR, CSYMBOL_FIRST_CHAR, true},
|
||||
}
|
||||
|
||||
func TestIs(t *testing.T) {
|
||||
for _, a := range isCases {
|
||||
f := Is(a.mask, a.c)
|
||||
if f != a.is {
|
||||
t.Fatal("case:", a, "result:", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTypeEx(t *testing.T) {
|
||||
for _, a := range strCases {
|
||||
f := IsTypeEx(a.maskFirst, a.maskNext, a.str)
|
||||
if f != a.is {
|
||||
t.Fatal("case:", a, "result:", f)
|
||||
}
|
||||
if a.maskFirst == a.maskNext {
|
||||
f = IsType(a.maskFirst, a.str)
|
||||
if f != a.is {
|
||||
t.Fatal("case:", a, "result:", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
vendor/github.com/qiniu/x/doc.go
generated
vendored
Normal file
9
vendor/github.com/qiniu/x/doc.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package x
|
||||
|
||||
import (
|
||||
_ "qiniupkg.com/x/bytes.v7"
|
||||
_ "qiniupkg.com/x/ctype.v7"
|
||||
_ "qiniupkg.com/x/rpc.v7"
|
||||
_ "qiniupkg.com/x/url.v7"
|
||||
)
|
||||
|
||||
146
vendor/github.com/qiniu/x/errors.v7/error_info.go
generated
vendored
Normal file
146
vendor/github.com/qiniu/x/errors.v7/error_info.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
prefix = " ==> "
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func New(msg string) error {
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type appendDetailer interface {
|
||||
AppendErrorDetail(b []byte) []byte
|
||||
}
|
||||
|
||||
func appendErrorDetail(b []byte, err error) []byte {
|
||||
if e, ok := err.(appendDetailer); ok {
|
||||
return e.AppendErrorDetail(b)
|
||||
}
|
||||
b = append(b, prefix...)
|
||||
return append(b, err.Error()...)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type errorDetailer interface {
|
||||
ErrorDetail() string
|
||||
}
|
||||
|
||||
func Detail(err error) string {
|
||||
if e, ok := err.(errorDetailer); ok {
|
||||
return e.ErrorDetail()
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type summaryErr interface {
|
||||
SummaryErr() error
|
||||
}
|
||||
|
||||
func Err(err error) error {
|
||||
if e, ok := err.(summaryErr); ok {
|
||||
return e.SummaryErr()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type ErrorInfo struct {
|
||||
err error
|
||||
why error
|
||||
cmd []interface{}
|
||||
pc uintptr
|
||||
}
|
||||
|
||||
func shortFile(file string) string {
|
||||
pos := strings.LastIndex(file, "/src/")
|
||||
if pos != -1 {
|
||||
return file[pos+5:]
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func Info(err error, cmd ...interface{}) *ErrorInfo {
|
||||
pc, _, _, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
pc = 0
|
||||
}
|
||||
return &ErrorInfo{cmd: cmd, err: Err(err), pc: pc}
|
||||
}
|
||||
|
||||
func InfoEx(calldepth int, err error, cmd ...interface{}) *ErrorInfo {
|
||||
pc, _, _, ok := runtime.Caller(calldepth+1)
|
||||
if !ok {
|
||||
pc = 0
|
||||
}
|
||||
return &ErrorInfo{cmd: cmd, err: Err(err), pc: pc}
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) Detail(err error) *ErrorInfo {
|
||||
r.why = err
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) NestedObject() interface{} {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) SummaryErr() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) Error() string {
|
||||
return r.err.Error()
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) ErrorDetail() string {
|
||||
b := make([]byte, 1, 64)
|
||||
b[0] = '\n'
|
||||
b = r.AppendErrorDetail(b)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) AppendErrorDetail(b []byte) []byte {
|
||||
b = append(b, prefix...)
|
||||
if r.pc != 0 {
|
||||
f := runtime.FuncForPC(r.pc)
|
||||
if f != nil {
|
||||
file, line := f.FileLine(r.pc)
|
||||
b = append(b, shortFile(file)...)
|
||||
b = append(b, ':')
|
||||
b = append(b, strconv.Itoa(line)...)
|
||||
b = append(b, ':', ' ')
|
||||
|
||||
fnName := f.Name()
|
||||
fnName = fnName[strings.LastIndex(fnName, "/")+1:]
|
||||
fnName = fnName[strings.Index(fnName, ".")+1:]
|
||||
b = append(b, '[')
|
||||
b = append(b, fnName...)
|
||||
b = append(b, ']', ' ')
|
||||
}
|
||||
}
|
||||
b = append(b, Detail(r.err)...)
|
||||
b = append(b, ' ', '~', ' ')
|
||||
b = append(b, fmt.Sprintln(r.cmd...)...)
|
||||
if r.why != nil {
|
||||
b = appendErrorDetail(b, r.why)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
30
vendor/github.com/qiniu/x/errors.v7/error_info_test.go
generated
vendored
Normal file
30
vendor/github.com/qiniu/x/errors.v7/error_info_test.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func MysqlError(err error, cmd ...interface{}) error {
|
||||
|
||||
return InfoEx(1, syscall.EINVAL, cmd...).Detail(err)
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) makeError() error {
|
||||
|
||||
err := errors.New("detail error")
|
||||
return MysqlError(err, "do sth failed")
|
||||
}
|
||||
|
||||
func TestErrorsInfo(t *testing.T) {
|
||||
|
||||
err := new(ErrorInfo).makeError()
|
||||
msg := Detail(err)
|
||||
if msg != `
|
||||
==> qiniupkg.com/x/errors.v7/error_info_test.go:17: [(*ErrorInfo).makeError] invalid argument ~ do sth failed
|
||||
==> detail error` {
|
||||
t.Fatal("TestErrorsInfo failed")
|
||||
}
|
||||
}
|
||||
|
||||
19
vendor/github.com/qiniu/x/jsonutil.v7/unmarshal_string.go
generated
vendored
Normal file
19
vendor/github.com/qiniu/x/jsonutil.v7/unmarshal_string.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package jsonutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func Unmarshal(data string, v interface{}) error {
|
||||
|
||||
sh := *(*reflect.StringHeader)(unsafe.Pointer(&data))
|
||||
arr := (*[1<<30]byte)(unsafe.Pointer(sh.Data))
|
||||
return json.Unmarshal(arr[:sh.Len], v)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
19
vendor/github.com/qiniu/x/jsonutil.v7/unmarshal_test.go
generated
vendored
Normal file
19
vendor/github.com/qiniu/x/jsonutil.v7/unmarshal_test.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package jsonutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
|
||||
var ret struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
err := Unmarshal(`{"id": "123"}`, &ret)
|
||||
if err != nil {
|
||||
t.Fatal("Unmarshal failed:", err)
|
||||
}
|
||||
if ret.Id != "123" {
|
||||
t.Fatal("Unmarshal uncorrect:", ret.Id)
|
||||
}
|
||||
}
|
||||
4
vendor/github.com/qiniu/x/log.v7/README.md
generated
vendored
Normal file
4
vendor/github.com/qiniu/x/log.v7/README.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
log
|
||||
===
|
||||
|
||||
Extension module of golang logging
|
||||
521
vendor/github.com/qiniu/x/log.v7/logext.go
generated
vendored
Normal file
521
vendor/github.com/qiniu/x/log.v7/logext.go
generated
vendored
Normal file
@@ -0,0 +1,521 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These flags define which text to prefix to each log entry generated by the Logger.
|
||||
const (
|
||||
// Bits or'ed together to control what's printed. There is no control over the
|
||||
// order they appear (the order listed here) or the format they present (as
|
||||
// described in the comments). A colon appears after these items:
|
||||
// 2009/0123 01:23:23.123123 /a/b/c/d.go:23: message
|
||||
Ldate = 1 << iota // the date: 2009/0123
|
||||
Ltime // the time: 01:23:23
|
||||
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
|
||||
Llongfile // full file name and line number: /a/b/c/d.go:23
|
||||
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
|
||||
Lmodule // module name
|
||||
Llevel // level: 0(Debug), 1(Info), 2(Warn), 3(Error), 4(Panic), 5(Fatal)
|
||||
LstdFlags = Ldate | Ltime | Lmicroseconds // initial values for the standard logger
|
||||
Ldefault = Lmodule | Llevel | Lshortfile | LstdFlags
|
||||
) // [prefix][time][level][module][shortfile|longfile]
|
||||
|
||||
const (
|
||||
Ldebug = iota
|
||||
Linfo
|
||||
Lwarn
|
||||
Lerror
|
||||
Lpanic
|
||||
Lfatal
|
||||
)
|
||||
|
||||
var levels = []string{
|
||||
"[DEBUG]",
|
||||
"[INFO]",
|
||||
"[WARN]",
|
||||
"[ERROR]",
|
||||
"[PANIC]",
|
||||
"[FATAL]",
|
||||
}
|
||||
|
||||
// A Logger represents an active logging object that generates lines of
|
||||
// output to an io.Writer. Each logging operation makes a single call to
|
||||
// the Writer's Write method. A Logger can be used simultaneously from
|
||||
// multiple goroutines; it guarantees to serialize access to the Writer.
|
||||
type Logger struct {
|
||||
mu sync.Mutex // ensures atomic writes; protects the following fields
|
||||
prefix string // prefix to write at beginning of each line
|
||||
flag int // properties
|
||||
Level int // debug level
|
||||
out io.Writer // destination for output
|
||||
buf bytes.Buffer // for accumulating text to write
|
||||
levelStats [6]int64
|
||||
}
|
||||
|
||||
// New creates a new Logger.
|
||||
// The out variable sets the destination to which log data will be written.
|
||||
// The prefix appears at the beginning of each generated log line.
|
||||
// The flag argument defines the logging properties.
|
||||
func New(out io.Writer, prefix string, flag int) *Logger {
|
||||
return &Logger{out: out, prefix: prefix, Level: 1, flag: flag}
|
||||
}
|
||||
|
||||
var Std = New(os.Stderr, "", Ldefault)
|
||||
|
||||
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
||||
// Knows the buffer has capacity.
|
||||
func itoa(buf *bytes.Buffer, i int, wid int) {
|
||||
var u uint = uint(i)
|
||||
if u == 0 && wid <= 1 {
|
||||
buf.WriteByte('0')
|
||||
return
|
||||
}
|
||||
|
||||
// Assemble decimal in reverse order.
|
||||
var b [32]byte
|
||||
bp := len(b)
|
||||
for ; u > 0 || wid > 0; u /= 10 {
|
||||
bp--
|
||||
wid--
|
||||
b[bp] = byte(u%10) + '0'
|
||||
}
|
||||
|
||||
// avoid slicing b to avoid an allocation.
|
||||
for bp < len(b) {
|
||||
buf.WriteByte(b[bp])
|
||||
bp++
|
||||
}
|
||||
}
|
||||
|
||||
func shortFile(file string, flag int) string {
|
||||
sep := "/"
|
||||
if (flag & Lmodule) != 0 {
|
||||
sep = "/src/"
|
||||
}
|
||||
pos := strings.LastIndex(file, sep)
|
||||
if pos != -1 {
|
||||
return file[pos+5:]
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time, file string, line int, lvl int, reqId string) {
|
||||
if l.prefix != "" {
|
||||
buf.WriteString(l.prefix)
|
||||
}
|
||||
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
|
||||
if l.flag&Ldate != 0 {
|
||||
year, month, day := t.Date()
|
||||
itoa(buf, year, 4)
|
||||
buf.WriteByte('/')
|
||||
itoa(buf, int(month), 2)
|
||||
buf.WriteByte('/')
|
||||
itoa(buf, day, 2)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if l.flag&(Ltime|Lmicroseconds) != 0 {
|
||||
hour, min, sec := t.Clock()
|
||||
itoa(buf, hour, 2)
|
||||
buf.WriteByte(':')
|
||||
itoa(buf, min, 2)
|
||||
buf.WriteByte(':')
|
||||
itoa(buf, sec, 2)
|
||||
if l.flag&Lmicroseconds != 0 {
|
||||
buf.WriteByte('.')
|
||||
itoa(buf, t.Nanosecond()/1e3, 6)
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
if reqId != "" {
|
||||
buf.WriteByte('[')
|
||||
buf.WriteString(reqId)
|
||||
buf.WriteByte(']')
|
||||
}
|
||||
if l.flag&Llevel != 0 {
|
||||
buf.WriteString(levels[lvl])
|
||||
}
|
||||
if l.flag&(Lshortfile|Llongfile) != 0 {
|
||||
if l.flag&Lshortfile != 0 {
|
||||
file = shortFile(file, l.flag)
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(file)
|
||||
buf.WriteByte(':')
|
||||
itoa(buf, line, -1)
|
||||
buf.WriteString(": ")
|
||||
}
|
||||
}
|
||||
|
||||
// Output writes the output for a logging event. The string s contains
|
||||
// the text to print after the prefix specified by the flags of the
|
||||
// Logger. A newline is appended if the last character of s is not
|
||||
// already a newline. Calldepth is used to recover the PC and is
|
||||
// provided for generality, although at the moment on all pre-defined
|
||||
// paths it will be 2.
|
||||
func (l *Logger) Output(reqId string, lvl int, calldepth int, s string) error {
|
||||
if lvl < l.Level {
|
||||
return nil
|
||||
}
|
||||
now := time.Now() // get this early.
|
||||
var file string
|
||||
var line int
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.flag&(Lshortfile|Llongfile|Lmodule) != 0 {
|
||||
// release lock while getting caller info - it's expensive.
|
||||
l.mu.Unlock()
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(calldepth)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
l.mu.Lock()
|
||||
}
|
||||
l.levelStats[lvl]++
|
||||
l.buf.Reset()
|
||||
l.formatHeader(&l.buf, now, file, line, lvl, reqId)
|
||||
l.buf.WriteString(s)
|
||||
if len(s) > 0 && s[len(s)-1] != '\n' {
|
||||
l.buf.WriteByte('\n')
|
||||
}
|
||||
_, err := l.out.Write(l.buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Printf calls l.Output to print to the logger.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func (l *Logger) Printf(format string, v ...interface{}) {
|
||||
l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Print calls l.Output to print to the logger.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func (l *Logger) Print(v ...interface{}) { l.Output("", Linfo, 2, fmt.Sprint(v...)) }
|
||||
|
||||
// Println calls l.Output to print to the logger.
|
||||
// Arguments are handled in the manner of fmt.Println.
|
||||
func (l *Logger) Println(v ...interface{}) { l.Output("", Linfo, 2, fmt.Sprintln(v...)) }
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
if Ldebug < l.Level {
|
||||
return
|
||||
}
|
||||
l.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(v ...interface{}) {
|
||||
if Ldebug < l.Level {
|
||||
return
|
||||
}
|
||||
l.Output("", Ldebug, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||
if Linfo < l.Level {
|
||||
return
|
||||
}
|
||||
l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Info(v ...interface{}) {
|
||||
if Linfo < l.Level {
|
||||
return
|
||||
}
|
||||
l.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
||||
l.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(v ...interface{}) { l.Output("", Lwarn, 2, fmt.Sprintln(v...)) }
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
l.Output("", Lerror, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Error(v ...interface{}) { l.Output("", Lerror, 2, fmt.Sprintln(v...)) }
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.Output("", Lfatal, 2, fmt.Sprint(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).
|
||||
func (l *Logger) Fatalln(v ...interface{}) {
|
||||
l.Output("", Lfatal, 2, fmt.Sprintln(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Panic is equivalent to l.Print() followed by a call to panic().
|
||||
func (l *Logger) Panic(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
l.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicf is equivalent to l.Printf() followed by a call to panic().
|
||||
func (l *Logger) Panicf(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
l.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicln is equivalent to l.Println() followed by a call to panic().
|
||||
func (l *Logger) Panicln(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
l.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Stack(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
s += "\n"
|
||||
buf := make([]byte, 1024*1024)
|
||||
n := runtime.Stack(buf, true)
|
||||
s += string(buf[:n])
|
||||
s += "\n"
|
||||
l.Output("", Lerror, 2, s)
|
||||
}
|
||||
|
||||
func (l *Logger) SingleStack(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
s += "\n"
|
||||
buf := make([]byte, 1024*1024)
|
||||
n := runtime.Stack(buf, false)
|
||||
s += string(buf[:n])
|
||||
s += "\n"
|
||||
l.Output("", Lerror, 2, s)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Stat() (stats []int64) {
|
||||
l.mu.Lock()
|
||||
v := l.levelStats
|
||||
l.mu.Unlock()
|
||||
return v[:]
|
||||
}
|
||||
|
||||
// Flags returns the output flags for the logger.
|
||||
func (l *Logger) Flags() int {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.flag
|
||||
}
|
||||
|
||||
// SetFlags sets the output flags for the logger.
|
||||
func (l *Logger) SetFlags(flag int) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.flag = flag
|
||||
}
|
||||
|
||||
// Prefix returns the output prefix for the logger.
|
||||
func (l *Logger) Prefix() string {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.prefix
|
||||
}
|
||||
|
||||
// SetPrefix sets the output prefix for the logger.
|
||||
func (l *Logger) SetPrefix(prefix string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.prefix = prefix
|
||||
}
|
||||
|
||||
// SetOutputLevel sets the output level for the logger.
|
||||
func (l *Logger) SetOutputLevel(lvl int) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.Level = lvl
|
||||
}
|
||||
|
||||
// SetOutput sets the output destination for the standard logger.
|
||||
func SetOutput(w io.Writer) {
|
||||
Std.mu.Lock()
|
||||
defer Std.mu.Unlock()
|
||||
Std.out = w
|
||||
}
|
||||
|
||||
// Flags returns the output flags for the standard logger.
|
||||
func Flags() int {
|
||||
return Std.Flags()
|
||||
}
|
||||
|
||||
// SetFlags sets the output flags for the standard logger.
|
||||
func SetFlags(flag int) {
|
||||
Std.SetFlags(flag)
|
||||
}
|
||||
|
||||
// Prefix returns the output prefix for the standard logger.
|
||||
func Prefix() string {
|
||||
return Std.Prefix()
|
||||
}
|
||||
|
||||
// SetPrefix sets the output prefix for the standard logger.
|
||||
func SetPrefix(prefix string) {
|
||||
Std.SetPrefix(prefix)
|
||||
}
|
||||
|
||||
func SetOutputLevel(lvl int) {
|
||||
Std.SetOutputLevel(lvl)
|
||||
}
|
||||
|
||||
func GetOutputLevel() int {
|
||||
return Std.Level
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Print calls Output to print to the standard logger.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func Print(v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Printf calls Output to print to the standard logger.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func Printf(format string, v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Println calls Output to print to the standard logger.
|
||||
// Arguments are handled in the manner of fmt.Println.
|
||||
func Println(v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
if Ldebug < Std.Level {
|
||||
return
|
||||
}
|
||||
Std.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
if Ldebug < Std.Level {
|
||||
return
|
||||
}
|
||||
Std.Output("", Ldebug, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Infof(format string, v ...interface{}) {
|
||||
if Linfo < Std.Level {
|
||||
return
|
||||
}
|
||||
Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Info(v ...interface{}) {
|
||||
if Linfo < Std.Level {
|
||||
return
|
||||
}
|
||||
Std.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Warnf(format string, v ...interface{}) {
|
||||
Std.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Warn(v ...interface{}) { Std.Output("", Lwarn, 2, fmt.Sprintln(v...)) }
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
Std.Output("", Lerror, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Error(v ...interface{}) { Std.Output("", Lerror, 2, fmt.Sprintln(v...)) }
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
|
||||
func Fatal(v ...interface{}) {
|
||||
Std.Output("", Lfatal, 2, fmt.Sprint(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
Std.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
|
||||
func Fatalln(v ...interface{}) {
|
||||
Std.Output("", Lfatal, 2, fmt.Sprintln(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Panic is equivalent to Print() followed by a call to panic().
|
||||
func Panic(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
Std.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicf is equivalent to Printf() followed by a call to panic().
|
||||
func Panicf(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
Std.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicln is equivalent to Println() followed by a call to panic().
|
||||
func Panicln(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
Std.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Stack(v ...interface{}) {
|
||||
Std.Stack(v...)
|
||||
}
|
||||
|
||||
func SingleStack(v ...interface{}) {
|
||||
Std.SingleStack(v...)
|
||||
}
|
||||
79
vendor/github.com/qiniu/x/log.v7/logext_test.go
generated
vendored
Normal file
79
vendor/github.com/qiniu/x/log.v7/logext_test.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
// keep Std clean
|
||||
std := Std
|
||||
SetOutputLevel(Ldebug)
|
||||
|
||||
Debugf("Debug: foo\n")
|
||||
Debug("Debug: foo")
|
||||
|
||||
Infof("Info: foo\n")
|
||||
Info("Info: foo")
|
||||
|
||||
Warnf("Warn: foo\n")
|
||||
Warn("Warn: foo")
|
||||
|
||||
Errorf("Error: foo\n")
|
||||
Error("Error: foo")
|
||||
|
||||
SetOutputLevel(Linfo)
|
||||
|
||||
Debugf("Debug: foo\n")
|
||||
Debug("Debug: foo")
|
||||
|
||||
Infof("Info: foo\n")
|
||||
Info("Info: foo")
|
||||
|
||||
Warnf("Warn: foo\n")
|
||||
Warn("Warn: foo")
|
||||
|
||||
Errorf("Error: foo\n")
|
||||
Error("Error: foo")
|
||||
|
||||
Std = std
|
||||
}
|
||||
|
||||
func TestLog_Time(t *testing.T) {
|
||||
// keep Std clean
|
||||
std := Std
|
||||
out := bytes.Buffer{}
|
||||
Std = New(&out, Std.Prefix(), Std.Flags())
|
||||
|
||||
assert.Equal(t, Std.Level, Linfo)
|
||||
Info("test")
|
||||
outStr := out.String()
|
||||
assert.True(t, regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}.\d{6}$`).MatchString(outStr[:26]))
|
||||
assert.Equal(t, outStr[26:], " [INFO] qiniupkg.com/x/log.v7/logext_test.go:53: test\n")
|
||||
|
||||
Std = std
|
||||
}
|
||||
|
||||
func TestLog_Level(t *testing.T) {
|
||||
// keep Std clean
|
||||
std := Std
|
||||
|
||||
out := bytes.Buffer{}
|
||||
Std = New(&out, Std.Prefix(), Std.Flags())
|
||||
SetOutputLevel(Lwarn)
|
||||
|
||||
assert.Equal(t, Std.Level, Lwarn)
|
||||
Debug("test")
|
||||
assert.Equal(t, out.String(), "")
|
||||
Info("test")
|
||||
assert.Equal(t, out.String(), "")
|
||||
Warn("test")
|
||||
outStr := out.String()
|
||||
assert.Equal(t, outStr[26:], " [WARN] qiniupkg.com/x/log.v7/logext_test.go:74: test\n")
|
||||
|
||||
Std = std
|
||||
}
|
||||
110
vendor/github.com/qiniu/x/mockhttp.v7/mockhttp.go
generated
vendored
Normal file
110
vendor/github.com/qiniu/x/mockhttp.v7/mockhttp.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package mockhttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
|
||||
"qiniupkg.com/x/log.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServerNotFound = errors.New("server not found")
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type mockServerRequestBody struct {
|
||||
reader io.Reader
|
||||
closeSignal bool
|
||||
}
|
||||
|
||||
func (r *mockServerRequestBody) Read(p []byte) (int, error) {
|
||||
if r.closeSignal || r.reader == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return r.reader.Read(p)
|
||||
}
|
||||
|
||||
func (r *mockServerRequestBody) Close() error {
|
||||
r.closeSignal = true
|
||||
if c, ok := r.reader.(io.Closer); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// type Transport
|
||||
|
||||
type Transport struct {
|
||||
route map[string]http.Handler
|
||||
}
|
||||
|
||||
func NewTransport() *Transport {
|
||||
|
||||
return &Transport{
|
||||
route: make(map[string]http.Handler),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Transport) ListenAndServe(host string, h http.Handler) {
|
||||
|
||||
if h == nil {
|
||||
h = http.DefaultServeMux
|
||||
}
|
||||
p.route[host] = h
|
||||
}
|
||||
|
||||
func (p *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
|
||||
h := p.route[req.URL.Host]
|
||||
if h == nil {
|
||||
log.Warn("Server not found:", req.Host)
|
||||
return nil, ErrServerNotFound
|
||||
}
|
||||
|
||||
cp := *req
|
||||
cp.URL.Scheme = ""
|
||||
cp.URL.Host = ""
|
||||
cp.RemoteAddr = "127.0.0.1:8000"
|
||||
cp.Body = &mockServerRequestBody{req.Body, false}
|
||||
req = &cp
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
req.Body.Close()
|
||||
|
||||
ctlen := int64(-1)
|
||||
if v := rw.HeaderMap.Get("Content-Length"); v != "" {
|
||||
ctlen, _ = strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Status: "",
|
||||
StatusCode: rw.Code,
|
||||
Header: rw.HeaderMap,
|
||||
Body: ioutil.NopCloser(rw.Body),
|
||||
ContentLength: ctlen,
|
||||
TransferEncoding: nil,
|
||||
Close: false,
|
||||
Trailer: nil,
|
||||
Request: req,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
var DefaultTransport = NewTransport()
|
||||
var DefaultClient = &http.Client{Transport: DefaultTransport}
|
||||
|
||||
func ListenAndServe(host string, h http.Handler) {
|
||||
|
||||
DefaultTransport.ListenAndServe(host, h)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
114
vendor/github.com/qiniu/x/mockhttp.v7/mockhttp_test.go
generated
vendored
Normal file
114
vendor/github.com/qiniu/x/mockhttp.v7/mockhttp_test.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package mockhttp_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"qiniupkg.com/x/mockhttp.v7"
|
||||
"qiniupkg.com/x/rpc.v7"
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func reply(w http.ResponseWriter, code int, data interface{}) {
|
||||
|
||||
msg, _ := json.Marshal(data)
|
||||
h := w.Header()
|
||||
h.Set("Content-Length", strconv.Itoa(len(msg)))
|
||||
h.Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
w.Write(msg)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type FooRet struct {
|
||||
A int `json:"a"`
|
||||
B string `json:"b"`
|
||||
C string `json:"c"`
|
||||
}
|
||||
|
||||
type HandleRet map[string]string
|
||||
|
||||
type FooServer struct{}
|
||||
|
||||
func (p *FooServer) foo(w http.ResponseWriter, req *http.Request) {
|
||||
reply(w, 200, &FooRet{1, req.Host, req.URL.Path})
|
||||
}
|
||||
|
||||
func (p *FooServer) handle(w http.ResponseWriter, req *http.Request) {
|
||||
reply(w, 200, HandleRet{"foo": "1", "bar": "2"})
|
||||
}
|
||||
|
||||
func (p *FooServer) postDump(w http.ResponseWriter, req *http.Request) {
|
||||
req.Body.Close()
|
||||
io.Copy(w, req.Body)
|
||||
}
|
||||
|
||||
func (p *FooServer) RegisterHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/foo", func(w http.ResponseWriter, req *http.Request) { p.foo(w, req) })
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { p.handle(w, req) })
|
||||
mux.HandleFunc("/dump", func(w http.ResponseWriter, req *http.Request) { p.postDump(w, req) })
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
|
||||
server := new(FooServer)
|
||||
server.RegisterHandlers(http.DefaultServeMux)
|
||||
|
||||
mockhttp.ListenAndServe("foo.com", nil)
|
||||
|
||||
c := rpc.Client{mockhttp.DefaultClient}
|
||||
{
|
||||
var foo FooRet
|
||||
err := c.Call(nil, &foo, "POST", "http://foo.com/foo")
|
||||
if err != nil {
|
||||
t.Fatal("call foo failed:", err)
|
||||
}
|
||||
if foo.A != 1 || foo.B != "foo.com" || foo.C != "/foo" {
|
||||
t.Fatal("call foo: invalid ret")
|
||||
}
|
||||
fmt.Println(foo)
|
||||
}
|
||||
{
|
||||
var ret map[string]string
|
||||
err := c.Call(nil, &ret, "POST", "http://foo.com/bar")
|
||||
if err != nil {
|
||||
t.Fatal("call foo failed:", err)
|
||||
}
|
||||
if ret["foo"] != "1" || ret["bar"] != "2" {
|
||||
t.Fatal("call bar: invalid ret")
|
||||
}
|
||||
fmt.Println(ret)
|
||||
}
|
||||
{
|
||||
resp, err := c.Post("http://foo.com/dump", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal("post foo failed:", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
resp, err = c.Post("http://foo.com/dump", "", strings.NewReader("abc"))
|
||||
if err != nil {
|
||||
t.Fatal("post foo failed:", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal("ioutil.ReadAll:", err)
|
||||
}
|
||||
if len(b) != 0 {
|
||||
t.Fatal("body should be empty:", string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
52
vendor/github.com/qiniu/x/reqid.v7/reqid.go
generated
vendored
Normal file
52
vendor/github.com/qiniu/x/reqid.v7/reqid.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package reqid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
. "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
var pid = uint32(time.Now().UnixNano() % 4294967291)
|
||||
|
||||
func genReqId() string {
|
||||
var b [12]byte
|
||||
binary.LittleEndian.PutUint32(b[:], pid)
|
||||
binary.LittleEndian.PutUint64(b[4:], uint64(time.Now().UnixNano()))
|
||||
return base64.URLEncoding.EncodeToString(b[:])
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type key int // key is unexported and used for Context
|
||||
|
||||
const (
|
||||
reqidKey key = 0
|
||||
)
|
||||
|
||||
func NewContext(ctx Context, reqid string) Context {
|
||||
return WithValue(ctx, reqidKey, reqid)
|
||||
}
|
||||
|
||||
func NewContextWith(ctx Context, w http.ResponseWriter, req *http.Request) Context {
|
||||
reqid := req.Header.Get("X-Reqid")
|
||||
if reqid == "" {
|
||||
reqid = genReqId()
|
||||
req.Header.Set("X-Reqid", reqid)
|
||||
}
|
||||
h := w.Header()
|
||||
h.Set("X-Reqid", reqid)
|
||||
return WithValue(ctx, reqidKey, reqid)
|
||||
}
|
||||
|
||||
func FromContext(ctx Context) (reqid string, ok bool) {
|
||||
reqid, ok = ctx.Value(reqidKey).(string)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
107
vendor/github.com/qiniu/x/rpc.v7/gob/gobrpc_client.go
generated
vendored
Normal file
107
vendor/github.com/qiniu/x/rpc.v7/gob/gobrpc_client.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
package gob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"qiniupkg.com/x/rpc.v7"
|
||||
|
||||
. "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Register(value interface{}) {
|
||||
|
||||
gob.Register(value)
|
||||
}
|
||||
|
||||
func RegisterName(name string, value interface{}) {
|
||||
|
||||
gob.RegisterName(name, value)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func ResponseError(resp *http.Response) (err error) {
|
||||
|
||||
e := &rpc.ErrorInfo{
|
||||
Reqid: resp.Header.Get("X-Reqid"),
|
||||
Code: resp.StatusCode,
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
e.Err = resp.Header.Get("X-Err")
|
||||
if errno := resp.Header.Get("X-Errno"); errno != "" {
|
||||
v, err2 := strconv.ParseInt(errno, 10, 32)
|
||||
if err2 != nil {
|
||||
e.Err = err2.Error()
|
||||
}
|
||||
e.Errno = int(v)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func CallRet(ctx Context, ret interface{}, resp *http.Response) (err error) {
|
||||
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode/100 == 2 {
|
||||
if ret != nil && resp.ContentLength != 0 {
|
||||
err = gob.NewDecoder(resp.Body).Decode(ret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ResponseError(resp)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type Client struct {
|
||||
rpc.Client
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultClient = Client{rpc.DefaultClient}
|
||||
)
|
||||
|
||||
func (r Client) Call(
|
||||
ctx Context, ret interface{}, method, url1 string) (err error) {
|
||||
|
||||
resp, err := r.DoRequestWith(ctx, method, url1, "application/gob", nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
func (r Client) CallWithGob(
|
||||
ctx Context, ret interface{}, method, url1 string, params interface{}) (err error) {
|
||||
|
||||
var b bytes.Buffer
|
||||
err = gob.NewEncoder(&b).Encode(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := r.DoRequestWith(ctx, method, url1, "application/gob", &b, b.Len())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
344
vendor/github.com/qiniu/x/rpc.v7/rpc_client.go
generated
vendored
Normal file
344
vendor/github.com/qiniu/x/rpc.v7/rpc_client.go
generated
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"qiniupkg.com/x/reqid.v7"
|
||||
|
||||
. "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
UserAgent = "Golang qiniu/rpc package"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRequestURL = errors.New("invalid request url")
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type Client struct {
|
||||
*http.Client
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultClient = Client{&http.Client{Transport: http.DefaultTransport}}
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func newRequest(method, url1 string, body io.Reader) (req *http.Request, err error) {
|
||||
|
||||
var host string
|
||||
|
||||
// url1 = "-H <Host> http://<ip>[:<port>]/<path>"
|
||||
//
|
||||
if strings.HasPrefix(url1, "-H") {
|
||||
url2 := strings.TrimLeft(url1[2:], " \t")
|
||||
pos := strings.Index(url2, " ")
|
||||
if pos <= 0 {
|
||||
return nil, ErrInvalidRequestURL
|
||||
}
|
||||
host = url2[:pos]
|
||||
url1 = strings.TrimLeft(url2[pos+1:], " \t")
|
||||
}
|
||||
|
||||
req, err = http.NewRequest(method, url1, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if host != "" {
|
||||
req.Host = host
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r Client) DoRequest(ctx Context, method, url string) (resp *http.Response, err error) {
|
||||
|
||||
req, err := newRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.Do(ctx, req)
|
||||
}
|
||||
|
||||
func (r Client) DoRequestWith(
|
||||
ctx Context, method, url1 string,
|
||||
bodyType string, body io.Reader, bodyLength int) (resp *http.Response, err error) {
|
||||
|
||||
req, err := newRequest(method, url1, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", bodyType)
|
||||
req.ContentLength = int64(bodyLength)
|
||||
return r.Do(ctx, req)
|
||||
}
|
||||
|
||||
func (r Client) DoRequestWith64(
|
||||
ctx Context, method, url1 string,
|
||||
bodyType string, body io.Reader, bodyLength int64) (resp *http.Response, err error) {
|
||||
|
||||
req, err := newRequest(method, url1, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", bodyType)
|
||||
req.ContentLength = bodyLength
|
||||
return r.Do(ctx, req)
|
||||
}
|
||||
|
||||
func (r Client) DoRequestWithForm(
|
||||
ctx Context, method, url1 string, data map[string][]string) (resp *http.Response, err error) {
|
||||
|
||||
msg := url.Values(data).Encode()
|
||||
if method == "GET" || method == "HEAD" || method == "DELETE" {
|
||||
if strings.ContainsRune(url1, '?') {
|
||||
url1 += "&"
|
||||
} else {
|
||||
url1 += "?"
|
||||
}
|
||||
return r.DoRequest(ctx, method, url1 + msg)
|
||||
}
|
||||
return r.DoRequestWith(
|
||||
ctx, method, url1, "application/x-www-form-urlencoded", strings.NewReader(msg), len(msg))
|
||||
}
|
||||
|
||||
func (r Client) DoRequestWithJson(
|
||||
ctx Context, method, url1 string, data interface{}) (resp *http.Response, err error) {
|
||||
|
||||
msg, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.DoRequestWith(
|
||||
ctx, method, url1, "application/json", bytes.NewReader(msg), len(msg))
|
||||
}
|
||||
|
||||
func (r Client) Do(ctx Context, req *http.Request) (resp *http.Response, err error) {
|
||||
|
||||
if ctx == nil {
|
||||
ctx = Background()
|
||||
}
|
||||
|
||||
if reqid, ok := reqid.FromContext(ctx); ok {
|
||||
req.Header.Set("X-Reqid", reqid)
|
||||
}
|
||||
|
||||
if _, ok := req.Header["User-Agent"]; !ok {
|
||||
req.Header.Set("User-Agent", UserAgent)
|
||||
}
|
||||
|
||||
transport := r.Transport // don't change r.Transport
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
// avoid cancel() is called before Do(req), but isn't accurate
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if tr, ok := getRequestCanceler(transport); ok { // support CancelRequest
|
||||
reqC := make(chan bool, 1)
|
||||
go func() {
|
||||
resp, err = r.Client.Do(req)
|
||||
reqC <- true
|
||||
}()
|
||||
select {
|
||||
case <-reqC:
|
||||
case <-ctx.Done():
|
||||
tr.CancelRequest(req)
|
||||
<-reqC
|
||||
err = ctx.Err()
|
||||
}
|
||||
} else {
|
||||
resp, err = r.Client.Do(req)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type ErrorInfo struct {
|
||||
Err string `json:"error,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Reqid string `json:"reqid,omitempty"`
|
||||
Errno int `json:"errno,omitempty"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) ErrorDetail() string {
|
||||
|
||||
msg, _ := json.Marshal(r)
|
||||
return string(msg)
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) Error() string {
|
||||
|
||||
return r.Err
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) RpcError() (code, errno int, key, err string) {
|
||||
|
||||
return r.Code, r.Errno, r.Key, r.Err
|
||||
}
|
||||
|
||||
func (r *ErrorInfo) HttpCode() int {
|
||||
|
||||
return r.Code
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func parseError(e *ErrorInfo, r io.Reader) {
|
||||
|
||||
body, err1 := ioutil.ReadAll(r)
|
||||
if err1 != nil {
|
||||
e.Err = err1.Error()
|
||||
return
|
||||
}
|
||||
|
||||
var ret struct {
|
||||
Err string `json:"error"`
|
||||
Key string `json:"key"`
|
||||
Errno int `json:"errno"`
|
||||
}
|
||||
if json.Unmarshal(body, &ret) == nil && ret.Err != "" {
|
||||
// qiniu error msg style returns here
|
||||
e.Err, e.Key, e.Errno = ret.Err, ret.Key, ret.Errno
|
||||
return
|
||||
}
|
||||
e.Err = string(body)
|
||||
}
|
||||
|
||||
func ResponseError(resp *http.Response) (err error) {
|
||||
|
||||
e := &ErrorInfo{
|
||||
Reqid: resp.Header.Get("X-Reqid"),
|
||||
Code: resp.StatusCode,
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
if resp.ContentLength != 0 {
|
||||
ct, ok := resp.Header["Content-Type"]
|
||||
if ok && strings.HasPrefix(ct[0], "application/json") {
|
||||
parseError(e, resp.Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func CallRet(ctx Context, ret interface{}, resp *http.Response) (err error) {
|
||||
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode/100 == 2 {
|
||||
if ret != nil && resp.ContentLength != 0 {
|
||||
err = json.NewDecoder(resp.Body).Decode(ret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ResponseError(resp)
|
||||
}
|
||||
|
||||
func (r Client) CallWithForm(
|
||||
ctx Context, ret interface{}, method, url1 string, param map[string][]string) (err error) {
|
||||
|
||||
resp, err := r.DoRequestWithForm(ctx, method, url1, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
func (r Client) CallWithJson(
|
||||
ctx Context, ret interface{}, method, url1 string, param interface{}) (err error) {
|
||||
|
||||
resp, err := r.DoRequestWithJson(ctx, method, url1, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
func (r Client) CallWith(
|
||||
ctx Context, ret interface{}, method, url1, bodyType string, body io.Reader, bodyLength int) (err error) {
|
||||
|
||||
resp, err := r.DoRequestWith(ctx, method, url1, bodyType, body, bodyLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
func (r Client) CallWith64(
|
||||
ctx Context, ret interface{}, method, url1, bodyType string, body io.Reader, bodyLength int64) (err error) {
|
||||
|
||||
resp, err := r.DoRequestWith64(ctx, method, url1, bodyType, body, bodyLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
func (r Client) Call(
|
||||
ctx Context, ret interface{}, method, url1 string) (err error) {
|
||||
|
||||
resp, err := r.DoRequestWith(ctx, method, url1, "application/x-www-form-urlencoded", nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return CallRet(ctx, ret, resp)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type requestCanceler interface {
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
|
||||
type nestedObjectGetter interface {
|
||||
NestedObject() interface{}
|
||||
}
|
||||
|
||||
func getRequestCanceler(tp http.RoundTripper) (rc requestCanceler, ok bool) {
|
||||
|
||||
if rc, ok = tp.(requestCanceler); ok {
|
||||
return
|
||||
}
|
||||
|
||||
p := interface{}(tp)
|
||||
for {
|
||||
getter, ok1 := p.(nestedObjectGetter)
|
||||
if !ok1 {
|
||||
return
|
||||
}
|
||||
p = getter.NestedObject()
|
||||
if rc, ok = p.(requestCanceler); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
66
vendor/github.com/qiniu/x/rpc.v7/rpc_client_test.go
generated
vendored
Normal file
66
vendor/github.com/qiniu/x/rpc.v7/rpc_client_test.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
func TestNewRequest(t *testing.T) {
|
||||
|
||||
req, err := http.NewRequest("GET", "-H\t abc.com \thttp://127.0.0.1/foo/bar", nil)
|
||||
if err != nil {
|
||||
t.Fatal("http.NewRequest failed")
|
||||
}
|
||||
if req.Host != "" {
|
||||
t.Fatal(`http.NewRequest: req.Host != ""`)
|
||||
}
|
||||
|
||||
req, err = newRequest("GET", "-H\t abc.com \thttp://127.0.0.1/foo/bar", nil)
|
||||
if err != nil {
|
||||
t.Fatal("newRequest failed:", err)
|
||||
}
|
||||
|
||||
fmt.Println("Host:", req.Host, "path:", req.URL.Path, "url.host:", req.URL.Host)
|
||||
|
||||
if req.Host != "abc.com" || req.URL.Path != "/foo/bar" || req.URL.Host != "127.0.0.1" {
|
||||
t.Fatal(`req.Host != "abc.com" || req.URL.Path != "/foo/bar" || req.URL.Host != "127.0.0.1"`)
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
type transport struct {
|
||||
a http.RoundTripper
|
||||
}
|
||||
|
||||
func (p *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
return p.a.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (p *transport) NestedObject() interface{} {
|
||||
return p.a
|
||||
}
|
||||
|
||||
func Test_getRequestCanceler(t *testing.T) {
|
||||
|
||||
p := &transport{a: http.DefaultTransport}
|
||||
if _, ok := getRequestCanceler(p); !ok {
|
||||
t.Fatal("getRequestCanceler failed")
|
||||
}
|
||||
|
||||
p2 := &transport{a: p}
|
||||
if _, ok := getRequestCanceler(p2); !ok {
|
||||
t.Fatal("getRequestCanceler(p2) failed")
|
||||
}
|
||||
|
||||
p3 := &transport{}
|
||||
if _, ok := getRequestCanceler(p3); ok {
|
||||
t.Fatal("getRequestCanceler(p3)?")
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
79
vendor/github.com/qiniu/x/ts.v7/testing.go
generated
vendored
Normal file
79
vendor/github.com/qiniu/x/ts.v7/testing.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package ts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const logStackLevel = 2
|
||||
|
||||
func log(skip int, t *testing.T, args ...interface{}) {
|
||||
|
||||
_, file, line, _ := runtime.Caller(skip)
|
||||
_, fname := filepath.Split(file)
|
||||
args1 := make([]interface{}, len(args)+1)
|
||||
args1[0] = fname + ":" + strconv.Itoa(line) + ":"
|
||||
copy(args1[1:], args)
|
||||
|
||||
if os.PathSeparator == '/' {
|
||||
fmt.Fprintln(os.Stdout, args1...)
|
||||
} else {
|
||||
t.Log(args1...)
|
||||
}
|
||||
}
|
||||
|
||||
func logf(skip int, t *testing.T, format string, args ...interface{}) {
|
||||
|
||||
_, file, line, _ := runtime.Caller(skip)
|
||||
_, fname := filepath.Split(file)
|
||||
args2 := make([]interface{}, len(args)+2)
|
||||
args2[0] = fname
|
||||
args2[1] = line
|
||||
copy(args2[2:], args)
|
||||
|
||||
if os.PathSeparator == '/' {
|
||||
fmt.Fprintf(os.Stderr, "%s:%d: "+format+"\n", args2...)
|
||||
} else {
|
||||
t.Logf("%s:%d: "+format, args2...)
|
||||
}
|
||||
}
|
||||
|
||||
// Log formats its arguments using default formatting, analogous to Print(),
|
||||
// and records the text in the error log.
|
||||
func Log(t *testing.T, args ...interface{}) {
|
||||
log(logStackLevel, t, args...)
|
||||
}
|
||||
|
||||
// Logf formats its arguments according to the format, analogous to Printf(),
|
||||
// and records the text in the error log.
|
||||
func Logf(t *testing.T, format string, args ...interface{}) {
|
||||
logf(logStackLevel, t, format, args...)
|
||||
}
|
||||
|
||||
// Error is equivalent to Log() followed by Fail().
|
||||
func Error(t *testing.T, args ...interface{}) {
|
||||
log(logStackLevel, t, args...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Errorf is equivalent to Logf() followed by Fail().
|
||||
func Errorf(t *testing.T, format string, args ...interface{}) {
|
||||
logf(logStackLevel, t, format, args...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Fatal is equivalent to Log() followed by FailNow().
|
||||
func Fatal(t *testing.T, args ...interface{}) {
|
||||
log(logStackLevel, t, args...)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Fatalf is equivalent to Logf() followed by FailNow().
|
||||
func Fatalf(t *testing.T, format string, args ...interface{}) {
|
||||
logf(logStackLevel, t, format, args...)
|
||||
t.FailNow()
|
||||
}
|
||||
208
vendor/github.com/qiniu/x/url.v7/urlescape.go
generated
vendored
Normal file
208
vendor/github.com/qiniu/x/url.v7/urlescape.go
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Encoding int
|
||||
|
||||
const (
|
||||
EncodePath Encoding = 1 + iota
|
||||
EncodeUserPassword
|
||||
EncodeQueryComponent
|
||||
EncodeFragment
|
||||
)
|
||||
|
||||
type EscapeError string
|
||||
|
||||
func (e EscapeError) Error() string {
|
||||
return "invalid URL escape " + strconv.Quote(string(e))
|
||||
}
|
||||
|
||||
func ishex(c byte) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unhex(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Return true if the specified character should be escaped when
|
||||
// appearing in a URL string, according to RFC 3986.
|
||||
// When 'all' is true the full range of reserved characters are matched.
|
||||
func shouldEscape(c byte, mode Encoding) bool {
|
||||
|
||||
// §2.3 Unreserved characters (alphanum)
|
||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
|
||||
return false
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '-', '_', '.', '~': // §2.3 Unreserved characters (mark)
|
||||
return false
|
||||
|
||||
case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
|
||||
// Different sections of the URL allow a few of
|
||||
// the reserved characters to appear unescaped.
|
||||
switch mode {
|
||||
case EncodePath: // §3.3
|
||||
// The RFC allows : @ & = + $ but saves / ; , for assigning
|
||||
// meaning to individual path segments. This package
|
||||
// only manipulates the path as a whole, so we allow those
|
||||
// last two as well. That leaves only ? to escape.
|
||||
return c == '?'
|
||||
|
||||
case EncodeUserPassword: // §3.2.2
|
||||
// The RFC allows ; : & = + $ , in userinfo, so we must escape only @ and /.
|
||||
// The parsing of userinfo treats : as special so we must escape that too.
|
||||
return c == '@' || c == '/' || c == ':'
|
||||
|
||||
case EncodeQueryComponent: // §3.4
|
||||
// The RFC reserves (so we must escape) everything.
|
||||
return true
|
||||
|
||||
case EncodeFragment: // §4.1
|
||||
// The RFC text is silent but the grammar allows
|
||||
// everything, so escape nothing.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Everything else must be escaped.
|
||||
return true
|
||||
}
|
||||
|
||||
// QueryUnescape does the inverse transformation of QueryEscape, converting
|
||||
// %AB into the byte 0xAB and '+' into ' ' (space). It returns an error if
|
||||
// any % is not followed by two hexadecimal digits.
|
||||
func QueryUnescape(s string) (string, error) {
|
||||
|
||||
return UnescapeEx(s, EncodeQueryComponent)
|
||||
}
|
||||
|
||||
func Unescape(s string) (string, error) {
|
||||
|
||||
return UnescapeEx(s, EncodePath)
|
||||
}
|
||||
|
||||
// UnescapeEx unescapes a string; the mode specifies
|
||||
// which section of the URL string is being unescaped.
|
||||
func UnescapeEx(s string, mode Encoding) (string, error) {
|
||||
|
||||
// Count %, check that they're well-formed.
|
||||
n := 0
|
||||
hasPlus := false
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
n++
|
||||
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
||||
s = s[i:]
|
||||
if len(s) > 3 {
|
||||
s = s[0:3]
|
||||
}
|
||||
return "", EscapeError(s)
|
||||
}
|
||||
i += 3
|
||||
case '+':
|
||||
hasPlus = mode == EncodeQueryComponent
|
||||
i++
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 && !hasPlus {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
t := make([]byte, len(s)-2*n)
|
||||
j := 0
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case '%':
|
||||
t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
|
||||
j++
|
||||
i += 3
|
||||
case '+':
|
||||
if mode == EncodeQueryComponent {
|
||||
t[j] = ' '
|
||||
} else {
|
||||
t[j] = '+'
|
||||
}
|
||||
j++
|
||||
i++
|
||||
default:
|
||||
t[j] = s[i]
|
||||
j++
|
||||
i++
|
||||
}
|
||||
}
|
||||
return string(t), nil
|
||||
}
|
||||
|
||||
// QueryEscape escapes the string so it can be safely placed
|
||||
// inside a URL query.
|
||||
func QueryEscape(s string) string {
|
||||
|
||||
return EscapeEx(s, EncodeQueryComponent)
|
||||
}
|
||||
|
||||
func Escape(s string) string {
|
||||
|
||||
return EscapeEx(s, EncodePath)
|
||||
}
|
||||
|
||||
func EscapeEx(s string, mode Encoding) string {
|
||||
|
||||
spaceCount, hexCount := 0, 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if shouldEscape(c, mode) {
|
||||
if c == ' ' && mode == EncodeQueryComponent {
|
||||
spaceCount++
|
||||
} else {
|
||||
hexCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if spaceCount == 0 && hexCount == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
t := make([]byte, len(s)+2*hexCount)
|
||||
j := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case c == ' ' && mode == EncodeQueryComponent:
|
||||
t[j] = '+'
|
||||
j++
|
||||
case shouldEscape(c, mode):
|
||||
t[j] = '%'
|
||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
||||
j += 3
|
||||
default:
|
||||
t[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
211
vendor/github.com/qiniu/x/xlog.v7/xlog.go
generated
vendored
Normal file
211
vendor/github.com/qiniu/x/xlog.v7/xlog.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
package xlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"qiniupkg.com/x/log.v7"
|
||||
"qiniupkg.com/x/reqid.v7"
|
||||
|
||||
. "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
Ldate = log.Ldate
|
||||
Ltime = log.Ltime
|
||||
Lmicroseconds = log.Lmicroseconds
|
||||
Llongfile = log.Llongfile
|
||||
Lshortfile = log.Lshortfile
|
||||
Lmodule = log.Lmodule
|
||||
Llevel = log.Llevel
|
||||
LstdFlags = log.LstdFlags
|
||||
Ldefault = log.Ldefault
|
||||
)
|
||||
|
||||
const (
|
||||
Ldebug = log.Ldebug
|
||||
Linfo = log.Linfo
|
||||
Lwarn = log.Lwarn
|
||||
Lerror = log.Lerror
|
||||
Lpanic = log.Lpanic
|
||||
Lfatal = log.Lfatal
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// type *Logger
|
||||
|
||||
type Logger struct {
|
||||
ReqId string
|
||||
}
|
||||
|
||||
func New(reqId string) *Logger {
|
||||
|
||||
return &Logger{reqId}
|
||||
}
|
||||
|
||||
func NewWith(ctx Context) *Logger {
|
||||
|
||||
reqId, ok := reqid.FromContext(ctx)
|
||||
if !ok {
|
||||
log.Debug("xlog.New: reqid isn't find in context")
|
||||
}
|
||||
return &Logger{reqId}
|
||||
}
|
||||
|
||||
func (xlog *Logger) Spawn(child string) *Logger {
|
||||
|
||||
return &Logger{xlog.ReqId + "." + child}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Print calls Output to print to the standard Logger.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func (xlog *Logger) Print(v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Linfo, 2, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Printf calls Output to print to the standard Logger.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func (xlog *Logger) Printf(format string, v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Println calls Output to print to the standard Logger.
|
||||
// Arguments are handled in the manner of fmt.Println.
|
||||
func (xlog *Logger) Println(v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (xlog *Logger) Debugf(format string, v ...interface{}) {
|
||||
if log.Ldebug < log.Std.Level {
|
||||
return
|
||||
}
|
||||
log.Std.Output(xlog.ReqId, log.Ldebug, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (xlog *Logger) Debug(v ...interface{}) {
|
||||
if log.Ldebug < log.Std.Level {
|
||||
return
|
||||
}
|
||||
log.Std.Output(xlog.ReqId, log.Ldebug, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (xlog *Logger) Infof(format string, v ...interface{}) {
|
||||
if log.Linfo < log.Std.Level {
|
||||
return
|
||||
}
|
||||
log.Std.Output(xlog.ReqId, log.Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (xlog *Logger) Info(v ...interface{}) {
|
||||
if log.Linfo < log.Std.Level {
|
||||
return
|
||||
}
|
||||
log.Std.Output(xlog.ReqId, log.Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (xlog *Logger) Warnf(format string, v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lwarn, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (xlog *Logger) Warn(v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lwarn, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (xlog *Logger) Errorf(format string, v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lerror, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (xlog *Logger) Error(v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lerror, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
|
||||
func (xlog *Logger) Fatal(v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lfatal, 2, fmt.Sprint(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
|
||||
func (xlog *Logger) Fatalf(format string, v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lfatal, 2, fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalln is equivalent to Println() followed by a call to os.Exit(1).
|
||||
func (xlog *Logger) Fatalln(v ...interface{}) {
|
||||
log.Std.Output(xlog.ReqId, log.Lfatal, 2, fmt.Sprintln(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Panic is equivalent to Print() followed by a call to panic().
|
||||
func (xlog *Logger) Panic(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
log.Std.Output(xlog.ReqId, log.Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicf is equivalent to Printf() followed by a call to panic().
|
||||
func (xlog *Logger) Panicf(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
log.Std.Output(xlog.ReqId, log.Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicln is equivalent to Println() followed by a call to panic().
|
||||
func (xlog *Logger) Panicln(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
log.Std.Output(xlog.ReqId, log.Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (xlog *Logger) Stack(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
s += "\n"
|
||||
buf := make([]byte, 1024*1024)
|
||||
n := runtime.Stack(buf, true)
|
||||
s += string(buf[:n])
|
||||
s += "\n"
|
||||
log.Std.Output(xlog.ReqId, log.Lerror, 2, s)
|
||||
}
|
||||
|
||||
func (xlog *Logger) SingleStack(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
s += "\n"
|
||||
buf := make([]byte, 1024*1024)
|
||||
n := runtime.Stack(buf, false)
|
||||
s += string(buf[:n])
|
||||
s += "\n"
|
||||
log.Std.Output(xlog.ReqId, log.Lerror, 2, s)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
func SetOutput(w io.Writer) {
|
||||
log.SetOutput(w)
|
||||
}
|
||||
|
||||
func SetFlags(flag int) {
|
||||
log.SetFlags(flag)
|
||||
}
|
||||
|
||||
func SetOutputLevel(lvl int) {
|
||||
log.SetOutputLevel(lvl)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
Reference in New Issue
Block a user