|
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