s3cmd的addHeader功能Go實(shí)現(xiàn)

通過本篇文章將了解到:1、利用go語言實(shí)現(xiàn)s3cmd中addHeader的流程;2、利用aws-sdk-go和http庫實(shí)現(xiàn)ceph HEAD和PUT請(qǐng)求。

s3cmd

s3cmd是一個(gè)操作對(duì)象存儲(chǔ)的軟件,使用python開發(fā)。其功能包括常見的S3操作,比如桶的創(chuàng)建、刪除,對(duì)象的增刪改查等操作。實(shí)驗(yàn)使用的是Ceph RGW提供S3服務(wù),s3cmd同樣可以適配使用。

在CentOS 7上使用如下命令安裝:

yum install s3cmd 

安裝后使用s3cmd --configure 或者直接修改配置文件 vim ~/.s3cfg。完成access_key、secret_key、host_base、host_bucket、use_https等字段的配置可以訪問Ceph RGW對(duì)象存儲(chǔ)。

s3cmd的modify命令可以通過--add-header選項(xiàng)給對(duì)象添加而外的頭部信息。語法如下:

s3cmd modify s3://{桶名}/{對(duì)象名}  --add-header={key}:{value}

需求

最近在項(xiàng)目上需要用到給對(duì)象數(shù)據(jù)添加自定義的元數(shù)據(jù)。目前Ceph官方支持的形式x-amz-meta-{key}:{value} 形式,稱為Canonicalized Header。

通過該方式可以給對(duì)象添加額外自定義的元數(shù)據(jù),并且如果配置了復(fù)制zone,把復(fù)制zone和ElasticSearch的后端綁定,該自定義元數(shù)據(jù)可以在Elasticsearch中形成索引條目(親測(cè)有效)。同時(shí)支持查詢語句,在不暴露ElasticSearch服務(wù)的情況下,通過RGW網(wǎng)關(guān)高效檢索。

使用s3cmd可以添加自定義的元數(shù)據(jù),命令如下。這里添加了一個(gè)key為city,value為Hangzhou的字段。

s3cmd modify s3://{桶名}/{對(duì)象名}  --add-header=x-amz-meta-city:Hangzhou

現(xiàn)在項(xiàng)目組用Go語言對(duì)接,為了添加自定義元數(shù)據(jù),一種方式可以直接在程序里面調(diào)用命令,但這要求運(yùn)行主機(jī)上已配置好s3cmd命令。另外,是否有Go語言庫可以支持,可以利用Go的庫支持該功能。查看ceph-go和aws-sdk-go兩個(gè)項(xiàng)目沒有直接的提供類似的功能。需要實(shí)現(xiàn)一個(gè)類似的功能。

addHeader實(shí)現(xiàn)流程

借鑒s3cmd的思路(閱讀S3.py內(nèi)object_modify/object_copy等函數(shù)代碼)。

1、通過HEAD接口獲得當(dāng)前對(duì)象的頭部信息(只獲得頭部信息即可,因?yàn)椴粫?huì)去修改對(duì)象的數(shù)據(jù));

2、修改HEAD頭部信息,并添加對(duì)應(yīng)的自定義字段;

3、結(jié)合PUT接口和x-amz-copy-src選項(xiàng)(即拷貝對(duì)象接口),復(fù)制對(duì)象并上傳新的header信息。

為什么這里要使用x-amz-copy-src?如果不使用,新的對(duì)象會(huì)沒有數(shù)據(jù);或者就需要在步驟1中使用GET接口獲取完整對(duì)象至本地,再上傳,造成資源的消耗。x-amz-copy-src是在服務(wù)端完成的。

4、需要說明的是HEAD的作為源對(duì)象,PUT操作的目的對(duì)象是同一個(gè)對(duì)象。

GO語言實(shí)現(xiàn)

該功能的Go實(shí)現(xiàn)基于:
1、利用ceph本身RESTful的接口;
2、利用http包進(jìn)行http消息的發(fā)送和接收;
3、利用aws-sdk-go中的簽名對(duì)消息進(jìn)行簽署。

具體步驟

首先利用HEAD接口僅僅獲得對(duì)象的頭信息,Ceph提供了HEAD的接口。

HEAD /{bucket}/{object} HTTP/1.1

Go語言的實(shí)現(xiàn)需要用到aws-sdk-go當(dāng)中的簽名函數(shù)。因?yàn)镃eph兼容S3接口,認(rèn)證的方式也是和AWS S3一樣,需要在發(fā)送請(qǐng)求前先給請(qǐng)求簽名。accessKey和secretKey是S3創(chuàng)建用戶時(shí)設(shè)定的相應(yīng)值。

cred := credentials.NewStaticCredentials(accessKey, secretKey, "")
signer := v4.NewSigner(cred)
_, err = signer.Sign(request, nil, service, authRegion, time.Now())

發(fā)送請(qǐng)求

resp, err := httpClient.Do(request)

獲得頭部信息之后需要進(jìn)行修改,否則在簽名的時(shí)候會(huì)出現(xiàn)認(rèn)證失敗的問題。刪除原先的頭部信息會(huì)在請(qǐng)求發(fā)送的時(shí)候根據(jù)實(shí)際情況自動(dòng)添加。

h2 = resp.Header.Clone()

to_remove := []string{"Date", "Content-Length", 
                      "Content-Md5", "Accept-Ranges", 
                      "X-Amz-Request-Id", "Last-Modified", 
                      "ETag", "Connection", "Server", 
                      "X-Amz-Version-Id", "X-Amz-Delete-Maker"}

