Left: | ||
Right: |
OLD | NEW |
---|---|
1 // | 1 // |
2 // goamz - Go packages to interact with the Amazon Web Services. | 2 // goamz - Go packages to interact with the Amazon Web Services. |
3 // | 3 // |
4 // https://wiki.ubuntu.com/goamz | 4 // https://wiki.ubuntu.com/goamz |
5 // | 5 // |
6 // Copyright (c) 2011 Canonical Ltd. | 6 // Copyright (c) 2011 Canonical Ltd. |
7 // | 7 // |
8 // Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com> | 8 // Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com> |
9 // | 9 // |
10 | 10 |
11 package s3 | 11 package s3 |
12 | 12 |
13 import ( | 13 import ( |
14 "bytes" | 14 "bytes" |
15 "encoding/xml" | 15 "encoding/xml" |
16 "fmt" | 16 "fmt" |
17 "io" | 17 "io" |
18 "io/ioutil" | 18 "io/ioutil" |
19 "launchpad.net/goamz/aws" | 19 "launchpad.net/goamz/aws" |
20 "log" | 20 "log" |
21 "net" | |
21 "net/http" | 22 "net/http" |
22 "net/http/httputil" | 23 "net/http/httputil" |
23 "net/url" | 24 "net/url" |
24 "strconv" | 25 "strconv" |
25 "strings" | 26 "strings" |
26 "time" | 27 "time" |
27 ) | 28 ) |
28 | 29 |
29 const debug = false | 30 const debug = false |
30 | 31 |
31 // The S3 type encapsulates operations with an S3 region. | 32 // The S3 type encapsulates operations with an S3 region. |
32 type S3 struct { | 33 type S3 struct { |
33 aws.Auth | 34 aws.Auth |
34 aws.Region | 35 aws.Region |
35 private byte // Reserve the right of using private data. | 36 private byte // Reserve the right of using private data. |
36 } | 37 } |
37 | 38 |
38 // The Bucket type encapsulates operations with an S3 bucket. | 39 // The Bucket type encapsulates operations with an S3 bucket. |
39 type Bucket struct { | 40 type Bucket struct { |
40 *S3 | 41 *S3 |
41 Name string | 42 Name string |
42 } | 43 } |
43 | 44 |
44 // The Owner type represents the owner of the object in an S3 bucket. | 45 // The Owner type represents the owner of the object in an S3 bucket. |
45 type Owner struct { | 46 type Owner struct { |
46 ID string | 47 ID string |
47 DisplayName string | 48 DisplayName string |
48 } | 49 } |
49 | 50 |
51 var attempts = aws.AttemptStrategy{ | |
52 Total: 5 * time.Second, | |
53 Delay: 200 * time.Millisecond, | |
54 } | |
55 | |
50 // New creates a new S3. | 56 // New creates a new S3. |
51 func New(auth aws.Auth, region aws.Region) *S3 { | 57 func New(auth aws.Auth, region aws.Region) *S3 { |
52 return &S3{auth, region, 0} | 58 return &S3{auth, region, 0} |
53 } | 59 } |
54 | 60 |
55 // Bucket returns a Bucket with the given name. | 61 // Bucket returns a Bucket with the given name. |
56 func (s3 *S3) Bucket(name string) *Bucket { | 62 func (s3 *S3) Bucket(name string) *Bucket { |
57 if s3.Region.S3BucketEndpoint != "" || s3.Region.S3LowercaseBucket { | 63 if s3.Region.S3BucketEndpoint != "" || s3.Region.S3LowercaseBucket { |
58 name = strings.ToLower(name) | 64 name = strings.ToLower(name) |
59 } | 65 } |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
101 headers: headers, | 107 headers: headers, |
102 payload: b.locationConstraint(), | 108 payload: b.locationConstraint(), |
103 } | 109 } |
104 return b.S3.query(req, nil) | 110 return b.S3.query(req, nil) |
105 } | 111 } |
106 | 112 |
107 // DelBucket removes an existing S3 bucket. All objects in the bucket must | 113 // DelBucket removes an existing S3 bucket. All objects in the bucket must |
108 // be removed before the bucket itself can be removed. | 114 // be removed before the bucket itself can be removed. |
109 // | 115 // |
110 // See http://goo.gl/GoBrY for details. | 116 // See http://goo.gl/GoBrY for details. |
111 func (b *Bucket) DelBucket() error { | 117 func (b *Bucket) DelBucket() (err error) { |
112 req := &request{ | 118 req := &request{ |
113 method: "DELETE", | 119 method: "DELETE", |
114 bucket: b.Name, | 120 bucket: b.Name, |
115 path: "/", | 121 path: "/", |
116 } | 122 } |
117 » return b.S3.query(req, nil) | 123 » for attempt := attempts.Start(); attempt.Next(); { |
118 } | 124 » » err = b.S3.query(req, nil) |
119 | 125 » » if !shouldRetry(err) || hasCode(err, "NoSuchBucket") { |
120 func hasCode(err error, code string) bool { | 126 » » » break |
121 » s3err, ok := err.(*Error) | 127 » » } |
122 » return ok && s3err.Code == code | 128 » } |
129 » return err | |
123 } | 130 } |
124 | 131 |
125 // Get retrieves an object from an S3 bucket. | 132 // Get retrieves an object from an S3 bucket. |
126 // | 133 // |
127 // See http://goo.gl/isCO7 for details. | 134 // See http://goo.gl/isCO7 for details. |
128 func (b *Bucket) Get(path string) (data []byte, err error) { | 135 func (b *Bucket) Get(path string) (data []byte, err error) { |
129 body, err := b.GetReader(path) | 136 body, err := b.GetReader(path) |
130 if err != nil { | 137 if err != nil { |
131 return nil, err | 138 return nil, err |
132 } | 139 } |
133 data, err = ioutil.ReadAll(body) | 140 data, err = ioutil.ReadAll(body) |
134 body.Close() | 141 body.Close() |
135 return data, err | 142 return data, err |
136 } | 143 } |
137 | 144 |
138 // GetReader retrieves an object from an S3 bucket. | 145 // GetReader retrieves an object from an S3 bucket. |
139 // It is the caller's responsibility to call Close on rc when | 146 // It is the caller's responsibility to call Close on rc when |
140 // finished reading. | 147 // finished reading. |
141 func (b *Bucket) GetReader(path string) (rc io.ReadCloser, err error) { | 148 func (b *Bucket) GetReader(path string) (rc io.ReadCloser, err error) { |
142 req := &request{ | 149 req := &request{ |
143 bucket: b.Name, | 150 bucket: b.Name, |
144 path: path, | 151 path: path, |
145 } | 152 } |
146 err = b.S3.prepare(req) | 153 err = b.S3.prepare(req) |
147 if err != nil { | 154 if err != nil { |
148 return nil, err | 155 return nil, err |
149 } | 156 } |
150 » resp, err := b.S3.run(req, nil) | 157 » for attempt := attempts.Start(); attempt.Next(); { |
151 » if err != nil { | 158 » » resp, err := b.S3.run(req, nil) |
152 » » return nil, err | 159 » » if shouldRetry(err) && attempt.HasNext() { |
rog
2013/02/01 10:04:01
nice idiom.
| |
160 » » » continue | |
161 » » } | |
162 » » if err != nil { | |
163 » » » return nil, err | |
164 » » } | |
165 » » return resp.Body, nil | |
153 } | 166 } |
154 » return resp.Body, nil | 167 » panic("unreachable") |
155 } | 168 } |
156 | 169 |
157 // Put inserts an object into the S3 bucket. | 170 // Put inserts an object into the S3 bucket. |
158 // | 171 // |
159 // See http://goo.gl/FEBPD for details. | 172 // See http://goo.gl/FEBPD for details. |
160 func (b *Bucket) Put(path string, data []byte, contType string, perm ACL) error { | 173 func (b *Bucket) Put(path string, data []byte, contType string, perm ACL) error { |
161 body := bytes.NewBuffer(data) | 174 body := bytes.NewBuffer(data) |
162 return b.PutReader(path, body, int64(len(data)), contType, perm) | 175 return b.PutReader(path, body, int64(len(data)), contType, perm) |
163 } | 176 } |
164 | 177 |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
283 "marker": {marker}, | 296 "marker": {marker}, |
284 } | 297 } |
285 if max != 0 { | 298 if max != 0 { |
286 params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)} | 299 params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)} |
287 } | 300 } |
288 req := &request{ | 301 req := &request{ |
289 bucket: b.Name, | 302 bucket: b.Name, |
290 params: params, | 303 params: params, |
291 } | 304 } |
292 result = &ListResp{} | 305 result = &ListResp{} |
293 » err = b.S3.query(req, result) | 306 » for attempt := attempts.Start(); attempt.Next(); { |
307 » » err = b.S3.query(req, result) | |
308 » » if !shouldRetry(err) { | |
309 » » » break | |
310 » » } | |
311 » » println("Retrying List.") | |
rog
2013/02/01 10:04:01
d
niemeyer
2013/02/01 20:21:58
Done.
| |
312 » } | |
294 if err != nil { | 313 if err != nil { |
295 return nil, err | 314 return nil, err |
296 } | 315 } |
297 return result, nil | 316 return result, nil |
298 } | 317 } |
299 | 318 |
300 // URL returns a non-signed URL that allows retriving the | 319 // URL returns a non-signed URL that allows retriving the |
301 // object at path. It only works if the object is publicly | 320 // object at path. It only works if the object is publicly |
302 // readable (see SignedURL). | 321 // readable (see SignedURL). |
303 func (b *Bucket) URL(path string) string { | 322 func (b *Bucket) URL(path string) string { |
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
494 r.Body.Close() | 513 r.Body.Close() |
495 err.StatusCode = r.StatusCode | 514 err.StatusCode = r.StatusCode |
496 if err.Message == "" { | 515 if err.Message == "" { |
497 err.Message = r.Status | 516 err.Message = r.Status |
498 } | 517 } |
499 if debug { | 518 if debug { |
500 log.Printf("err: %#v\n", err) | 519 log.Printf("err: %#v\n", err) |
501 } | 520 } |
502 return &err | 521 return &err |
503 } | 522 } |
523 | |
524 func shouldRetry(err error) bool { | |
525 if err == nil { | |
526 return false | |
527 } | |
528 switch e := err.(type) { | |
rog
2013/02/01 10:04:01
io.ErrUnexpectedEOF is one i've seen in the past,
niemeyer
2013/02/01 20:21:58
Done.
| |
529 case *net.DNSError: | |
530 return true | |
531 case *net.OpError: | |
rog
2013/02/01 10:04:01
this seems very broad. we don't want to retry for
niemeyer
2013/02/01 20:21:58
True. I'll restrict to "read" and "write".
| |
532 return true | |
533 case *Error: | |
534 switch e.Code { | |
535 case "InternalError", "NoSuchUpload", "NoSuchBucket": | |
536 return true | |
537 } | |
538 } | |
539 return false | |
540 } | |
541 | |
542 func hasCode(err error, code string) bool { | |
543 s3err, ok := err.(*Error) | |
544 return ok && s3err.Code == code | |
545 } | |
546 | |
OLD | NEW |