/usr/lib/python3/dist-packages/googlecloudapis/apitools/base/py/http_wrapper.py is in python3-googlecloudapis 0.9.30+debian1-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """HTTP wrapper for apitools.
This library wraps the underlying http library we use, which is
currently httplib2.
"""
import collections
import httplib
import logging
import socket
import time
import urlparse
import httplib2
from googlecloudapis.apitools.base.py import exceptions
from googlecloudapis.apitools.base.py import util
__all__ = [
'GetHttp',
'MakeRequest',
]
# 308 and 429 don't have names in httplib.
RESUME_INCOMPLETE = 308
TOO_MANY_REQUESTS = 429
_REDIRECT_STATUS_CODES = (
httplib.MOVED_PERMANENTLY,
httplib.FOUND,
httplib.SEE_OTHER,
httplib.TEMPORARY_REDIRECT,
RESUME_INCOMPLETE,
)
class Request(object):
"""Class encapsulating the data for an HTTP request."""
def __init__(self, url='', http_method='GET', headers=None, body=''):
self.url = url
self.http_method = http_method
self.headers = headers or {}
self.__body = None
self.body = body
@property
def body(self):
return self.__body
@body.setter
def body(self, value):
self.__body = value
if value is not None:
self.headers['content-length'] = str(len(self.__body))
else:
self.headers.pop('content-length', None)
# Note: currently the order of fields here is important, since we want
# to be able to pass in the result from httplib2.request.
class Response(collections.namedtuple(
'HttpResponse', ['info', 'content', 'request_url'])):
"""Class encapsulating data for an HTTP response."""
__slots__ = ()
def __len__(self):
def ProcessContentRange(content_range):
_, _, range_spec = content_range.partition(' ')
byte_range, _, _ = range_spec.partition('/')
start, _, end = byte_range.partition('-')
return int(end) - int(start) + 1
if '-content-encoding' in self.info and 'content-range' in self.info:
# httplib2 rewrites content-length in the case of a compressed
# transfer; we can't trust the content-length header in that
# case, but we *can* trust content-range, if it's present.
return ProcessContentRange(self.info['content-range'])
elif 'content-length' in self.info:
return int(self.info.get('content-length'))
elif 'content-range' in self.info:
return ProcessContentRange(self.info['content-range'])
return len(self.content)
@property
def status_code(self):
return int(self.info['status'])
@property
def retry_after(self):
if 'retry-after' in self.info:
return int(self.info['retry-after'])
@property
def is_redirect(self):
return (self.status_code in _REDIRECT_STATUS_CODES and
'location' in self.info)
def MakeRequest(http, http_request, retries=5, redirections=5):
"""Send http_request via the given http.
This wrapper exists to handle translation between the plain httplib2
request/response types and the Request and Response types above.
This will also be the hook for error/retry handling.
Args:
http: An httplib2.Http instance, or a http multiplexer that delegates to
an underlying http, for example, HTTPMultiplexer.
http_request: A Request to send.
retries: (int, default 5) Number of retries to attempt on 5XX replies.
redirections: (int, default 5) Number of redirects to follow.
Returns:
A Response object.
Raises:
InvalidDataFromServerError: if there is no response after retries.
"""
response = None
exc = None
connection_type = None
# Handle overrides for connection types. This is used if the caller
# wants control over the underlying connection for managing callbacks
# or hash digestion.
if getattr(http, 'connections', None):
url_scheme = urlparse.urlsplit(http_request.url).scheme
if url_scheme and url_scheme in http.connections:
connection_type = http.connections[url_scheme]
for retry in xrange(retries + 1):
# Note that the str() calls here are important for working around
# some funny business with message construction and unicode in
# httplib itself. See, eg,
# http://bugs.python.org/issue11898
info = None
try:
info, content = http.request(
str(http_request.url), method=str(http_request.http_method),
body=http_request.body, headers=http_request.headers,
redirections=redirections, connection_type=connection_type)
except httplib.BadStatusLine as e:
logging.error('Caught BadStatusLine from httplib, retrying: %s', e)
exc = e
except socket.error as e:
if http_request.http_method != 'GET':
raise
logging.error('Caught socket error, retrying: %s', e)
exc = e
except httplib.IncompleteRead as e:
if http_request.http_method != 'GET':
raise
logging.error('Caught IncompleteRead error, retrying: %s', e)
exc = e
if info is not None:
response = Response(info, content, http_request.url)
if (response.status_code < 500 and
response.status_code != TOO_MANY_REQUESTS and
not response.retry_after):
break
logging.info('Retrying request to url <%s> after status code %s.',
response.request_url, response.status_code)
elif isinstance(exc, httplib.IncompleteRead):
logging.info('Retrying request to url <%s> after incomplete read.',
str(http_request.url))
else:
logging.info('Retrying request to url <%s> after connection break.',
str(http_request.url))
# TODO(user): Make this timeout configurable.
if response:
time.sleep(response.retry_after or util.CalculateWaitForRetry(retry))
else:
time.sleep(util.CalculateWaitForRetry(retry))
if response is None:
raise exceptions.InvalidDataFromServerError(
'HTTP error on final retry: %s' % exc)
return response
def GetHttp():
return httplib2.Http()
|