codekingpro/portable-devtools
114k
1# Copyright 2016 Google Inc.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15"""Transport adapter for httplib2."""16 17from __future__ import absolute_import18 19import http.client20import logging21 22from google.auth import exceptions23from google.auth import transport24import httplib225 26 27_LOGGER = logging.getLogger(__name__)28# Properties present in file-like streams / buffers.29_STREAM_PROPERTIES = ("read", "seek", "tell")30 31 32class _Response(transport.Response):33 """httplib2 transport response adapter.34 35 Args:36 response (httplib2.Response): The raw httplib2 response.37 data (bytes): The response body.38 """39 40 def __init__(self, response, data):41 self._response = response42 self._data = data43 44 @property45 def status(self):46 """int: The HTTP status code."""47 return self._response.status48 49 @property50 def headers(self):51 """Mapping[str, str]: The HTTP response headers."""52 return dict(self._response)53 54 @property55 def data(self):56 """bytes: The response body."""57 return self._data58 59 60class Request(transport.Request):61 """httplib2 request adapter.62 63 This class is used internally for making requests using various transports64 in a consistent way. If you use :class:`AuthorizedHttp` you do not need65 to construct or use this class directly.66 67 This class can be useful if you want to manually refresh a68 :class:`~google.auth.credentials.Credentials` instance::69 70 import google_auth_httplib271 import httplib272 73 http = httplib2.Http()74 request = google_auth_httplib2.Request(http)75 76 credentials.refresh(request)77 78 Args:79 http (httplib2.Http): The underlying http object to use to make80 requests.81 82 .. automethod:: __call__83 """84 85 def __init__(self, http):86 self.http = http87 88 def __call__(89 self, url, method="GET", body=None, headers=None, timeout=None, **kwargs90 ):91 """Make an HTTP request using httplib2.92 93 Args:94 url (str): The URI to be requested.95 method (str): The HTTP method to use for the request. Defaults96 to 'GET'.97 body (bytes): The payload / body in HTTP request.98 headers (Mapping[str, str]): Request headers.99 timeout (Optional[int]): The number of seconds to wait for a100 response from the server. This is ignored by httplib2 and will101 issue a warning.102 kwargs: Additional arguments passed throught to the underlying103 :meth:`httplib2.Http.request` method.104 105 Returns:106 google.auth.transport.Response: The HTTP response.107 108 Raises:109 google.auth.exceptions.TransportError: If any exception occurred.110 """111 if timeout is not None:112 _LOGGER.warning(113 "httplib2 transport does not support per-request timeout. "114 "Set the timeout when constructing the httplib2.Http instance."115 )116 117 try:118 _LOGGER.debug("Making request: %s %s", method, url)119 response, data = self.http.request(120 url, method=method, body=body, headers=headers, **kwargs121 )122 return _Response(response, data)123 # httplib2 should catch the lower http error, this is a bug and124 # needs to be fixed there. Catch the error for the meanwhile.125 except (httplib2.HttpLib2Error, http.client.HTTPException) as exc:126 raise exceptions.TransportError(exc)127 128 129def _make_default_http():130 """Returns a default httplib2.Http instance."""131 return httplib2.Http()132 133 134class AuthorizedHttp(object):135 """A httplib2 HTTP class with credentials.136 137 This class is used to perform requests to API endpoints that require138 authorization::139 140 from google.auth.transport._httplib2 import AuthorizedHttp141 142 authed_http = AuthorizedHttp(credentials)143 144 response = authed_http.request(145 'https://www.googleapis.com/storage/v1/b')146 147 This class implements :meth:`request` in the same way as148 :class:`httplib2.Http` and can usually be used just like any other149 instance of :class:``httplib2.Http`.150 151 The underlying :meth:`request` implementation handles adding the152 credentials' headers to the request and refreshing credentials as needed.153 """154 155 def __init__(156 self,157 credentials,158 http=None,159 refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,160 max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,161 ):162 """163 Args:164 credentials (google.auth.credentials.Credentials): The credentials165 to add to the request.166 http (httplib2.Http): The underlying HTTP object to167 use to make requests. If not specified, a168 :class:`httplib2.Http` instance will be constructed.169 refresh_status_codes (Sequence[int]): Which HTTP status codes170 indicate that credentials should be refreshed and the request171 should be retried.172 max_refresh_attempts (int): The maximum number of times to attempt173 to refresh the credentials and retry the request.174 """175 176 if http is None:177 http = _make_default_http()178 179 self.http = http180 self.credentials = credentials181 self._refresh_status_codes = refresh_status_codes182 self._max_refresh_attempts = max_refresh_attempts183 # Request instance used by internal methods (for example,184 # credentials.refresh).185 self._request = Request(self.http)186 187 def close(self):188 """Calls httplib2's Http.close"""189 self.http.close()190 191 def request(192 self,193 uri,194 method="GET",195 body=None,196 headers=None,197 redirections=httplib2.DEFAULT_MAX_REDIRECTS,198 connection_type=None,199 **kwargs200 ):201 """Implementation of httplib2's Http.request."""202 203 _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)204 205 # Make a copy of the headers. They will be modified by the credentials206 # and we want to pass the original headers if we recurse.207 request_headers = headers.copy() if headers is not None else {}208 209 self.credentials.before_request(self._request, method, uri, request_headers)210 211 # Check if the body is a file-like stream, and if so, save the body212 # stream position so that it can be restored in case of refresh.213 body_stream_position = None214 if all(getattr(body, stream_prop, None) for stream_prop in _STREAM_PROPERTIES):215 body_stream_position = body.tell()216 217 # Make the request.218 response, content = self.http.request(219 uri,220 method,221 body=body,222 headers=request_headers,223 redirections=redirections,224 connection_type=connection_type,225 **kwargs226 )227 228 # If the response indicated that the credentials needed to be229 # refreshed, then refresh the credentials and re-attempt the230 # request.231 # A stored token may expire between the time it is retrieved and232 # the time the request is made, so we may need to try twice.233 if (234 response.status in self._refresh_status_codes235 and _credential_refresh_attempt < self._max_refresh_attempts236 ):237 238 _LOGGER.info(239 "Refreshing credentials due to a %s response. Attempt %s/%s.",240 response.status,241 _credential_refresh_attempt + 1,242 self._max_refresh_attempts,243 )244 245 self.credentials.refresh(self._request)246 247 # Restore the body's stream position if needed.248 if body_stream_position is not None:249 body.seek(body_stream_position)250 251 # Recurse. Pass in the original headers, not our modified set.252 return self.request(253 uri,254 method,255 body=body,256 headers=headers,257 redirections=redirections,258 connection_type=connection_type,259 _credential_refresh_attempt=_credential_refresh_attempt + 1,260 **kwargs261 )262 263 return response, content264 265 def add_certificate(self, key, cert, domain, password=None):266 """Proxy to httplib2.Http.add_certificate."""267 self.http.add_certificate(key, cert, domain, password=password)268 269 @property270 def connections(self):271 """Proxy to httplib2.Http.connections."""272 return self.http.connections273 274 @connections.setter275 def connections(self, value):276 """Proxy to httplib2.Http.connections."""277 self.http.connections = value278 279 @property280 def follow_redirects(self):281 """Proxy to httplib2.Http.follow_redirects."""282 return self.http.follow_redirects283 284 @follow_redirects.setter285 def follow_redirects(self, value):286 """Proxy to httplib2.Http.follow_redirects."""287 self.http.follow_redirects = value288 289 @property290 def timeout(self):291 """Proxy to httplib2.Http.timeout."""292 return self.http.timeout293 294 @timeout.setter295 def timeout(self, value):296 """Proxy to httplib2.Http.timeout."""297 self.http.timeout = value298 299 @property300 def redirect_codes(self):301 """Proxy to httplib2.Http.redirect_codes."""302 return self.http.redirect_codes303 304 @redirect_codes.setter305 def redirect_codes(self, value):306 """Proxy to httplib2.Http.redirect_codes."""307 self.http.redirect_codes = value308 