上传回调是指客户端在请求时携带回调(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 }