You need to enable JavaScript to run this app.
导航

上传回调(Go SDK)

最近更新时间2024.04.22 21:26:58

首次发布时间2024.04.22 16:09:55

上传回调是指客户端在请求时携带回调(Callback)参数,服务端在上传完成后,发送同步的 POST 回调请求到 CallBack 中指定的第三方应用服务器,在服务器确认接受并返回结果后,才将所有结果返回给客户端。
关于上传回调的详细介绍,请参见上传回调

示例代码

普通上传实现上传回调

package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "os"
    "strings"

    "github.com/volcengine/ve-tos-golang-sdk/v2/tos"
)

func checkErr(err error) {
    if err != nil {
       if serverErr, ok := err.(*tos.TosServerError); ok {
          fmt.Println("Error:", serverErr.Error())
          fmt.Println("Request ID:", serverErr.RequestID)
          fmt.Println("Response Status Code:", serverErr.StatusCode)
          fmt.Println("Response Header:", serverErr.Header)
          fmt.Println("Response Err Code:", serverErr.Code)
          fmt.Println("Response Err Msg:", serverErr.Message)
       } else if clientErr, ok := err.(*tos.TosClientError); ok {
          fmt.Println("Error:", clientErr.Error())
          fmt.Println("Client Cause Err:", clientErr.Cause.Error())
       } else {
          fmt.Println("Error:", err)
       }
       panic(err)
    }
}

func main() {
    var (
       // 火山引擎账号 accessKey 具有 TOS 访问权限
       ak = os.Getenv("TOS_ACCESS_KEY")
       sk = os.Getenv("TOS_SECRET_KEY")
       // Bucket 对于的 Endpoint,以华北2(北京)为例:https://tos-cn-beijing.volces.com
       endpoint = "https://tos-cn-beijing.volces.com"
       region   = "cn-beijing"
       // 填写 BucketName
       bucketName = "*** Provide your bucket name ***"
       // 填写对象名
       objectKey   = "*** Provide your object key ***"
       callbackUrl = "*** Provide your callback url ***"
    )
    // 初始化客户端
    client, err := tos.NewClientV2(endpoint, tos.WithRegion(region), tos.WithCredentials(tos.NewStaticCredentials(ak, sk)))
    checkErr(err)

    // 设置上传回掉参数
    // 可以根据需求设置 Callback 参数
    callback := `
    {
       "callbackUrl" : "` + callbackUrl + `", 
       "callbackBody" : "{\"bucket\": ${bucket}, \"object\": ${object}, \"key1\": ${x:key1}}", 
       "callbackBodyType" : "application/json"                
    }`
    // 配置相关变量参数
    callbackVar := `
    {
       "x:key1" : "ceshi"
    }`

    body := strings.NewReader("object content")
    output, err := client.PutObjectV2(context.Background(), &tos.PutObjectV2Input{
       PutObjectBasicInput: tos.PutObjectBasicInput{
          Bucket:      bucketName,
          Key:         objectKey,
          Callback:    base64.StdEncoding.EncodeToString([]byte(callback)),
          CallbackVar: base64.StdEncoding.EncodeToString([]byte(callbackVar)),
       },
       Content: body,
    })
    checkErr(err)
    fmt.Println("Put Object Request ID: ", output.RequestID)
    fmt.Println("Put Object Response Status Code: ", output.StatusCode)
}

分片上传实现上传回调

package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "os"

    "github.com/volcengine/ve-tos-golang-sdk/v2/tos"
)

func checkErr(err error) {
    if err != nil {
       if serverErr, ok := err.(*tos.TosServerError); ok {
          fmt.Println("Error:", serverErr.Error())
          fmt.Println("Request ID:", serverErr.RequestID)
          fmt.Println("Response Status Code:", serverErr.StatusCode)
          fmt.Println("Response Header:", serverErr.Header)
          fmt.Println("Response Err Code:", serverErr.Code)
          fmt.Println("Response Err Msg:", serverErr.Message)
       } else if clientErr, ok := err.(*tos.TosClientError); ok {
          fmt.Println("Error:", clientErr.Error())
          fmt.Println("Client Cause Err:", clientErr.Cause.Error())
       } else {
          fmt.Println("Error:", err)
       }
       panic(err)
    }
}

