| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289 |
- package oss
- import (
- "bytes"
- "crypto/md5"
- "encoding/base64"
- "encoding/xml"
- "fmt"
- "hash"
- "hash/crc64"
- "io"
- "net/http"
- "net/url"
- "os"
- "strconv"
- "strings"
- "time"
- )
- // Bucket implements the operations of object.
- type Bucket struct {
- Client Client
- BucketName string
- }
- // PutObject creates a new object and it will overwrite the original one if it exists already.
- //
- // objectKey the object key in UTF-8 encoding. The length must be between 1 and 1023, and cannot start with "/" or "\".
- // reader io.Reader instance for reading the data for uploading
- // options the options for uploading the object. The valid options here are CacheControl, ContentDisposition, ContentEncoding
- // Expires, ServerSideEncryption, ObjectACL and Meta. Refer to the link below for more details.
- // https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error {
- opts := AddContentType(options, objectKey)
- request := &PutObjectRequest{
- ObjectKey: objectKey,
- Reader: reader,
- }
- resp, err := bucket.DoPutObject(request, opts)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return err
- }
- // PutObjectFromFile creates a new object from the local file.
- //
- // objectKey object key.
- // filePath the local file path to upload.
- // options the options for uploading the object. Refer to the parameter options in PutObject for more details.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error {
- fd, err := os.Open(filePath)
- if err != nil {
- return err
- }
- defer fd.Close()
- opts := AddContentType(options, filePath, objectKey)
- request := &PutObjectRequest{
- ObjectKey: objectKey,
- Reader: fd,
- }
- resp, err := bucket.DoPutObject(request, opts)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return err
- }
- // DoPutObject does the actual upload work.
- //
- // request the request instance for uploading an object.
- // options the options for uploading an object.
- //
- // Response the response from OSS.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DoPutObject(request *PutObjectRequest, options []Option) (*Response, error) {
- isOptSet, _, _ := IsOptionSet(options, HTTPHeaderContentType)
- if !isOptSet {
- options = AddContentType(options, request.ObjectKey)
- }
- listener := GetProgressListener(options)
- params := map[string]interface{}{}
- resp, err := bucket.do("PUT", request.ObjectKey, params, options, request.Reader, listener)
- if err != nil {
- return nil, err
- }
- if bucket.GetConfig().IsEnableCRC {
- err = CheckCRC(resp, "DoPutObject")
- if err != nil {
- return resp, err
- }
- }
- err = CheckRespCode(resp.StatusCode, []int{http.StatusOK})
- return resp, err
- }
- // GetObject downloads the object.
- //
- // objectKey the object key.
- // options the options for downloading the object. The valid values are: Range, IfModifiedSince, IfUnmodifiedSince, IfMatch,
- // IfNoneMatch, AcceptEncoding. For more details, please check out:
- // https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
- //
- // io.ReadCloser reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) {
- result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options)
- if err != nil {
- return nil, err
- }
- return result.Response, nil
- }
- // GetObjectToFile downloads the data to a local file.
- //
- // objectKey the object key to download.
- // filePath the local file to store the object data.
- // options the options for downloading the object. Refer to the parameter options in method GetObject for more details.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error {
- tempFilePath := filePath + TempFileSuffix
- // Calls the API to actually download the object. Returns the result instance.
- result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options)
- if err != nil {
- return err
- }
- defer result.Response.Close()
- // If the local file does not exist, create a new one. If it exists, overwrite it.
- fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode)
- if err != nil {
- return err
- }
- // Copy the data to the local file path.
- _, err = io.Copy(fd, result.Response.Body)
- fd.Close()
- if err != nil {
- return err
- }
- // Compares the CRC value
- hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
- encodeOpt, _ := FindOption(options, HTTPHeaderAcceptEncoding, nil)
- acceptEncoding := ""
- if encodeOpt != nil {
- acceptEncoding = encodeOpt.(string)
- }
- if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" {
- result.Response.ClientCRC = result.ClientCRC.Sum64()
- err = CheckCRC(result.Response, "GetObjectToFile")
- if err != nil {
- os.Remove(tempFilePath)
- return err
- }
- }
- return os.Rename(tempFilePath, filePath)
- }
- // DoGetObject is the actual API that gets the object. It's the internal function called by other public APIs.
- //
- // request the request to download the object.
- // options the options for downloading the file. Checks out the parameter options in method GetObject.
- //
- // GetObjectResult the result instance of getting the object.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (*GetObjectResult, error) {
- params, _ := GetRawParams(options)
- resp, err := bucket.do("GET", request.ObjectKey, params, options, nil, nil)
- if err != nil {
- return nil, err
- }
- result := &GetObjectResult{
- Response: resp,
- }
- // CRC
- var crcCalc hash.Hash64
- hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
- if bucket.GetConfig().IsEnableCRC && !hasRange {
- crcCalc = crc64.New(CrcTable())
- result.ServerCRC = resp.ServerCRC
- result.ClientCRC = crcCalc
- }
- // Progress
- listener := GetProgressListener(options)
- contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64)
- resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil)
- return result, nil
- }
- // CopyObject copies the object inside the bucket.
- //
- // srcObjectKey the source object to copy.
- // destObjectKey the target object to copy.
- // options options for copying an object. You can specify the conditions of copy. The valid conditions are CopySourceIfMatch,
- // CopySourceIfNoneMatch, CopySourceIfModifiedSince, CopySourceIfUnmodifiedSince, MetadataDirective.
- // Also you can specify the target object's attributes, such as CacheControl, ContentDisposition, ContentEncoding, Expires,
- // ServerSideEncryption, ObjectACL, Meta. Refer to the link below for more details :
- // https://help.aliyun.com/document_detail/oss/api-reference/object/CopyObject.html
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
- var out CopyObjectResult
- //first find version id
- versionIdKey := "versionId"
- versionId, _ := FindOption(options, versionIdKey, nil)
- if versionId == nil {
- options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
- } else {
- options = DeleteOption(options, versionIdKey)
- options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string)))
- }
- params := map[string]interface{}{}
- resp, err := bucket.do("PUT", destObjectKey, params, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- return out, err
- }
- // CopyObjectTo copies the object to another bucket.
- //
- // srcObjectKey source object key. The source bucket is Bucket.BucketName .
- // destBucketName target bucket name.
- // destObjectKey target object name.
- // options copy options, check out parameter options in function CopyObject for more details.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) CopyObjectTo(destBucketName, destObjectKey, srcObjectKey string, options ...Option) (CopyObjectResult, error) {
- return bucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
- }
- //
- // CopyObjectFrom copies the object to another bucket.
- //
- // srcBucketName source bucket name.
- // srcObjectKey source object name.
- // destObjectKey target object name. The target bucket name is Bucket.BucketName.
- // options copy options. Check out parameter options in function CopyObject.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
- destBucketName := bucket.BucketName
- var out CopyObjectResult
- srcBucket, err := bucket.Client.Bucket(srcBucketName)
- if err != nil {
- return out, err
- }
- return srcBucket.copy(srcObjectKey, destBucketName, destObjectKey, options...)
- }
- func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
- var out CopyObjectResult
- //first find version id
- versionIdKey := "versionId"
- versionId, _ := FindOption(options, versionIdKey, nil)
- if versionId == nil {
- options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
- } else {
- options = DeleteOption(options, versionIdKey)
- options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string)))
- }
- headers := make(map[string]string)
- err := handleOptions(headers, options)
- if err != nil {
- return out, err
- }
- params := map[string]interface{}{}
- resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, params, headers, nil, 0, nil)
- // get response header
- respHeader, _ := FindOption(options, responseHeader, nil)
- if respHeader != nil {
- pRespHeader := respHeader.(*http.Header)
- *pRespHeader = resp.Headers
- }
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- return out, err
- }
- // AppendObject uploads the data in the way of appending an existing or new object.
- //
- // AppendObject the parameter appendPosition specifies which postion (in the target object) to append. For the first append (to a non-existing file),
- // the appendPosition should be 0. The appendPosition in the subsequent calls will be the current object length.
- // For example, the first appendObject's appendPosition is 0 and it uploaded 65536 bytes data, then the second call's position is 65536.
- // The response header x-oss-next-append-position after each successful request also specifies the next call's append position (so the caller need not to maintain this information).
- //
- // objectKey the target object to append to.
- // reader io.Reader. The read instance for reading the data to append.
- // appendPosition the start position to append.
- // destObjectProperties the options for the first appending, such as CacheControl, ContentDisposition, ContentEncoding,
- // Expires, ServerSideEncryption, ObjectACL.
- //
- // int64 the next append position, it's valid when error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) {
- request := &AppendObjectRequest{
- ObjectKey: objectKey,
- Reader: reader,
- Position: appendPosition,
- }
- result, err := bucket.DoAppendObject(request, options)
- if err != nil {
- return appendPosition, err
- }
- return result.NextPosition, err
- }
- // DoAppendObject is the actual API that does the object append.
- //
- // request the request object for appending object.
- // options the options for appending object.
- //
- // AppendObjectResult the result object for appending object.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Option) (*AppendObjectResult, error) {
- params := map[string]interface{}{}
- params["append"] = nil
- params["position"] = strconv.FormatInt(request.Position, 10)
- headers := make(map[string]string)
- opts := AddContentType(options, request.ObjectKey)
- handleOptions(headers, opts)
- var initCRC uint64
- isCRCSet, initCRCOpt, _ := IsOptionSet(options, initCRC64)
- if isCRCSet {
- initCRC = initCRCOpt.(uint64)
- }
- listener := GetProgressListener(options)
- handleOptions(headers, opts)
- resp, err := bucket.Client.Conn.Do("POST", bucket.BucketName, request.ObjectKey, params, headers,
- request.Reader, initCRC, listener)
- // get response header
- respHeader, _ := FindOption(options, responseHeader, nil)
- if respHeader != nil {
- pRespHeader := respHeader.(*http.Header)
- *pRespHeader = resp.Headers
- }
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- nextPosition, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderOssNextAppendPosition), 10, 64)
- result := &AppendObjectResult{
- NextPosition: nextPosition,
- CRC: resp.ServerCRC,
- }
- if bucket.GetConfig().IsEnableCRC && isCRCSet {
- err = CheckCRC(resp, "AppendObject")
- if err != nil {
- return result, err
- }
- }
- return result, nil
- }
- // DeleteObject deletes the object.
- //
- // objectKey the object key to delete.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DeleteObject(objectKey string, options ...Option) error {
- params, _ := GetRawParams(options)
- resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
- }
- // DeleteObjects deletes multiple objects.
- //
- // objectKeys the object keys to delete.
- // options the options for deleting objects.
- // Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used.
- //
- // DeleteObjectsResult the result object.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) {
- out := DeleteObjectsResult{}
- dxml := deleteXML{}
- for _, key := range objectKeys {
- dxml.Objects = append(dxml.Objects, DeleteObject{Key: key})
- }
- isQuiet, _ := FindOption(options, deleteObjectsQuiet, false)
- dxml.Quiet = isQuiet.(bool)
- bs, err := xml.Marshal(dxml)
- if err != nil {
- return out, err
- }
- buffer := new(bytes.Buffer)
- buffer.Write(bs)
- contentType := http.DetectContentType(buffer.Bytes())
- options = append(options, ContentType(contentType))
- sum := md5.Sum(bs)
- b64 := base64.StdEncoding.EncodeToString(sum[:])
- options = append(options, ContentMD5(b64))
- params := map[string]interface{}{}
- params["delete"] = nil
- params["encoding-type"] = "url"
- resp, err := bucket.do("POST", "", params, options, buffer, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- deletedResult := DeleteObjectVersionsResult{}
- if !dxml.Quiet {
- if err = xmlUnmarshal(resp.Body, &deletedResult); err == nil {
- err = decodeDeleteObjectsResult(&deletedResult)
- }
- }
- // Keep compatibility:need convert to struct DeleteObjectsResult
- out.XMLName = deletedResult.XMLName
- for _, v := range deletedResult.DeletedObjectsDetail {
- out.DeletedObjects = append(out.DeletedObjects, v.Key)
- }
- return out, err
- }
- // DeleteObjectVersions deletes multiple object versions.
- //
- // objectVersions the object keys and versions to delete.
- // options the options for deleting objects.
- // Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used.
- //
- // DeleteObjectVersionsResult the result object.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DeleteObjectVersions(objectVersions []DeleteObject, options ...Option) (DeleteObjectVersionsResult, error) {
- out := DeleteObjectVersionsResult{}
- dxml := deleteXML{}
- dxml.Objects = objectVersions
- isQuiet, _ := FindOption(options, deleteObjectsQuiet, false)
- dxml.Quiet = isQuiet.(bool)
- bs, err := xml.Marshal(dxml)
- if err != nil {
- return out, err
- }
- buffer := new(bytes.Buffer)
- buffer.Write(bs)
- contentType := http.DetectContentType(buffer.Bytes())
- options = append(options, ContentType(contentType))
- sum := md5.Sum(bs)
- b64 := base64.StdEncoding.EncodeToString(sum[:])
- options = append(options, ContentMD5(b64))
- params := map[string]interface{}{}
- params["delete"] = nil
- params["encoding-type"] = "url"
- resp, err := bucket.do("POST", "", params, options, buffer, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- if !dxml.Quiet {
- if err = xmlUnmarshal(resp.Body, &out); err == nil {
- err = decodeDeleteObjectsResult(&out)
- }
- }
- return out, err
- }
- // IsObjectExist checks if the object exists.
- //
- // bool flag of object's existence (true:exists; false:non-exist) when error is nil.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) IsObjectExist(objectKey string, options ...Option) (bool, error) {
- _, err := bucket.GetObjectMeta(objectKey, options...)
- if err == nil {
- return true, nil
- }
- switch err.(type) {
- case ServiceError:
- if err.(ServiceError).StatusCode == 404 {
- return false, nil
- }
- }
- return false, err
- }
- // ListObjects lists the objects under the current bucket.
- //
- // options it contains all the filters for listing objects.
- // It could specify a prefix filter on object keys, the max keys count to return and the object key marker and the delimiter for grouping object names.
- // The key marker means the returned objects' key must be greater than it in lexicographic order.
- //
- // For example, if the bucket has 8 objects, my-object-1, my-object-11, my-object-2, my-object-21,
- // my-object-22, my-object-3, my-object-31, my-object-32. If the prefix is my-object-2 (no other filters), then it returns
- // my-object-2, my-object-21, my-object-22 three objects. If the marker is my-object-22 (no other filters), then it returns
- // my-object-3, my-object-31, my-object-32 three objects. If the max keys is 5, then it returns 5 objects.
- // The three filters could be used together to achieve filter and paging functionality.
- // If the prefix is the folder name, then it could list all files under this folder (including the files under its subfolders).
- // But if the delimiter is specified with '/', then it only returns that folder's files (no subfolder's files). The direct subfolders are in the commonPrefixes properties.
- // For example, if the bucket has three objects fun/test.jpg, fun/movie/001.avi, fun/movie/007.avi. And if the prefix is "fun/", then it returns all three objects.
- // But if the delimiter is '/', then only "fun/test.jpg" is returned as files and fun/movie/ is returned as common prefix.
- //
- // For common usage scenario, check out sample/list_object.go.
- //
- // ListObjectsResult the return value after operation succeeds (only valid when error is nil).
- //
- func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
- var out ListObjectsResult
- options = append(options, EncodingType("url"))
- params, err := GetRawParams(options)
- if err != nil {
- return out, err
- }
- resp, err := bucket.do("GET", "", params, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- if err != nil {
- return out, err
- }
- err = decodeListObjectsResult(&out)
- return out, err
- }
- // Recommend to use ListObjectsV2 to replace ListObjects
- // ListOListObjectsV2bjects lists the objects under the current bucket.
- // ListObjectsResultV2 the return value after operation succeeds (only valid when error is nil).
- func (bucket Bucket) ListObjectsV2(options ...Option) (ListObjectsResultV2, error) {
- var out ListObjectsResultV2
- options = append(options, EncodingType("url"))
- options = append(options, ListType(2))
- params, err := GetRawParams(options)
- if err != nil {
- return out, err
- }
- resp, err := bucket.do("GET", "", params, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- if err != nil {
- return out, err
- }
- err = decodeListObjectsResultV2(&out)
- return out, err
- }
- // ListObjectVersions lists objects of all versions under the current bucket.
- func (bucket Bucket) ListObjectVersions(options ...Option) (ListObjectVersionsResult, error) {
- var out ListObjectVersionsResult
- options = append(options, EncodingType("url"))
- params, err := GetRawParams(options)
- if err != nil {
- return out, err
- }
- params["versions"] = nil
- resp, err := bucket.do("GET", "", params, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- if err != nil {
- return out, err
- }
- err = decodeListObjectVersionsResult(&out)
- return out, err
- }
- // SetObjectMeta sets the metadata of the Object.
- //
- // objectKey object
- // options options for setting the metadata. The valid options are CacheControl, ContentDisposition, ContentEncoding, Expires,
- // ServerSideEncryption, and custom metadata.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error {
- options = append(options, MetadataDirective(MetaReplace))
- _, err := bucket.CopyObject(objectKey, objectKey, options...)
- return err
- }
- // GetObjectDetailedMeta gets the object's detailed metadata
- //
- // objectKey object key.
- // options the constraints of the object. Only when the object meets the requirements this method will return the metadata. Otherwise returns error. Valid options are IfModifiedSince, IfUnmodifiedSince,
- // IfMatch, IfNoneMatch. For more details check out https://help.aliyun.com/document_detail/oss/api-reference/object/HeadObject.html
- //
- // http.Header object meta when error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) {
- params, _ := GetRawParams(options)
- resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- return resp.Headers, nil
- }
- // GetObjectMeta gets object metadata.
- //
- // GetObjectMeta is more lightweight than GetObjectDetailedMeta as it only returns basic metadata including ETag
- // size, LastModified. The size information is in the HTTP header Content-Length.
- //
- // objectKey object key
- //
- // http.Header the object's metadata, valid when error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.Header, error) {
- params, _ := GetRawParams(options)
- params["objectMeta"] = nil
- //resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil)
- resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- return resp.Headers, nil
- }
- // SetObjectACL updates the object's ACL.
- //
- // Only the bucket's owner could update object's ACL which priority is higher than bucket's ACL.
- // For example, if the bucket ACL is private and object's ACL is public-read-write.
- // Then object's ACL is used and it means all users could read or write that object.
- // When the object's ACL is not set, then bucket's ACL is used as the object's ACL.
- //
- // Object read operations include GetObject, HeadObject, CopyObject and UploadPartCopy on the source object;
- // Object write operations include PutObject, PostObject, AppendObject, DeleteObject, DeleteMultipleObjects,
- // CompleteMultipartUpload and CopyObject on target object.
- //
- // objectKey the target object key (to set the ACL on)
- // objectAcl object ACL. Valid options are PrivateACL, PublicReadACL, PublicReadWriteACL.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType, options ...Option) error {
- options = append(options, ObjectACL(objectACL))
- params, _ := GetRawParams(options)
- params["acl"] = nil
- resp, err := bucket.do("PUT", objectKey, params, options, nil, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
- }
- // GetObjectACL gets object's ACL
- //
- // objectKey the object to get ACL from.
- //
- // GetObjectACLResult the result object when error is nil. GetObjectACLResult.Acl is the object ACL.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObjectACL(objectKey string, options ...Option) (GetObjectACLResult, error) {
- var out GetObjectACLResult
- params, _ := GetRawParams(options)
- params["acl"] = nil
- resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- return out, err
- }
- // PutSymlink creates a symlink (to point to an existing object)
- //
- // Symlink cannot point to another symlink.
- // When creating a symlink, it does not check the existence of the target file, and does not check if the target file is symlink.
- // Neither it checks the caller's permission on the target file. All these checks are deferred to the actual GetObject call via this symlink.
- // If trying to add an existing file, as long as the caller has the write permission, the existing one will be overwritten.
- // If the x-oss-meta- is specified, it will be added as the metadata of the symlink file.
- //
- // symObjectKey the symlink object's key.
- // targetObjectKey the target object key to point to.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, options ...Option) error {
- options = append(options, symlinkTarget(url.QueryEscape(targetObjectKey)))
- params, _ := GetRawParams(options)
- params["symlink"] = nil
- resp, err := bucket.do("PUT", symObjectKey, params, options, nil, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusOK})
- }
- // GetSymlink gets the symlink object with the specified key.
- // If the symlink object does not exist, returns 404.
- //
- // objectKey the symlink object's key.
- //
- // error it's nil if no error, otherwise it's an error object.
- // When error is nil, the target file key is in the X-Oss-Symlink-Target header of the returned object.
- //
- func (bucket Bucket) GetSymlink(objectKey string, options ...Option) (http.Header, error) {
- params, _ := GetRawParams(options)
- params["symlink"] = nil
- resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- targetObjectKey := resp.Headers.Get(HTTPHeaderOssSymlinkTarget)
- targetObjectKey, err = url.QueryUnescape(targetObjectKey)
- if err != nil {
- return resp.Headers, err
- }
- resp.Headers.Set(HTTPHeaderOssSymlinkTarget, targetObjectKey)
- return resp.Headers, err
- }
- // RestoreObject restores the object from the archive storage.
- //
- // An archive object is in cold status by default and it cannot be accessed.
- // When restore is called on the cold object, it will become available for access after some time.
- // If multiple restores are called on the same file when the object is being restored, server side does nothing for additional calls but returns success.
- // By default, the restored object is available for access for one day. After that it will be unavailable again.
- // But if another RestoreObject are called after the file is restored, then it will extend one day's access time of that object, up to 7 days.
- //
- // objectKey object key to restore.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) RestoreObject(objectKey string, options ...Option) error {
- params, _ := GetRawParams(options)
- params["restore"] = nil
- resp, err := bucket.do("POST", objectKey, params, options, nil, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted})
- }
- // RestoreObjectDetail support more features than RestoreObject
- func (bucket Bucket) RestoreObjectDetail(objectKey string, restoreConfig RestoreConfiguration, options ...Option) error {
- if restoreConfig.Tier == "" {
- // Expedited, Standard, Bulk
- restoreConfig.Tier = string(RestoreStandard)
- }
- if restoreConfig.Days == 0 {
- restoreConfig.Days = 1
- }
- bs, err := xml.Marshal(restoreConfig)
- if err != nil {
- return err
- }
- buffer := new(bytes.Buffer)
- buffer.Write(bs)
- contentType := http.DetectContentType(buffer.Bytes())
- options = append(options, ContentType(contentType))
- params, _ := GetRawParams(options)
- params["restore"] = nil
- resp, err := bucket.do("POST", objectKey, params, options, buffer, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted})
- }
- // RestoreObjectXML support more features than RestoreObject
- func (bucket Bucket) RestoreObjectXML(objectKey, configXML string, options ...Option) error {
- buffer := new(bytes.Buffer)
- buffer.Write([]byte(configXML))
- contentType := http.DetectContentType(buffer.Bytes())
- options = append(options, ContentType(contentType))
- params, _ := GetRawParams(options)
- params["restore"] = nil
- resp, err := bucket.do("POST", objectKey, params, options, buffer, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted})
- }
- // SignURL signs the URL. Users could access the object directly with this URL without getting the AK.
- //
- // objectKey the target object to sign.
- // signURLConfig the configuration for the signed URL
- //
- // string returns the signed URL, when error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) SignURL(objectKey string, method HTTPMethod, expiredInSec int64, options ...Option) (string, error) {
- if expiredInSec < 0 {
- return "", fmt.Errorf("invalid expires: %d, expires must bigger than 0", expiredInSec)
- }
- expiration := time.Now().Unix() + expiredInSec
- params, err := GetRawParams(options)
- if err != nil {
- return "", err
- }
- headers := make(map[string]string)
- err = handleOptions(headers, options)
- if err != nil {
- return "", err
- }
- return bucket.Client.Conn.signURL(method, bucket.BucketName, objectKey, expiration, params, headers), nil
- }
- // PutObjectWithURL uploads an object with the URL. If the object exists, it will be overwritten.
- // PutObjectWithURL It will not generate minetype according to the key name.
- //
- // signedURL signed URL.
- // reader io.Reader the read instance for reading the data for the upload.
- // options the options for uploading the data. The valid options are CacheControl, ContentDisposition, ContentEncoding,
- // Expires, ServerSideEncryption, ObjectACL and custom metadata. Check out the following link for details:
- // https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) PutObjectWithURL(signedURL string, reader io.Reader, options ...Option) error {
- resp, err := bucket.DoPutObjectWithURL(signedURL, reader, options)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return err
- }
- // PutObjectFromFileWithURL uploads an object from a local file with the signed URL.
- // PutObjectFromFileWithURL It does not generate mimetype according to object key's name or the local file name.
- //
- // signedURL the signed URL.
- // filePath local file path, such as dirfile.txt, for uploading.
- // options options for uploading, same as the options in PutObject function.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) PutObjectFromFileWithURL(signedURL, filePath string, options ...Option) error {
- fd, err := os.Open(filePath)
- if err != nil {
- return err
- }
- defer fd.Close()
- resp, err := bucket.DoPutObjectWithURL(signedURL, fd, options)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return err
- }
- // DoPutObjectWithURL is the actual API that does the upload with URL work(internal for SDK)
- //
- // signedURL the signed URL.
- // reader io.Reader the read instance for getting the data to upload.
- // options options for uploading.
- //
- // Response the response object which contains the HTTP response.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DoPutObjectWithURL(signedURL string, reader io.Reader, options []Option) (*Response, error) {
- listener := GetProgressListener(options)
- params := map[string]interface{}{}
- resp, err := bucket.doURL("PUT", signedURL, params, options, reader, listener)
- if err != nil {
- return nil, err
- }
- if bucket.GetConfig().IsEnableCRC {
- err = CheckCRC(resp, "DoPutObjectWithURL")
- if err != nil {
- return resp, err
- }
- }
- err = CheckRespCode(resp.StatusCode, []int{http.StatusOK})
- return resp, err
- }
- // GetObjectWithURL downloads the object and returns the reader instance, with the signed URL.
- //
- // signedURL the signed URL.
- // options options for downloading the object. Valid options are IfModifiedSince, IfUnmodifiedSince, IfMatch,
- // IfNoneMatch, AcceptEncoding. For more information, check out the following link:
- // https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html
- //
- // io.ReadCloser the reader object for getting the data from response. It needs be closed after the usage. It's only valid when error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObjectWithURL(signedURL string, options ...Option) (io.ReadCloser, error) {
- result, err := bucket.DoGetObjectWithURL(signedURL, options)
- if err != nil {
- return nil, err
- }
- return result.Response, nil
- }
- // GetObjectToFileWithURL downloads the object into a local file with the signed URL.
- //
- // signedURL the signed URL
- // filePath the local file path to download to.
- // options the options for downloading object. Check out the parameter options in function GetObject for the reference.
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) GetObjectToFileWithURL(signedURL, filePath string, options ...Option) error {
- tempFilePath := filePath + TempFileSuffix
- // Get the object's content
- result, err := bucket.DoGetObjectWithURL(signedURL, options)
- if err != nil {
- return err
- }
- defer result.Response.Close()
- // If the file does not exist, create one. If exists, then overwrite it.
- fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode)
- if err != nil {
- return err
- }
- // Save the data to the file.
- _, err = io.Copy(fd, result.Response.Body)
- fd.Close()
- if err != nil {
- return err
- }
- // Compare the CRC value. If CRC values do not match, return error.
- hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
- encodeOpt, _ := FindOption(options, HTTPHeaderAcceptEncoding, nil)
- acceptEncoding := ""
- if encodeOpt != nil {
- acceptEncoding = encodeOpt.(string)
- }
- if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" {
- result.Response.ClientCRC = result.ClientCRC.Sum64()
- err = CheckCRC(result.Response, "GetObjectToFileWithURL")
- if err != nil {
- os.Remove(tempFilePath)
- return err
- }
- }
- return os.Rename(tempFilePath, filePath)
- }
- // DoGetObjectWithURL is the actual API that downloads the file with the signed URL.
- //
- // signedURL the signed URL.
- // options the options for getting object. Check out parameter options in GetObject for the reference.
- //
- // GetObjectResult the result object when the error is nil.
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) DoGetObjectWithURL(signedURL string, options []Option) (*GetObjectResult, error) {
- params, _ := GetRawParams(options)
- resp, err := bucket.doURL("GET", signedURL, params, options, nil, nil)
- if err != nil {
- return nil, err
- }
- result := &GetObjectResult{
- Response: resp,
- }
- // CRC
- var crcCalc hash.Hash64
- hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange)
- if bucket.GetConfig().IsEnableCRC && !hasRange {
- crcCalc = crc64.New(CrcTable())
- result.ServerCRC = resp.ServerCRC
- result.ClientCRC = crcCalc
- }
- // Progress
- listener := GetProgressListener(options)
- contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64)
- resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil)
- return result, nil
- }
- //
- // ProcessObject apply process on the specified image file.
- //
- // The supported process includes resize, rotate, crop, watermark, format,
- // udf, customized style, etc.
- //
- //
- // objectKey object key to process.
- // process process string, such as "image/resize,w_100|sys/saveas,o_dGVzdC5qcGc,b_dGVzdA"
- //
- // error it's nil if no error, otherwise it's an error object.
- //
- func (bucket Bucket) ProcessObject(objectKey string, process string, options ...Option) (ProcessObjectResult, error) {
- var out ProcessObjectResult
- params, _ := GetRawParams(options)
- params["x-oss-process"] = nil
- processData := fmt.Sprintf("%v=%v", "x-oss-process", process)
- data := strings.NewReader(processData)
- resp, err := bucket.do("POST", objectKey, params, nil, data, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = jsonUnmarshal(resp.Body, &out)
- return out, err
- }
- //
- // PutObjectTagging add tagging to object
- //
- // objectKey object key to add tagging
- // tagging tagging to be added
- //
- // error nil if success, otherwise error
- //
- func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging, options ...Option) error {
- bs, err := xml.Marshal(tagging)
- if err != nil {
- return err
- }
- buffer := new(bytes.Buffer)
- buffer.Write(bs)
- params, _ := GetRawParams(options)
- params["tagging"] = nil
- resp, err := bucket.do("PUT", objectKey, params, options, buffer, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return nil
- }
- //
- // GetObjectTagging get tagging of the object
- //
- // objectKey object key to get tagging
- //
- // Tagging
- // error nil if success, otherwise error
- func (bucket Bucket) GetObjectTagging(objectKey string, options ...Option) (GetObjectTaggingResult, error) {
- var out GetObjectTaggingResult
- params, _ := GetRawParams(options)
- params["tagging"] = nil
- resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- err = xmlUnmarshal(resp.Body, &out)
- return out, err
- }
- //
- // DeleteObjectTagging delete object taggging
- //
- // objectKey object key to delete tagging
- //
- // error nil if success, otherwise error
- //
- func (bucket Bucket) DeleteObjectTagging(objectKey string, options ...Option) error {
- params, _ := GetRawParams(options)
- params["tagging"] = nil
- if objectKey == "" {
- return fmt.Errorf("invalid argument: object name is empty")
- }
- resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent})
- }
- func (bucket Bucket) OptionsMethod(objectKey string, options ...Option) (http.Header, error) {
- var out http.Header
- resp, err := bucket.do("OPTIONS", objectKey, nil, options, nil, nil)
- if err != nil {
- return out, err
- }
- defer resp.Body.Close()
- out = resp.Headers
- return out, nil
- }
- // public
- func (bucket Bucket) Do(method, objectName string, params map[string]interface{}, options []Option,
- data io.Reader, listener ProgressListener) (*Response, error) {
- return bucket.do(method, objectName, params, options, data, listener)
- }
- // Private
- func (bucket Bucket) do(method, objectName string, params map[string]interface{}, options []Option,
- data io.Reader, listener ProgressListener) (*Response, error) {
- headers := make(map[string]string)
- err := handleOptions(headers, options)
- if err != nil {
- return nil, err
- }
- err = CheckBucketName(bucket.BucketName)
- if len(bucket.BucketName) > 0 && err != nil {
- return nil, err
- }
- resp, err := bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
- params, headers, data, 0, listener)
- // get response header
- respHeader, _ := FindOption(options, responseHeader, nil)
- if respHeader != nil && resp != nil {
- pRespHeader := respHeader.(*http.Header)
- *pRespHeader = resp.Headers
- }
- return resp, err
- }
- func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[string]interface{}, options []Option,
- data io.Reader, listener ProgressListener) (*Response, error) {
- headers := make(map[string]string)
- err := handleOptions(headers, options)
- if err != nil {
- return nil, err
- }
- resp, err := bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener)
- // get response header
- respHeader, _ := FindOption(options, responseHeader, nil)
- if respHeader != nil {
- pRespHeader := respHeader.(*http.Header)
- *pRespHeader = resp.Headers
- }
- return resp, err
- }
- func (bucket Bucket) GetConfig() *Config {
- return bucket.Client.Config
- }
- func AddContentType(options []Option, keys ...string) []Option {
- typ := TypeByExtension("")
- for _, key := range keys {
- typ = TypeByExtension(key)
- if typ != "" {
- break
- }
- }
- if typ == "" {
- typ = "application/octet-stream"
- }
- opts := []Option{ContentType(typ)}
- opts = append(opts, options...)
- return opts
- }
|