packer-cn/vendor/github.com/ufilesdk-dev/ufile-gosdk/request.go

182 lines
6.2 KiB
Go
Raw Normal View History

2019-10-12 04:46:21 -04:00
package ufsdk
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
//UFileRequest SDK 主要的 request 模块。本 SDK 遵从以下原则:
//
//1.接口尽可能简洁,隐藏所有复杂实现。
//
//2.本 SDK 主要的作用是封装 HTTP 请求,不做过多其他的封装(如 HTTP Body 序列化,详细的错误检查)。
//
//3.只简单封装 HTTP 请求所需要的参数,给接口使用者提供所有原生的 HTTP response header,body,status code 返回,以便排错。
//
//4.远端请求返回值统一返回一个 error如果为 nil 表示无错。LastResponseStatusLastResponseHeaderLastResponseBody 可以查看具体的 HTTP 返回信息()。如果你想少敲几行代码可以直接调用 DumpResponse(true) 查看详细返回。
//
type UFileRequest struct {
Auth Auth
BucketName string
Host string
Client *http.Client
Context context.Context
baseURL *url.URL
RequestHeader http.Header
LastResponseStatus int
LastResponseHeader http.Header
LastResponseBody []byte
verifyUploadMD5 bool
lastResponse *http.Response
}
//NewFileRequest 创建一个用于管理文件的 request管理文件的 url 与 管理 bucket 接口不一样,
//请将 bucket 和文件管理所需要的分开NewUBucketRequest 是用来管理 bucket 的。
//Request 创建后的 instance 不是线程安全的,如果你需要做并发的操作,请创建多个 UFileRequest。
//config 参数里面包含了公私钥,以及其他必填的参数。详情见 config 相关文档。
//client 这里你可以传空,会使用默认的 http.Client。如果你需要设置超时以及一些其他相关的网络配置选项请传入一个自定义的 client。
func NewFileRequest(config *Config, client *http.Client) (*UFileRequest, error) {
config.BucketName = strings.TrimSpace(config.BucketName)
config.FileHost = strings.TrimSpace(config.FileHost)
if config.BucketName == "" || config.FileHost == "" {
return nil, errors.New("管理文件上传必须要提供 bucket 名字和所在地域的 Host 域名")
}
req := newRequest(config.PublicKey, config.PrivateKey,
config.BucketName, config.FileHost, client)
req.verifyUploadMD5 = config.VerifyUploadMD5
if req.baseURL.Scheme == "" { //用户传了非自定义域名
req.baseURL.Host = req.BucketName + "." + req.Host
req.baseURL.Scheme = "http"
}
return req, nil
}
//NewFileRequestWithHeader 创建一个可携带用户自定义http请求头的 request
//Request 创建后的 instance 不是线程安全的,如果你需要做并发的操作,请创建多个 UFileRequest。
//config 参数里面包含了公私钥,以及其他必填的参数。详情见 config 相关文档。
//header 自定义http请求头
//client 这里你可以传空,会使用默认的 http.Client。如果你需要设置超时以及一些其他相关的网络配置选项请传入一个自定义的 client。
func NewFileRequestWithHeader(config *Config, header http.Header, client *http.Client) (*UFileRequest, error) {
req, err := NewFileRequest(config, client)
if err != nil{
return nil, err
}
req.RequestHeader = header
return req, nil
}
//NewBucketRequest 创建一个用于管理 bucket 的 request。
//注意:不要拿它去调用文件管理的 request我文件管理和 bucket 管理接口放到一个 request 里面的目的就是让接口更统一,代码更清晰,简洁。
//config 参数里面包含了公私钥,以及其他必填的参数。详情见 config 相关文档。
func NewBucketRequest(config *Config, client *http.Client) (*UFileRequest, error) {
config.BucketHost = strings.TrimSpace(config.BucketHost)
if config.BucketHost == "" {
return nil, errors.New("管理 Bucket 必须要提供对应的 API host")
}
req := newRequest(config.PublicKey, config.PrivateKey, "", config.BucketHost, client)
req.verifyUploadMD5 = config.VerifyUploadMD5
if req.baseURL.Scheme == "" {
req.baseURL.Scheme = "http"
}
return req, nil
}
//DumpResponse dump 当前请求的返回结果,里面有一个 print 函数,会把 body,header,status code 直接输出到 stdout。
//如果你需要 Dump 到其他的地方,直接拿返回值即可。
func (u *UFileRequest) DumpResponse(isDumpBody bool) []byte {
var b bytes.Buffer
if u.lastResponse == nil {
return nil
}
b.WriteString(fmt.Sprintf("%s %d\n", u.lastResponse.Proto, u.LastResponseStatus))
for k, vs := range u.LastResponseHeader {
str := k + ": "
for i, v := range vs {
if i != 0 {
str += "; " + v
} else {
str += v
}
}
b.WriteString(str)
}
if isDumpBody {
b.Write(u.LastResponseBody)
}
return b.Bytes()
}
func newRequest(publicKey, privateKey, bucket, host string, client *http.Client) *UFileRequest {
req := new(UFileRequest)
req.Auth = NewAuth(publicKey, privateKey)
req.BucketName = bucket
req.Host = strings.TrimSpace(host)
req.baseURL = new(url.URL)
req.baseURL.Host = req.Host
req.baseURL.Path = "/" //for default usage.
if client == nil {
client = new(http.Client)
}
req.Client = client
req.Context = context.TODO()
return req
}
func (u *UFileRequest) responseParse(resp *http.Response) error {
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()
u.LastResponseStatus = resp.StatusCode
u.LastResponseHeader = resp.Header
u.LastResponseBody = resBody
u.lastResponse = resp
return nil
}
func (u *UFileRequest) request(req *http.Request) error {
resp, err := u.requestWithResp(req)
if err != nil {
return err
}
err = u.responseParse(resp)
if err != nil {
return err
}
if !VerifyHTTPCode(resp.StatusCode) {
return fmt.Errorf("Remote response code is %d - %s not 2xx call DumpResponse(true) show details",
resp.StatusCode, http.StatusText(resp.StatusCode))
}
return nil
}
func (u *UFileRequest) requestWithResp(req *http.Request) (resp *http.Response, err error) {
req.Header.Set("User-Agent", "UFile-GoSDK-Client/2.0")
resp, err = u.Client.Do(req.WithContext(u.Context))
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
if err != nil {
select {
case <-u.Context.Done():
err = u.Context.Err()
default:
}
return
}
return
}