func main() {
    var (
       accessKey = os.Getenv("TOS_ACCESS_KEY")
       secretKey = os.Getenv("TOS_SECRET_KEY")
       // Bucket 对应的 Endpoint,以华北2(北京)为例:https://tos-cn-beijing.volces.com
       endpoint = "https://tos-cn-beijing.volces.com"
       region   = "cn-beijing"
       // 填写 BucketName
       bucketName = "*** Provide your bucket name ***"

       // 指定的 ObjectKey
       objectKey = "*** Provide your object name ***"
       ctx       = context.Background()
       uploadId  = "*** Provide your upload id ***"
       // 需要合并的 parts
       parts = make([]tos.UploadedPartV2, 0)
       callbackUrl = "*** Provide your callback url ***"
    )
    // 初始化客户端
    client, err := tos.NewClientV2(endpoint, tos.WithRegion(region), tos.WithCredentials(tos.NewStaticCredentials(accessKey, secretKey)))
    checkErr(err)
    // 设置上传回掉参数
    // 可以根据需求设置 Callback 参数
    callback := `
    {
       "callbackUrl" : "` + callbackUrl + `", 
       "callbackBody" : "{\"bucket\": ${bucket}, \"object\": ${object}, \"key1\": ${x:key1}}", 
       "callbackBodyType" : "application/json"                
    }`
    // 配置相关变量参数
    callbackVar := `
    {
       "x:key1" : "ceshi"
    }`

    completeOutput, err := client.CompleteMultipartUploadV2(ctx, &tos.CompleteMultipartUploadV2Input{
       Bucket:      bucketName,
       Key:         objectKey,
       UploadID:    uploadId,
       Parts:       parts,
       Callback:    base64.StdEncoding.EncodeToString([]byte(callback)),
       CallbackVar: base64.StdEncoding.EncodeToString([]byte(callbackVar)),
    })
    checkErr(err)
    fmt.Println("CompleteMultipartUploadV2 Request ID:", completeOutput.RequestID)

}

验证回调签名的示例代码

package main

import (
    "bytes"
    "crypto"
    "crypto/md5"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"
    "sort"
)

const Authorization = "Authorization"
const XTosPublicKeyUrl = "x-tos-pub-key-url"

func calcStringToSign(req *http.Request, body []byte) []byte {
    buf := bytes.NewBuffer(nil)
    buf.WriteString(req.URL.Path)

    query := req.URL.Query()
    if len(query) > 0 {
       buf.WriteByte('?')
       keys := make([]string, 0, len(query))
       for key := range query {
          keys = append(keys, key)
       }
       sort.Strings(keys)

       for _, key := range keys {
          values := query[key]
          for _, value := range values {
             buf.WriteString(key)
             buf.WriteByte('=')
             buf.WriteString(value)
             buf.WriteByte('&')
          }
       }

       buf.Truncate(buf.Len() - 1)
    }

    buf.WriteByte('\n')
    buf.Write(body)
    return buf.Bytes()
}

func getSignature(r *http.Request) ([]byte, error) {
    authorization := r.Header.Get(Authorization)
    if authorization == "" {
       return nil, nil
    }

    return base64.StdEncoding.DecodeString(authorization)
}

func getPublicKey(r *http.Request) ([]byte, error) {
    var bytePublicKey []byte

    publicKeyURLBase64 := r.Header.Get(XTosPublicKeyUrl)
    if publicKeyURLBase64 == "" {
       return bytePublicKey, errors.New("no x-tos-pub-key-url field in request header ")
    }
    publicKeyURL, _ := base64.StdEncoding.DecodeString(publicKeyURLBase64)

    resp, err := http.Get(string(publicKeyURL))
    if err != nil {
       return bytePublicKey, err
    }

    bytePublicKey, err = ioutil.ReadAll(resp.Body)
    defer resp.Body.Close()
    if err != nil {
       return bytePublicKey, err
    }

    if resp.StatusCode >= http.StatusBadRequest {
       return nil, errors.New("get public key failed")
    }

    return bytePublicKey, nil
}

func getContentAndSignMD5(r *http.Request) ([]byte, []byte, error) {
    bodyContent, err := ioutil.ReadAll(r.Body)
    _ = r.Body.Close()
    if err != nil {
       return nil, nil, err
    }

    stringToSign := calcStringToSign(r, bodyContent)

    authMd5 := md5.New()
    authMd5.Write(stringToSign)
    signMd5 := authMd5.Sum(nil)
    return signMd5, bodyContent, nil
}

func verifySignature(publicKey []byte, signMd5 []byte, sign []byte) error {
    pubBlock, _ := pem.Decode(publicKey)
    if pubBlock == nil {
       return errors.New("invalid public key pem")
    }
    pubInterface, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
    if err != nil || pubInterface == nil {
       return errors.New("invalid public key pem")
    }

    pub := pubInterface.(*rsa.PublicKey)

    err = rsa.VerifyPKCS1v15(pub, crypto.MD5, signMd5, sign)
    if err != nil {
       return err
    }
    return nil
}

func CallbackHandler(w http.ResponseWriter, r *http.Request) {
    // 获取签名
    sign, err := getSignature(r)
    if err != nil {
       fmt.Println("get callback signature err:", err.Error())
       w.WriteHeader(http.StatusBadRequest)
       return
    }

    // 获取公共签名,该结果可以缓存
    publicKey, err := getPublicKey(r)
    if err != nil {
       w.WriteHeader(http.StatusBadRequest)
       return
    }

    // 读取 body 并计算签名
    signMd5, body, err := getContentAndSignMD5(r)
    if err != nil {
       w.WriteHeader(http.StatusBadRequest)
       return
    }

    // 验证签名
    if err = verifySignature(publicKey, signMd5, sign); err != nil {
       w.WriteHeader(http.StatusUnauthorized)
       return
    }

    // 处理回调 body
    fmt.Println(body)
    // 模拟返回
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"msg":"ok"}`))
    return
}