Browse code

* S3/S3.py: Re-sign requests before retrial to avoid RequestTimeTooSkewed errors on failed long-running uploads. BTW 'request' now has its own class S3Request.

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
Showing 3 changed files
... ...
@@ -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