git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/trunk@385 830e0280-6d2a-0410-9c65-932aecc39d9d
Michal Ludvig authored on 2009/03/20 12:54:40... | ... |
@@ -1,3 +1,10 @@ |
1 |
+2009-03-20 Michal Ludvig <michal@logix.cz> |
|
2 |
+ |
|
3 |
+ * S3/S3.py: Re-sign requests before retrial to avoid |
|
4 |
+ RequestTimeTooSkewed errors on failed long-running |
|
5 |
+ uploads. |
|
6 |
+ BTW 'request' now has its own class S3Request. |
|
7 |
+ |
|
1 | 8 |
2009-03-04 Michal Ludvig <michal@logix.cz> |
2 | 9 |
|
3 | 10 |
* s3cmd, S3/Config.py, S3/S3.py: Support for --verbatim. |
... | ... |
@@ -24,6 +24,60 @@ from Config import Config |
24 | 24 |
from Exceptions import * |
25 | 25 |
from ACL import ACL |
26 | 26 |
|
27 |
+class S3Request(object): |
|
28 |
+ def __init__(self, s3, method_string, resource, headers, params = {}): |
|
29 |
+ self.s3 = s3 |
|
30 |
+ self.headers = SortedDict(headers or {}) |
|
31 |
+ self.resource = resource |
|
32 |
+ self.method_string = method_string |
|
33 |
+ self.params = params |
|
34 |
+ |
|
35 |
+ self.update_timestamp() |
|
36 |
+ self.sign() |
|
37 |
+ |
|
38 |
+ def update_timestamp(self): |
|
39 |
+ if self.headers.has_key("date"): |
|
40 |
+ del(self.headers["date"]) |
|
41 |
+ self.headers["x-amz-date"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) |
|
42 |
+ |
|
43 |
+ def format_param_str(self): |
|
44 |
+ """ |
|
45 |
+ Format URL parameters from self.params and returns |
|
46 |
+ ?parm1=val1&parm2=val2 or an empty string if there |
|
47 |
+ are no parameters. Output of this function should |
|
48 |
+ be appended directly to self.resource['uri'] |
|
49 |
+ """ |
|
50 |
+ param_str = "" |
|
51 |
+ for param in self.params: |
|
52 |
+ if self.params[param] not in (None, ""): |
|
53 |
+ param_str += "&%s=%s" % (param, self.params[param]) |
|
54 |
+ else: |
|
55 |
+ param_str += "&%s" % param |
|
56 |
+ return param_str and "?" + param_str[1:] |
|
57 |
+ |
|
58 |
+ def sign(self): |
|
59 |
+ h = self.method_string + "\n" |
|
60 |
+ h += self.headers.get("content-md5", "")+"\n" |
|
61 |
+ h += self.headers.get("content-type", "")+"\n" |
|
62 |
+ h += self.headers.get("date", "")+"\n" |
|
63 |
+ for header in self.headers.keys(): |
|
64 |
+ if header.startswith("x-amz-"): |
|
65 |
+ h += header+":"+str(self.headers[header])+"\n" |
|
66 |
+ if self.resource['bucket']: |
|
67 |
+ h += "/" + self.resource['bucket'] |
|
68 |
+ h += self.resource['uri'] |
|
69 |
+ debug("SignHeaders: " + repr(h)) |
|
70 |
+ signature = sign_string(h) |
|
71 |
+ |
|
72 |
+ self.headers["Authorization"] = "AWS "+self.s3.config.access_key+":"+signature |
|
73 |
+ |
|
74 |
+ def get_triplet(self): |
|
75 |
+ self.update_timestamp() |
|
76 |
+ self.sign() |
|
77 |
+ resource = dict(self.resource) ## take a copy |
|
78 |
+ resource['uri'] += self.format_param_str() |
|
79 |
+ return (self.method_string, resource, self.headers) |
|
80 |
+ |
|
27 | 81 |
class S3(object): |
28 | 82 |
http_methods = BidirMap( |
29 | 83 |
GET = 0x01, |
... | ... |
@@ -326,39 +380,19 @@ class S3(object): |
326 | 326 |
if extra: |
327 | 327 |
resource['uri'] += extra |
328 | 328 |
|
329 |
- if not headers: |
|
330 |
- headers = SortedDict() |
|
331 |
- |
|
332 |
- debug("headers: %s" % headers) |
|
329 |
+ method_string = S3.http_methods.getkey(S3.operations[operation] & S3.http_methods["MASK"]) |
|
333 | 330 |
|
334 |
- if headers.has_key("date"): |
|
335 |
- if not headers.has_key("x-amz-date"): |
|
336 |
- headers["x-amz-date"] = headers["date"] |
|
337 |
- del(headers["date"]) |
|
338 |
- |
|
339 |
- if not headers.has_key("x-amz-date"): |
|
340 |
- headers["x-amz-date"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) |
|
331 |
+ request = S3Request(self, method_string, resource, headers, params) |
|
341 | 332 |
|
342 |
- method_string = S3.http_methods.getkey(S3.operations[operation] & S3.http_methods["MASK"]) |
|
343 |
- signature = self.sign_headers(method_string, resource, headers) |
|
344 |
- headers["Authorization"] = "AWS "+self.config.access_key+":"+signature |
|
345 |
- param_str = "" |
|
346 |
- for param in params: |
|
347 |
- if params[param] not in (None, ""): |
|
348 |
- param_str += "&%s=%s" % (param, params[param]) |
|
349 |
- else: |
|
350 |
- param_str += "&%s" % param |
|
351 |
- if param_str != "": |
|
352 |
- resource['uri'] += "?" + param_str[1:] |
|
353 | 333 |
debug("CreateRequest: resource[uri]=" + resource['uri']) |
354 |
- return (method_string, resource, headers) |
|
334 |
+ return request |
|
355 | 335 |
|
356 | 336 |
def _fail_wait(self, retries): |
357 | 337 |
# Wait a few seconds. The more it fails the more we wait. |
358 | 338 |
return (self._max_retries - retries + 1) * 3 |
359 | 339 |
|
360 | 340 |
def send_request(self, request, body = None, retries = _max_retries): |
361 |
- method_string, resource, headers = request |
|
341 |
+ method_string, resource, headers = request.get_triplet() |
|
362 | 342 |
debug("Processing request, please wait...") |
363 | 343 |
if not headers.has_key('content-length'): |
364 | 344 |
headers['content-length'] = body and len(body) or 0 |
... | ... |
@@ -407,7 +441,7 @@ class S3(object): |
407 | 407 |
return response |
408 | 408 |
|
409 | 409 |
def send_file(self, request, file, labels, throttle = 0, retries = _max_retries): |
410 |
- method_string, resource, headers = request |
|
410 |
+ method_string, resource, headers = request.get_triplet() |
|
411 | 411 |
size_left = size_total = headers.get("content-length") |
412 | 412 |
if self.config.progress_meter: |
413 | 413 |
progress = self.config.progress_class(labels, size_total) |
... | ... |
@@ -521,7 +555,7 @@ class S3(object): |
521 | 521 |
return response |
522 | 522 |
|
523 | 523 |
def recv_file(self, request, stream, labels, start_position = 0, retries = _max_retries): |
524 |
- method_string, resource, headers = request |
|
524 |
+ method_string, resource, headers = request.get_triplet() |
|
525 | 525 |
if self.config.progress_meter: |
526 | 526 |
progress = self.config.progress_class(labels, 0) |
527 | 527 |
else: |
... | ... |
@@ -640,20 +674,6 @@ class S3(object): |
640 | 640 |
response["md5"], response["headers"]["etag"])) |
641 | 641 |
return response |
642 | 642 |
|
643 |
- def sign_headers(self, method, resource, headers): |
|
644 |
- h = method+"\n" |
|
645 |
- h += headers.get("content-md5", "")+"\n" |
|
646 |
- h += headers.get("content-type", "")+"\n" |
|
647 |
- h += headers.get("date", "")+"\n" |
|
648 |
- for header in headers.keys(): |
|
649 |
- if header.startswith("x-amz-"): |
|
650 |
- h += header+":"+str(headers[header])+"\n" |
|
651 |
- if resource['bucket']: |
|
652 |
- h += "/" + resource['bucket'] |
|
653 |
- h += resource['uri'] |
|
654 |
- debug("SignHeaders: " + repr(h)) |
|
655 |
- return sign_string(h) |
|
656 |
- |
|
657 | 643 |
@staticmethod |
658 | 644 |
def check_bucket_name(bucket, dns_strict = True): |
659 | 645 |
if dns_strict: |
... | ... |
@@ -1600,12 +1600,12 @@ if __name__ == '__main__': |
1600 | 1600 |
## Our modules |
1601 | 1601 |
## Keep them in try/except block to |
1602 | 1602 |
## detect any syntax errors in there |
1603 |
+ from S3.Exceptions import * |
|
1603 | 1604 |
from S3 import PkgInfo |
1604 | 1605 |
from S3.S3 import * |
1605 | 1606 |
from S3.Config import Config |
1606 | 1607 |
from S3.S3Uri import * |
1607 | 1608 |
from S3 import Utils |
1608 |
- from S3.Exceptions import * |
|
1609 | 1609 |
from S3.Utils import unicodise |
1610 | 1610 |
from S3.Progress import Progress |
1611 | 1611 |
from S3.CloudFront import Cmd as CfCmd |