for _, k := range to_remove {
   h2.Del(k) 
}

設(shè)置復(fù)制的方式,復(fù)制的方式有兩種一種是COPY,一種是REPLACE。使用COPY則新添加的元數(shù)據(jù)不會(huì)有效,使用REPLACE則需要將原先元數(shù)據(jù)保留、修改、添加新的自定義元數(shù)據(jù)。這里指定REPLACE。

request.Header.Add("X-Amz-Metadata-Directive", "REPLACE")

使用Set方法設(shè)置元數(shù)據(jù)。

request.Header.Set("X-Amz-Meta-City", "Hangzhou")

再發(fā)送PUT請(qǐng)求和x-amz-source-copy參數(shù)復(fù)制一份對(duì)象。Ceph同樣提供了復(fù)制對(duì)象的接口。

PUT /{dest-bucket}/{dest-object} HTTP/1.1
x-amz-copy-source: {source-bucket}/{source-object}

指定復(fù)制源

request.Header.Add("X-Amz-Copy-Source", srcAddr)

這樣就不需要將數(shù)據(jù)GET請(qǐng)求保留在本地,然后再上傳,節(jié)省部分資源開銷。

具體代碼

整體代碼如下

package main

import (

    "net/http"

    "net/http/httputil"

    "fmt"

    "context"

    "time"

    "github.com/aws/aws-sdk-go/aws/credentials"

    v4 "github.com/aws/aws-sdk-go/aws/signer/v4"

)



const (

    authRegion = "default"

    service = "s3"

    connectionTimeout = time.Second * 3

    bucketName = "cephgo"

    endPoint = "http://192.168.99.103:8080"

    accessKey = "654321"

    secretKey = "654321"

    srcFile = "file3"

    destFile = "file3"

)



func buildUrlPath(host, bucket, file string) string {

    return fmt.Sprintf("%s/%s/%s", host, bucket, file)

}



func main() {
   
    //build Head req
    httpMethod := http.MethodHead

    urlPath := buildUrlPath(endPoint, bucketName, srcFile) 

    httpClient := &http.Client{Timeout: connectionTimeout}

    request, err := http.NewRequestWithContext(context.Background(), httpMethod, urlPath ,nil)

    if err != nil {

        fmt.Println(err)

      return

    }
    
    //sign Head req

    cred := credentials.NewStaticCredentials(accessKey, secretKey, "")

    signer := v4.NewSigner(cred)

    _, err = signer.Sign(request, nil, service, authRegion, time.Now())

    if err != nil {

        fmt.Println(err)

      return

    }

    //send HEAD Quest

    resp, err := httpClient.Do(request)

    if err != nil {

        fmt.Println(err)

      return

    }

    //copy header 

    var h2 http.Header

    h2 = resp.Header.Clone()

    to_remove := []string{"Date", "Content-Length", 

                                 "Content-Md5", "Accept-Ranges", 

                                 "X-Amz-Request-Id", "Last-Modified", 

                                 "ETag", "Connection", "Server", 

                                 "X-Amz-Version-Id", "X-Amz-Delete-Maker"}

    for _, k := range to_remove {

       h2.Del(k) 

    }

    //build PUT req

    httpMethod = http.MethodPut

    urlPath =  buildUrlPath(endPoint, bucketName, destFile) 

    request, err = http.NewRequestWithContext(context.Background(), httpMethod, urlPath ,nil)

    if err != nil {

        fmt.Println(err)

      return

    }

    //edit header    

    srcAddr := "/" + bucketName + "/" + srcFile

    request.Header = h2 

    request.Header.Add("X-Amz-Copy-Source", srcAddr)

    request.Header.Add("X-Amz-Metadata-Directive", "REPLACE")

    request.Header.Set("X-Amz-Meta-City", "Hangzhou")

    //sign PUT req

    cred = credentials.NewStaticCredentials(accessKey, secretKey, "")

    signer = v4.NewSigner(cred)

    _, err = signer.Sign(request, nil, service, authRegion, time.Now())

    if err != nil {

        fmt.Println(err)

      return

    }

    requestDump, err := httputil.DumpRequest(request, true)

    if err != nil {

        fmt.Println(err)

      return

    }

    fmt.Println(string(requestDump))

    //send PUT Quest

    resp, err = httpClient.Do(request)

    if err != nil {

        fmt.Println(err)

      return
    }
    responseDump, err := httputil.DumpResponse(resp, true)

    if err != nil {

        fmt.Println(err)

      return

    }

    fmt.Println(string(responseDump))
}

執(zhí)行效果

執(zhí)行前對(duì)象無自定義元數(shù)據(jù)信息


添加自定義元數(shù)據(jù)前.png

執(zhí)行后對(duì)象存在自定義元數(shù)據(jù)信息,且數(shù)據(jù)完整。

添加自定義元數(shù)據(jù)后.png

寫到最后

1、如何刪除自定義的元數(shù)據(jù)?
s3cmd的modify命令可以通過--remove-header選項(xiàng)給對(duì)象添加而外的頭部信息。語法如下

s3cmd modify s3://{桶名}/{對(duì)象名} --remove-header={key}

依據(jù)之前的代碼,用go可以實(shí)現(xiàn),即利用http中Header的Del功能。添加如下語句即可。

request.Header.Del(key)

2、文章里描述了大體的流程,沒有考慮分段上傳等情況,距離實(shí)際封裝成接口還有一些工作。
3、實(shí)際上S3本身支持Tag方式,Ceph對(duì)象也可以添加。兩種機(jī)制需要比較。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容