|
36 | 36 | import mimetypes |
37 | 37 | import os |
38 | 38 | import random |
| 39 | +import ssl |
39 | 40 | import sys |
40 | 41 | import time |
41 | 42 | import uuid |
|
61 | 62 | MAX_URI_LENGTH = 2048 |
62 | 63 |
|
63 | 64 |
|
| 65 | +def _retry_request(http, num_retries, req_type, sleep, rand, uri, method, *args, |
| 66 | + **kwargs): |
| 67 | + """Retries an HTTP request multiple times while handling errors. |
| 68 | +
|
| 69 | + If after all retries the request still fails, last error is either returned as |
| 70 | + return value (for HTTP 5xx errors) or thrown (for ssl.SSLError). |
| 71 | +
|
| 72 | + Args: |
| 73 | + http: Http object to be used to execute request. |
| 74 | + num_retries: Maximum number of retries. |
| 75 | + req_type: Type of the request (used for logging retries). |
| 76 | + sleep, rand: Functions to sleep for random time between retries. |
| 77 | + uri: URI to be requested. |
| 78 | + method: HTTP method to be used. |
| 79 | + args, kwargs: Additional arguments passed to http.request. |
| 80 | +
|
| 81 | + Returns: |
| 82 | + resp, content - Response from the http request (may be HTTP 5xx). |
| 83 | + """ |
| 84 | + resp = None |
| 85 | + for retry_num in range(num_retries + 1): |
| 86 | + if retry_num > 0: |
| 87 | + sleep(rand() * 2**retry_num) |
| 88 | + logging.warning( |
| 89 | + 'Retry #%d for %s: %s %s%s' % (retry_num, req_type, method, uri, |
| 90 | + ', following status: %d' % resp.status if resp else '')) |
| 91 | + |
| 92 | + try: |
| 93 | + resp, content = http.request(uri, method, *args, **kwargs) |
| 94 | + except ssl.SSLError: |
| 95 | + if retry_num == num_retries: |
| 96 | + raise |
| 97 | + else: |
| 98 | + continue |
| 99 | + if resp.status < 500: |
| 100 | + break |
| 101 | + |
| 102 | + return resp, content |
| 103 | + |
| 104 | + |
64 | 105 | class MediaUploadProgress(object): |
65 | 106 | """Status of a resumable upload.""" |
66 | 107 |
|
@@ -546,16 +587,9 @@ def next_chunk(self, num_retries=0): |
546 | 587 | } |
547 | 588 | http = self._request.http |
548 | 589 |
|
549 | | - for retry_num in range(num_retries + 1): |
550 | | - if retry_num > 0: |
551 | | - self._sleep(self._rand() * 2**retry_num) |
552 | | - logging.warning( |
553 | | - 'Retry #%d for media download: GET %s, following status: %d' |
554 | | - % (retry_num, self._uri, resp.status)) |
555 | | - |
556 | | - resp, content = http.request(self._uri, headers=headers) |
557 | | - if resp.status < 500: |
558 | | - break |
| 590 | + resp, content = _retry_request( |
| 591 | + http, num_retries, 'media download', self._sleep, self._rand, self._uri, |
| 592 | + 'GET', headers=headers) |
559 | 593 |
|
560 | 594 | if resp.status in [200, 206]: |
561 | 595 | if 'content-location' in resp and resp['content-location'] != self._uri: |
@@ -654,7 +688,7 @@ def __init__(self, http, postproc, uri, |
654 | 688 |
|
655 | 689 | # Pull the multipart boundary out of the content-type header. |
656 | 690 | major, minor, params = mimeparse.parse_mime_type( |
657 | | - headers.get('content-type', 'application/json')) |
| 691 | + self.headers.get('content-type', 'application/json')) |
658 | 692 |
|
659 | 693 | # The size of the non-media part of the request. |
660 | 694 | self.body_size = len(self.body or '') |
@@ -716,16 +750,9 @@ def execute(self, http=None, num_retries=0): |
716 | 750 | self.headers['content-length'] = str(len(self.body)) |
717 | 751 |
|
718 | 752 | # Handle retries for server-side errors. |
719 | | - for retry_num in range(num_retries + 1): |
720 | | - if retry_num > 0: |
721 | | - self._sleep(self._rand() * 2**retry_num) |
722 | | - logging.warning('Retry #%d for request: %s %s, following status: %d' |
723 | | - % (retry_num, self.method, self.uri, resp.status)) |
724 | | - |
725 | | - resp, content = http.request(str(self.uri), method=str(self.method), |
726 | | - body=self.body, headers=self.headers) |
727 | | - if resp.status < 500: |
728 | | - break |
| 753 | + resp, content = _retry_request( |
| 754 | + http, num_retries, 'request', self._sleep, self._rand, str(self.uri), |
| 755 | + method=str(self.method), body=self.body, headers=self.headers) |
729 | 756 |
|
730 | 757 | for callback in self.response_callbacks: |
731 | 758 | callback(resp) |
@@ -799,18 +826,9 @@ def next_chunk(self, http=None, num_retries=0): |
799 | 826 | start_headers['X-Upload-Content-Length'] = size |
800 | 827 | start_headers['content-length'] = str(self.body_size) |
801 | 828 |
|
802 | | - for retry_num in range(num_retries + 1): |
803 | | - if retry_num > 0: |
804 | | - self._sleep(self._rand() * 2**retry_num) |
805 | | - logging.warning( |
806 | | - 'Retry #%d for resumable URI request: %s %s, following status: %d' |
807 | | - % (retry_num, self.method, self.uri, resp.status)) |
808 | | - |
809 | | - resp, content = http.request(self.uri, method=self.method, |
810 | | - body=self.body, |
811 | | - headers=start_headers) |
812 | | - if resp.status < 500: |
813 | | - break |
| 829 | + resp, content = _retry_request( |
| 830 | + http, num_retries, 'resumable URI request', self._sleep, self._rand, |
| 831 | + self.uri, method=self.method, body=self.body, headers=start_headers) |
814 | 832 |
|
815 | 833 | if resp.status == 200 and 'location' in resp: |
816 | 834 | self.resumable_uri = resp['location'] |
|
0 commit comments