├── doc └── OSS_API.pdf ├── README.md ├── .gitignore ├── example.go └── oss └── oss.go /doc/OSS_API.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanunon/oss-go-api/HEAD/doc/OSS_API.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Aliyun OSS API ## 2 | 3 | ### Setup ### 4 | 5 | go get -u github.com/yanunon/oss-go-api 6 | 7 | ### Document ### 8 | [http://godoc.org/github.com/yanunon/oss-go-api/oss](http://godoc.org/github.com/yanunon/oss-go-api/oss) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Yang Junyong 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the Google Inc. nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | * 27 | */ 28 | 29 | package main 30 | 31 | import ( 32 | "fmt" 33 | "github.com/yanunon/oss-go-api/oss" 34 | "log" 35 | "os" 36 | ) 37 | 38 | func GetService(c *oss.Client) { 39 | lar, err := c.GetService() 40 | if err != nil { 41 | log.Fatalln(err) 42 | } 43 | fmt.Printf("%+v\n", lar) 44 | } 45 | 46 | func PutBucket(c *oss.Client, bname string) { 47 | err := c.PutBucket(bname) 48 | if err != nil { 49 | log.Fatalln(err) 50 | } 51 | fmt.Println("Put bucket ok") 52 | } 53 | 54 | func GetBucket(c *oss.Client, bname string) { 55 | lbr, err := c.GetBucket(bname, "", "", "", "") 56 | if err != nil { 57 | log.Fatalln(err) 58 | } 59 | fmt.Printf("%+v\n", lbr) 60 | } 61 | 62 | func PutBucketACL(c *oss.Client, bname string, acl string) { 63 | err := c.PutBucketACL(bname, acl) 64 | if err != nil { 65 | log.Fatalln(err) 66 | } 67 | fmt.Println("Put bucket acl ok") 68 | } 69 | 70 | func GetBucketACL(c *oss.Client, bname string) { 71 | acl, err := c.GetBucketACL(bname) 72 | if err != nil { 73 | log.Fatalln(err) 74 | } 75 | fmt.Printf("%+v\n", acl) 76 | } 77 | 78 | func CopyObject(c *oss.Client, dst, src string) { 79 | err := c.CopyObject(dst, src) 80 | if err != nil { 81 | log.Fatalln(err) 82 | } 83 | fmt.Println("Copy object ok") 84 | } 85 | 86 | func DeleteObject(c *oss.Client, opath string) { 87 | err := c.DeleteObject(opath) 88 | if err != nil { 89 | log.Fatalln(err) 90 | } 91 | fmt.Println("Delete object ok") 92 | } 93 | 94 | func GetObject(c *oss.Client, fpath, opath string) { 95 | bytes, err := c.GetObject(opath, -1, -1) 96 | if err != nil { 97 | log.Fatalln(err) 98 | } 99 | 100 | file, err := os.Create(fpath) 101 | if err != nil { 102 | log.Fatalln(err) 103 | } 104 | defer file.Close() 105 | file.Write(bytes) 106 | fmt.Println("Get object ok") 107 | } 108 | 109 | func PutObject(c *oss.Client, opath, fpath string) { 110 | err := c.PutObject(opath, fpath) 111 | if err != nil { 112 | log.Fatalln(err) 113 | } 114 | fmt.Println("Put object ok") 115 | } 116 | 117 | func HeadObject(c *oss.Client, opath string) { 118 | header, err := c.HeadObject(opath) 119 | if err != nil { 120 | log.Fatalln(err) 121 | } 122 | fmt.Printf("%+v\n", header) 123 | } 124 | 125 | func DeleteMultipleObject(c *oss.Client, bname string, keys []string) { 126 | err := c.DeleteMultipleObject(bname, keys) 127 | if err != nil { 128 | log.Fatalln(err) 129 | } 130 | fmt.Println("Delete multiple object ok") 131 | } 132 | 133 | func PostObjectGroup(c *oss.Client, cfg oss.CreateFileGroup, gpath string) { 134 | cofg, err := c.PostObjectGroup(cfg, gpath) 135 | if err != nil { 136 | log.Fatalln(err) 137 | } 138 | fmt.Printf("%+v\n", cofg) 139 | } 140 | 141 | func GetObjectGroupIndex(c *oss.Client, gpath string) { 142 | fg, err := c.GetObjectGroupIndex(gpath) 143 | if err != nil { 144 | log.Fatalln(err) 145 | } 146 | fmt.Printf("%+v\n", fg) 147 | } 148 | 149 | func AbortMultipartUpload(c *oss.Client, opath, uploadId string) { 150 | err := c.AbortMultipartUpload(opath, uploadId) 151 | if err != nil { 152 | log.Fatalln(err) 153 | } 154 | fmt.Println("abort multipart upload ok") 155 | } 156 | 157 | func PutLargeObject(c *oss.Client, opath, fpath string) { 158 | err := c.PutLargeObject(opath, fpath) 159 | if err != nil { 160 | log.Fatalln(err) 161 | } 162 | fmt.Println("put large object ok") 163 | } 164 | 165 | func ListMultipartUpload(c *oss.Client, bname string) { 166 | lmur, err := c.ListMultipartUpload(bname, nil) 167 | if err != nil { 168 | log.Fatalln(err) 169 | } 170 | fmt.Printf("%+v\n", lmur) 171 | } 172 | 173 | func ListParts(c *oss.Client, opath, uploadId string) { 174 | lpr, err := c.ListParts(opath, uploadId) 175 | if err != nil { 176 | log.Fatalln(err) 177 | } 178 | fmt.Printf("%+v\n", lpr) 179 | } 180 | 181 | func main() { 182 | c := oss.NewClient("storage.aliyun.com", "ACCESS_ID", "ACCESS_KEY", 10) 183 | } 184 | -------------------------------------------------------------------------------- /oss/oss.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, Yang Junyong 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the Google Inc. nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | * 27 | */ 28 | 29 | // Package Aliyun OSS API. 30 | // 31 | package oss 32 | 33 | import ( 34 | "bytes" 35 | "crypto/hmac" 36 | "crypto/md5" 37 | "crypto/sha1" 38 | "encoding/base64" 39 | "encoding/xml" 40 | "errors" 41 | "fmt" 42 | "hash" 43 | "io" 44 | "io/ioutil" 45 | "net/http" 46 | "os" 47 | "sort" 48 | "strconv" 49 | "strings" 50 | "sync" 51 | "time" 52 | ) 53 | 54 | const ( 55 | ACL_PUBLIC_RW = "public-read-write" 56 | ACL_PUBLIC_R = "public-read" 57 | ACL_PRIVATE = "private" 58 | ) 59 | 60 | type AccessControlList struct { 61 | Grant string 62 | } 63 | type AccessControlPolicy struct { 64 | Owner Owner 65 | AccessControlList AccessControlList 66 | } 67 | 68 | type Bucket struct { 69 | Name string 70 | CreationDate string 71 | } 72 | 73 | type CompleteMultipartUpload struct { 74 | Part []Multipart 75 | } 76 | 77 | type Multipart struct { 78 | PartNumber int 79 | ETag string 80 | } 81 | 82 | type CompleteMultipartUploadResult struct { 83 | Location string 84 | Bucket string 85 | ETag string 86 | Key string 87 | } 88 | 89 | type FileGroup struct { 90 | Bucket string 91 | Key string 92 | ETag string 93 | FileLength int 94 | FilePart CreateFileGroup 95 | } 96 | 97 | type CreateFileGroup struct { 98 | Part []GroupPart 99 | } 100 | 101 | type CompleteFileGroup struct { 102 | Bucket string 103 | Key string 104 | Size int 105 | ETag string 106 | } 107 | 108 | //GroupPart's partname should be the object's key and ETag is the same with the object's md5sum. 109 | type GroupPart struct { 110 | PartNumber int 111 | PartName string 112 | PartSize int 113 | ETag string 114 | } 115 | 116 | type Client struct { 117 | AccessID string 118 | AccessKey string 119 | Host string 120 | HttpClient *http.Client 121 | FileIOLocker sync.Mutex 122 | ChanNum int 123 | } 124 | 125 | type Buckets struct { 126 | Bucket []Bucket 127 | } 128 | 129 | type initMultipartUploadResult struct { 130 | Bucket string 131 | Key string 132 | UploadId string 133 | } 134 | 135 | type ListAllMyBucketsResult struct { 136 | Owner Owner 137 | Buckets Buckets 138 | } 139 | 140 | type ListBucketResult struct { 141 | Name string 142 | Prefix string 143 | Marker string 144 | NextMarker string 145 | MaxKeys int 146 | Delimiter string 147 | IsTruncated bool 148 | Contents []Object 149 | } 150 | 151 | type Object struct { 152 | Key string 153 | LastModified string 154 | ETag string 155 | Type string 156 | Size int 157 | StorageClass string 158 | Owner Owner 159 | } 160 | 161 | type Owner struct { 162 | ID string 163 | DisplayName string 164 | } 165 | 166 | type ListMultipartUploadResult struct { 167 | Bucket string 168 | KeyMarker string 169 | UploadIdMarker string 170 | NextKeyMarker string 171 | NextUploadIdMarker string 172 | Delimiter string 173 | Prefix string 174 | MaxUploads int 175 | IsTruncated bool 176 | Upload []UploadPart 177 | } 178 | 179 | type UploadPart struct { 180 | Key string 181 | UploadId string 182 | Initiated string 183 | } 184 | 185 | type UploadedPart struct { 186 | PartNumber int 187 | LastModified string 188 | ETag string 189 | Size int 190 | } 191 | 192 | type ListPartsResult struct { 193 | Bucket string 194 | Key string 195 | UploadId string 196 | NextPartNumberMarker string 197 | MaxParts int 198 | IsTruncated bool 199 | Part []UploadedPart 200 | } 201 | 202 | type valSorter struct { 203 | Keys []string 204 | Vals []string 205 | } 206 | 207 | type multipartSorter struct { 208 | Parts []Multipart 209 | } 210 | 211 | //NewClient returns a new Client given a Host, AccessID and AccessKey. 212 | //ChanNum is the number of goroutines used in multipart upload. 213 | func NewClient(host, accessId, accessKey string, channum int) *Client { 214 | client := Client{ 215 | Host: host, 216 | AccessID: accessId, 217 | AccessKey: accessKey, 218 | HttpClient: http.DefaultClient, 219 | ChanNum: channum, 220 | } 221 | return &client 222 | } 223 | 224 | func (c *Client) signHeader(req *http.Request, canonicalizedResource string) { 225 | //format x-oss- 226 | tmpParams := make(map[string]string) 227 | 228 | for k, v := range req.Header { 229 | if strings.HasPrefix(strings.ToLower(k), "x-oss-") { 230 | tmpParams[strings.ToLower(k)] = v[0] 231 | } 232 | } 233 | //sort 234 | vs := newValSorter(tmpParams) 235 | vs.Sort() 236 | 237 | canonicalizedOSSHeaders := "" 238 | for i := range vs.Keys { 239 | canonicalizedOSSHeaders += vs.Keys[i] + ":" + vs.Vals[i] + "\n" 240 | } 241 | 242 | date := req.Header.Get("Date") 243 | contentType := req.Header.Get("Content-Type") 244 | contentMd5 := req.Header.Get("Content-Md5") 245 | 246 | signStr := req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource 247 | h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(c.AccessKey)) //sha1.New() 248 | io.WriteString(h, signStr) 249 | signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) 250 | authorizationStr := "OSS " + c.AccessID + ":" + signedStr 251 | //fmt.Println(authorizationStr) 252 | req.Header.Set("Authorization", authorizationStr) 253 | } 254 | 255 | func (c *Client) doRequest(method, path, canonicalizedResource string, params map[string]string, data io.Reader) (resp *http.Response, err error) { 256 | reqUrl := "http://" + c.Host + path 257 | req, _ := http.NewRequest(method, reqUrl, data) 258 | date := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") 259 | req.Header.Set("Date", date) 260 | req.Header.Set("Host", c.Host) 261 | 262 | if params != nil { 263 | for k, v := range params { 264 | req.Header.Set(k, v) 265 | } 266 | } 267 | 268 | if data != nil { 269 | req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength))) 270 | } 271 | c.signHeader(req, canonicalizedResource) 272 | resp, err = c.HttpClient.Do(req) 273 | return 274 | } 275 | 276 | //Get bucket list. Return a ListAllMyBucketsResult object. 277 | func (c *Client) GetService() (lar ListAllMyBucketsResult, err error) { 278 | resp, err := c.doRequest("GET", "/", "/", nil, nil) 279 | if err != nil { 280 | return 281 | } 282 | 283 | body, _ := ioutil.ReadAll(resp.Body) 284 | defer resp.Body.Close() 285 | 286 | if resp.StatusCode != 200 { 287 | err = errors.New(resp.Status) 288 | fmt.Println(string(body)) 289 | return 290 | } 291 | 292 | err = xml.Unmarshal(body, &lar) 293 | return 294 | } 295 | 296 | //Create a new bucket with a name. 297 | func (c *Client) PutBucket(bname string) (err error) { 298 | reqStr := "/" + bname 299 | resp, err := c.doRequest("PUT", reqStr, reqStr, nil, nil) 300 | if err != nil { 301 | return 302 | } 303 | 304 | if resp.StatusCode != 200 { 305 | err = errors.New(resp.Status) 306 | body, _ := ioutil.ReadAll(resp.Body) 307 | defer resp.Body.Close() 308 | fmt.Println(string(body)) 309 | } 310 | return 311 | } 312 | 313 | //Set bucket access control list. 314 | func (c *Client) PutBucketACL(bname, acl string) (err error) { 315 | params := map[string]string{"x-oss-acl": acl} 316 | reqStr := "/" + bname 317 | resp, err := c.doRequest("PUT", reqStr, reqStr, params, nil) 318 | if err != nil { 319 | return 320 | } 321 | 322 | if resp.StatusCode != 200 { 323 | err = errors.New(resp.Status) 324 | body, _ := ioutil.ReadAll(resp.Body) 325 | defer resp.Body.Close() 326 | fmt.Println(string(body)) 327 | } 328 | return 329 | } 330 | 331 | //Get bucket's object list. 332 | func (c *Client) GetBucket(bname, prefix, marker, delimiter, maxkeys string) (lbr ListBucketResult, err error) { 333 | reqStr := "/" + bname 334 | resStr := reqStr 335 | query := map[string]string{} 336 | if prefix != "" { 337 | query["prefix"] = prefix 338 | } 339 | 340 | if marker != "" { 341 | query["marker"] = marker 342 | } 343 | 344 | if delimiter != "" { 345 | query["delimiter"] = delimiter 346 | } 347 | 348 | if maxkeys != "" { 349 | query["max-keys"] = maxkeys 350 | } 351 | 352 | if len(query) > 0 { 353 | reqStr += "?" 354 | for k, v := range query { 355 | reqStr += k + "=" + v + "&" 356 | } 357 | } 358 | 359 | resp, err := c.doRequest("GET", reqStr, resStr, nil, nil) 360 | if err != nil { 361 | return 362 | } 363 | 364 | body, _ := ioutil.ReadAll(resp.Body) 365 | defer resp.Body.Close() 366 | 367 | if resp.StatusCode != 200 { 368 | err = errors.New(resp.Status) 369 | fmt.Println(string(body)) 370 | return 371 | } 372 | err = xml.Unmarshal(body, &lbr) 373 | return 374 | } 375 | 376 | //Get bucket's access control list. 377 | func (c *Client) GetBucketACL(bname string) (acl AccessControlPolicy, err error) { 378 | reqStr := "/" + bname + "?acl" 379 | resp, err := c.doRequest("GET", reqStr, reqStr, nil, nil) 380 | if err != nil { 381 | return 382 | } 383 | 384 | body, _ := ioutil.ReadAll(resp.Body) 385 | defer resp.Body.Close() 386 | 387 | if resp.StatusCode != 200 { 388 | err = errors.New(resp.Status) 389 | fmt.Println(string(body)) 390 | return 391 | } 392 | 393 | err = xml.Unmarshal(body, &acl) 394 | return 395 | } 396 | 397 | //Delete bucket by name. 398 | func (c *Client) DeleteBucket(bname string) (err error) { 399 | return c.DeleteObject(bname) 400 | } 401 | 402 | //Copy object from path src to dst. The format of path is "/bucketName/objectName". 403 | func (c *Client) CopyObject(dst, src string) (err error) { 404 | if strings.HasPrefix(src, "/") == false { 405 | src = "/" + src 406 | } 407 | if strings.HasPrefix(dst, "/") == false { 408 | dst = "/" + dst 409 | } 410 | params := map[string]string{"x-oss-copy-source": src} 411 | resp, err := c.doRequest("PUT", dst, dst, params, nil) 412 | if err != nil { 413 | return 414 | } 415 | 416 | if resp.StatusCode != 200 { 417 | err = errors.New(resp.Status) 418 | body, _ := ioutil.ReadAll(resp.Body) 419 | defer resp.Body.Close() 420 | fmt.Println(string(body)) 421 | } 422 | return 423 | } 424 | 425 | //Delete object by its path. The format of path is "/bucketName/objectName". 426 | func (c *Client) DeleteObject(opath string) (err error) { 427 | if strings.HasPrefix(opath, "/") == false { 428 | opath = "/" + opath 429 | } 430 | resp, err := c.doRequest("DELETE", opath, opath, nil, nil) 431 | if err != nil { 432 | return 433 | } 434 | 435 | if resp.StatusCode != 204 { 436 | err = errors.New(resp.Status) 437 | body, _ := ioutil.ReadAll(resp.Body) 438 | defer resp.Body.Close() 439 | fmt.Println(string(body)) 440 | } 441 | return 442 | } 443 | 444 | //Download object by its path. The format of path is "/bucketName/objectName". 445 | //If rangeStart > -1 and rangeEnd > -1, download the object partially. 446 | //Return the object's byte array. 447 | func (c *Client) GetObject(opath string, rangeStart, rangeEnd int) (obytes []byte, err error) { 448 | if strings.HasPrefix(opath, "/") == false { 449 | opath = "/" + opath 450 | } 451 | 452 | params := map[string]string{} 453 | if rangeStart > -1 && rangeEnd > -1 { 454 | params["range"] = "bytes=" + strconv.Itoa(rangeStart) + "-" + strconv.Itoa(rangeEnd) 455 | } 456 | 457 | resp, err := c.doRequest("GET", opath, opath, params, nil) 458 | if err != nil { 459 | return 460 | } 461 | 462 | body, _ := ioutil.ReadAll(resp.Body) 463 | defer resp.Body.Close() 464 | 465 | if resp.StatusCode != 200 && resp.StatusCode != 206 { 466 | err = errors.New(resp.Status) 467 | fmt.Println(string(body)) 468 | return 469 | } 470 | //fmt.Println(string(body)) 471 | obytes = body 472 | return 473 | } 474 | 475 | //Upload object by its remote path and local file path. The format of remote path is "/bucketName/objectName". 476 | func (c *Client) PutObject(opath string, filepath string) (err error) { 477 | if strings.HasPrefix(opath, "/") == false { 478 | opath = "/" + opath 479 | } 480 | 481 | //reqUrl := "http://" + c.Host + opath 482 | buffer := new(bytes.Buffer) 483 | 484 | fh, err := os.Open(filepath) 485 | if err != nil { 486 | return 487 | } 488 | defer fh.Close() 489 | io.Copy(buffer, fh) 490 | 491 | contentType := http.DetectContentType(buffer.Bytes()) 492 | params := map[string]string{} 493 | params["Content-Type"] = contentType 494 | 495 | resp, err := c.doRequest("PUT", opath, opath, params, buffer) 496 | if err != nil { 497 | return 498 | } 499 | 500 | body, _ := ioutil.ReadAll(resp.Body) 501 | defer resp.Body.Close() 502 | 503 | if resp.StatusCode != 200 { 504 | err = errors.New(resp.Status) 505 | fmt.Println(string(body)) 506 | return 507 | } 508 | return 509 | } 510 | 511 | //Get object's meta information by its path. The format of remote path is "/bucketName/objectName". 512 | func (c *Client) HeadObject(opath string) (header http.Header, err error) { 513 | if strings.HasPrefix(opath, "/") == false { 514 | opath = "/" + opath 515 | } 516 | resp, err := c.doRequest("HEAD", opath, opath, nil, nil) 517 | if err != nil { 518 | return 519 | } 520 | 521 | if resp.StatusCode != 200 { 522 | err = errors.New(resp.Status) 523 | return 524 | } 525 | header = resp.Header 526 | return 527 | } 528 | 529 | type deleteObj struct { 530 | //XMLName xml.Name `xml:"Object"` 531 | Key string 532 | } 533 | 534 | type deleteList struct { 535 | XMLName xml.Name `xml:"Delete"` 536 | Object []deleteObj 537 | Quiet bool 538 | } 539 | 540 | //Delete multiple objects by bucket name and keys. 541 | func (c *Client) DeleteMultipleObject(bname string, keys []string) (err error) { 542 | dl := deleteList{} 543 | for _, v := range keys { 544 | dl.Object = append(dl.Object, deleteObj{v}) 545 | } 546 | dl.Quiet = true 547 | 548 | bs, err := xml.Marshal(dl) 549 | if err != nil { 550 | return 551 | } 552 | 553 | reqStr := "/" + bname + "?delete" 554 | buffer := new(bytes.Buffer) 555 | buffer.Write(bs) 556 | 557 | h := md5.New() 558 | h.Write(bs) 559 | md5sum := base64.StdEncoding.EncodeToString(h.Sum(nil)) 560 | params := map[string]string{} 561 | params["Content-MD5"] = md5sum 562 | 563 | resp, err := c.doRequest("POST", reqStr, reqStr, params, buffer) 564 | if err != nil { 565 | return 566 | } 567 | 568 | body, _ := ioutil.ReadAll(resp.Body) 569 | defer resp.Body.Close() 570 | 571 | if resp.StatusCode != 200 { 572 | err = errors.New(resp.Status) 573 | fmt.Println(string(body)) 574 | return 575 | } 576 | return 577 | } 578 | 579 | type muJob struct { 580 | File *os.File 581 | Start int 582 | Length int 583 | Idx int 584 | Opath string 585 | UploadId string 586 | } 587 | 588 | func (c *Client) initMultipartUpload(opath string) (imur initMultipartUploadResult, err error) { 589 | resp, err := c.doRequest("POST", opath+"?uploads", opath+"?uploads", nil, nil) 590 | if err != nil { 591 | return 592 | } 593 | 594 | body, _ := ioutil.ReadAll(resp.Body) 595 | defer resp.Body.Close() 596 | 597 | if resp.StatusCode != 200 { 598 | err = errors.New(resp.Status) 599 | fmt.Println(string(body)) 600 | return 601 | } 602 | 603 | err = xml.Unmarshal(body, &imur) 604 | return 605 | } 606 | 607 | func (c *Client) uploadDoWork(job *muJob) (part Multipart, err error) { 608 | buffer := new(bytes.Buffer) 609 | c.FileIOLocker.Lock() 610 | job.File.Seek(int64(job.Start), 0) 611 | io.CopyN(buffer, job.File, int64(job.Length)) 612 | c.FileIOLocker.Unlock() 613 | h := md5.New() 614 | h.Write(buffer.Bytes()) 615 | md5sum := fmt.Sprintf("%x", h.Sum(nil)) 616 | md5sum = "\"" + strings.ToUpper(md5sum) + "\"" 617 | 618 | reqStr := job.Opath + "?partNumber=" + strconv.Itoa(job.Idx) + "&uploadId=" + job.UploadId 619 | 620 | resp, err := c.doRequest("PUT", reqStr, reqStr, nil, buffer) 621 | if err != nil { 622 | fmt.Println(err) 623 | return 624 | } 625 | 626 | if resp.StatusCode != 200 { 627 | err = errors.New(resp.Status) 628 | body, _ := ioutil.ReadAll(resp.Body) 629 | defer resp.Body.Close() 630 | fmt.Printf("resp status:%s\n", err) 631 | fmt.Println(string(body)) 632 | return 633 | } 634 | 635 | ETag := resp.Header.Get("ETag") 636 | if ETag != md5sum { 637 | fmt.Printf("ETag:%s != md5sum %s\n", ETag, md5sum) 638 | } 639 | part.ETag = ETag 640 | part.PartNumber = job.Idx 641 | return 642 | } 643 | 644 | func (c *Client) uploadWorker(jobs chan muJob, finishes chan Multipart, endWorker chan int) { 645 | var job muJob 646 | for { 647 | select { 648 | case job = <-jobs: 649 | part, _ := c.uploadDoWork(&job) 650 | finishes <- part 651 | case <-endWorker: 652 | break 653 | default: 654 | time.Sleep(100 * time.Millisecond) 655 | } 656 | } 657 | } 658 | 659 | func (c *Client) uploadPart(imur initMultipartUploadResult, opath, filepath string) (cmu CompleteMultipartUpload, err error) { 660 | file, err := os.Open(filepath) 661 | if err != nil { 662 | return 663 | } 664 | 665 | buffer_len := 5 << 20 666 | fi, err := file.Stat() 667 | if err != nil { 668 | return 669 | } 670 | file_len := int(fi.Size()) 671 | jobs_num := (file_len + buffer_len - 1) / buffer_len 672 | //fmt.Printf("jobs_num:%d\n", jobs_num) 673 | 674 | jobs := make(chan muJob, jobs_num) 675 | finishes := make(chan Multipart, jobs_num) 676 | endWorker := make(chan int, c.ChanNum) 677 | //start go 678 | for i := 0; i < c.ChanNum; i++ { 679 | go c.uploadWorker(jobs, finishes, endWorker) 680 | } 681 | 682 | //add job 683 | for i := 0; i < jobs_num; i++ { 684 | var job muJob 685 | if i == jobs_num-1 { 686 | last_len := file_len - buffer_len*i 687 | job = muJob{file, i * buffer_len, last_len, i + 1, opath, imur.UploadId} 688 | } else { 689 | job = muJob{file, i * buffer_len, buffer_len, i + 1, opath, imur.UploadId} 690 | } 691 | jobs <- job 692 | } 693 | 694 | //get finished 695 | for i := 0; i < jobs_num; i++ { 696 | var part Multipart 697 | part = <-finishes 698 | cmu.Part = append(cmu.Part, part) 699 | } 700 | mps := multipartSorter{cmu.Part} 701 | mps.Sort() 702 | cmu.Part = mps.Parts 703 | //end go 704 | for i := 0; i < c.ChanNum; i++ { 705 | endWorker <- i 706 | } 707 | return 708 | 709 | } 710 | 711 | func (c *Client) completeMultipartUpload(cmu CompleteMultipartUpload, opath, uploadId string) (cmur CompleteMultipartUploadResult, err error) { 712 | bs, err := xml.Marshal(cmu) 713 | if err != nil { 714 | return 715 | } 716 | 717 | reqStr := opath + "?uploadId=" + uploadId 718 | 719 | buffer := new(bytes.Buffer) 720 | buffer.Write(bs) 721 | //fmt.Println(string(bs)) 722 | 723 | resp, err := c.doRequest("POST", reqStr, reqStr, nil, buffer) 724 | if err != nil { 725 | return 726 | } 727 | 728 | body, _ := ioutil.ReadAll(resp.Body) 729 | defer resp.Body.Close() 730 | 731 | if resp.StatusCode != 200 { 732 | err = errors.New(resp.Status) 733 | fmt.Println(string(body)) 734 | return 735 | } 736 | err = xml.Unmarshal(body, &cmur) 737 | return 738 | } 739 | 740 | //Upload large object. 741 | func (c *Client) PutLargeObject(opath string, filepath string) (err error) { 742 | if strings.HasPrefix(opath, "/") == false { 743 | opath = "/" + opath 744 | } 745 | 746 | imur, err := c.initMultipartUpload(opath) 747 | //fmt.Printf("%+v\n", imur) 748 | imu, err := c.uploadPart(imur, opath, filepath) 749 | if err != nil { 750 | fmt.Println(err) 751 | return 752 | } 753 | 754 | _, err = c.completeMultipartUpload(imu, opath, imur.UploadId) 755 | return 756 | 757 | } 758 | 759 | //Abort multipart upload by its path and uploadID. 760 | func (c *Client) AbortMultipartUpload(opath, uploadId string) (err error) { 761 | if strings.HasPrefix(opath, "/") == false { 762 | opath = "/" + opath 763 | } 764 | 765 | reqStr := opath + "?uploadId=" + uploadId 766 | resp, err := c.doRequest("DELETE", reqStr, reqStr, nil, nil) 767 | if err != nil { 768 | return 769 | } 770 | 771 | if resp.StatusCode != 204 { 772 | err = errors.New(resp.Status) 773 | body, _ := ioutil.ReadAll(resp.Body) 774 | defer resp.Body.Close() 775 | fmt.Println(string(body)) 776 | } 777 | return 778 | } 779 | 780 | //List uncompleted multipart upload in a bucket which named bname. 781 | //The params is additional, it can be:"prefix","marker","delimiter","upload-id-marker","max-keys". 782 | //Return an object of ListMultipartUploadResult. 783 | func (c *Client) ListMultipartUpload(bname string, params map[string]string) (lmur ListMultipartUploadResult, err error) { 784 | if strings.HasPrefix(bname, "/") == false { 785 | bname = "/" + bname 786 | } 787 | 788 | reqStr := bname + "?uploads" 789 | if params != nil { 790 | for k, v := range params { 791 | reqStr += "&" + k + "=" + v 792 | } 793 | } 794 | 795 | resp, err := c.doRequest("GET", reqStr, reqStr, nil, nil) 796 | if err != nil { 797 | return 798 | } 799 | 800 | body, _ := ioutil.ReadAll(resp.Body) 801 | defer resp.Body.Close() 802 | 803 | if resp.StatusCode != 200 { 804 | err = errors.New(resp.Status) 805 | fmt.Println(string(body)) 806 | return 807 | } 808 | //fmt.Println(string(body)) 809 | err = xml.Unmarshal(body, &lmur) 810 | return 811 | } 812 | 813 | //List uploaded parts for a multipart uploading. 814 | func (c *Client) ListParts(opath, uploadId string) (lpr ListPartsResult, err error) { 815 | if strings.HasPrefix(opath, "/") == false { 816 | opath = "/" + opath 817 | } 818 | 819 | reqStr := opath + "?uploadId=" + uploadId 820 | resp, err := c.doRequest("GET", reqStr, reqStr, nil, nil) 821 | if err != nil { 822 | return 823 | } 824 | 825 | body, _ := ioutil.ReadAll(resp.Body) 826 | defer resp.Body.Close() 827 | 828 | if resp.StatusCode != 200 { 829 | err = errors.New(resp.Status) 830 | fmt.Println(string(body)) 831 | return 832 | } 833 | 834 | err = xml.Unmarshal(body, &lpr) 835 | return 836 | } 837 | 838 | //Put the CreateFileGroup to the server. 839 | //The format of path is "/bucketName/objectGroupName". 840 | //Return an object of CompleteFileGroup. 841 | //Notice:objects and objectgroup should in the same bucket. 842 | func (c *Client) PostObjectGroup(cfg CreateFileGroup, gpath string) (completefg CompleteFileGroup, err error) { 843 | //part := []GroupPart{{1, "11", "111"}, {2, "22", "222"}, {3, "33", "333"}} 844 | //fg := CreateFileGroup{Part:part} 845 | bs, err := xml.Marshal(cfg) 846 | if err != nil { 847 | return 848 | } 849 | 850 | if strings.HasPrefix(gpath, "/") == false { 851 | gpath = "/" + gpath 852 | } 853 | 854 | reqStr := gpath + "?group" 855 | buffer := new(bytes.Buffer) 856 | buffer.Write(bs) 857 | 858 | resp, err := c.doRequest("POST", reqStr, reqStr, nil, buffer) 859 | if err != nil { 860 | return 861 | } 862 | 863 | body, _ := ioutil.ReadAll(resp.Body) 864 | defer resp.Body.Close() 865 | 866 | if resp.StatusCode != 200 { 867 | err = errors.New(resp.Status) 868 | fmt.Println(string(body)) 869 | return 870 | } 871 | err = xml.Unmarshal(body, &completefg) 872 | return 873 | } 874 | 875 | //Get objectgroup's index by its path. 876 | //The format of path is "/bucketName/objectGroupName". 877 | //Return an object of FileGroup 878 | func (c *Client) GetObjectGroupIndex(gpath string) (fg FileGroup, err error) { 879 | params := map[string]string{"x-oss-file-group": ""} 880 | if strings.HasPrefix(gpath, "/") == false { 881 | gpath = "/" + gpath 882 | } 883 | resp, err := c.doRequest("GET", gpath, gpath, params, nil) 884 | if err != nil { 885 | return 886 | } 887 | 888 | body, _ := ioutil.ReadAll(resp.Body) 889 | defer resp.Body.Close() 890 | 891 | if resp.StatusCode != 200 { 892 | err = errors.New(resp.Status) 893 | fmt.Println(string(body)) 894 | return 895 | } 896 | 897 | err = xml.Unmarshal(body, &fg) 898 | return 899 | } 900 | 901 | //Get objectgroup by its path. The format of path is "/bucketName/objectGroupName". 902 | //The usage is the same as GetObject. 903 | func (c *Client) GetObjectGroup(gpath string, rangeStart, rangeEnd int) (obytes []byte, err error) { 904 | return c.GetObject(gpath, rangeStart, rangeEnd) 905 | } 906 | 907 | //Get objectgroup's meta information by its path. The format of path is "/bucketName/objectGroupName". 908 | func (c *Client) HeadObjectGroup(gpath string) (header http.Header, err error) { 909 | return c.HeadObject(gpath) 910 | } 911 | 912 | //Delete objectgroup by its path. The format of path is "/bucketName/objectGroupName". 913 | func (c *Client) DeleteObjectGroup(gpath string) (err error) { 914 | return c.DeleteObject(gpath) 915 | } 916 | 917 | func newValSorter(m map[string]string) *valSorter { 918 | vs := &valSorter{ 919 | Keys: make([]string, 0, len(m)), 920 | Vals: make([]string, 0, len(m)), 921 | } 922 | 923 | for k, v := range m { 924 | vs.Keys = append(vs.Keys, k) 925 | vs.Vals = append(vs.Vals, v) 926 | } 927 | return vs 928 | } 929 | 930 | func (vs *valSorter) Sort() { 931 | sort.Sort(vs) 932 | } 933 | 934 | func (vs *valSorter) Len() int { 935 | return len(vs.Vals) 936 | } 937 | 938 | func (vs *valSorter) Less(i, j int) bool { 939 | return bytes.Compare([]byte(vs.Keys[i]), []byte(vs.Keys[j])) < 0 940 | } 941 | 942 | func (vs *valSorter) Swap(i, j int) { 943 | vs.Vals[i], vs.Vals[j] = vs.Vals[j], vs.Vals[i] 944 | vs.Keys[i], vs.Keys[j] = vs.Keys[j], vs.Keys[i] 945 | } 946 | 947 | func (ms *multipartSorter) Sort() { 948 | sort.Sort(ms) 949 | } 950 | 951 | func (ms *multipartSorter) Len() int { 952 | return len(ms.Parts) 953 | } 954 | 955 | func (ms *multipartSorter) Less(i, j int) bool { 956 | return ms.Parts[i].PartNumber < ms.Parts[j].PartNumber 957 | } 958 | 959 | func (ms *multipartSorter) Swap(i, j int) { 960 | ms.Parts[i], ms.Parts[j] = ms.Parts[j], ms.Parts[i] 961 | } 962 | --------------------------------------------------------------------------------