Skip to content

Commit dde328a

Browse files
author
Jon Wayne Parrott
authored
Add http.client transport (#10)
1 parent a686678 commit dde328a

File tree

9 files changed

+254
-0
lines changed

9 files changed

+254
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
google.auth.exceptions module
2+
=============================
3+
4+
.. automodule:: google.auth.exceptions
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

packages/google-auth/docs/reference/google.auth.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ google.auth package
66
:undoc-members:
77
:show-inheritance:
88

9+
Subpackages
10+
-----------
11+
12+
.. toctree::
13+
14+
google.auth.transport
15+
916
Submodules
1017
----------
1118

1219
.. toctree::
1320

1421
google.auth.crypt
22+
google.auth.exceptions
1523
google.auth.jwt
1624

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
google.auth.transport package
2+
=============================
3+
4+
.. automodule:: google.auth.transport
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:
8+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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 at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# 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 and
13+
# limitations under the License.
14+
15+
"""Transport adapter for http.client, for internal use only."""
16+
17+
import socket
18+
19+
from six.moves import http_client
20+
from six.moves import urllib
21+
22+
from google.auth import exceptions
23+
from google.auth import transport
24+
25+
26+
class Response(transport.Response):
27+
"""http.client transport request adapter.
28+
29+
Args:
30+
response (http.client.HTTPResponse): The raw http client response.
31+
"""
32+
def __init__(self, response):
33+
self._status = response.status
34+
self._headers = {
35+
key.lower(): value for key, value in response.getheaders()}
36+
self._data = response.read()
37+
38+
@property
39+
def status(self):
40+
return self._status
41+
42+
@property
43+
def headers(self):
44+
return self._headers
45+
46+
@property
47+
def data(self):
48+
return self._data
49+
50+
51+
class Request(transport.Request):
52+
"""http.client transport request adapter."""
53+
54+
def __call__(self, url, method='GET', body=None, headers=None,
55+
timeout=None, **kwargs):
56+
"""Make an HTTP request using http.client.
57+
58+
Args:
59+
url (str): The URI to be requested.
60+
method (str): The HTTP method to use for the request. Defaults
61+
to 'GET'.
62+
body (bytes): The payload / body in HTTP request.
63+
headers (Mapping): Request headers.
64+
timeout (Optional(int)): The number of seconds to wait for a
65+
response from the server. If not specified or if None, the
66+
socket global default timeout will be used.
67+
kwargs: Additional arguments passed throught to the underlying
68+
:meth:`~http.client.HTTPConnection.request` method.
69+
70+
Returns:
71+
Response: The HTTP response.
72+
73+
Raises:
74+
google.auth.exceptions.TransportError: If any exception occurred.
75+
"""
76+
# socket._GLOBAL_DEFAULT_TIMEOUT is the default in http.client.
77+
if timeout is None:
78+
timeout = socket._GLOBAL_DEFAULT_TIMEOUT
79+
80+
# http.client doesn't allow None as the headers argument.
81+
if headers is None:
82+
headers = {}
83+
84+
# http.client needs the host and path parts specified separately.
85+
parts = urllib.parse.urlsplit(url)
86+
path = urllib.parse.urlunsplit(
87+
('', '', parts.path, parts.query, parts.fragment))
88+
89+
if parts.scheme != 'http':
90+
raise exceptions.TransportError(
91+
'http.client transport only supports the http scheme, {}'
92+
'was specified'.format(parts.scheme))
93+
94+
connection = http_client.HTTPConnection(parts.netloc)
95+
96+
try:
97+
connection.request(
98+
method, path, body=body, headers=headers, **kwargs)
99+
response = connection.getresponse()
100+
return Response(response)
101+
except (http_client.HTTPException, socket.error) as exc:
102+
raise exceptions.TransportError(exc)
103+
finally:
104+
connection.close()

packages/google-auth/pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ disable =
115115
wrong-import-position,
116116
no-name-in-module,
117117
locally-disabled,
118+
locally-enabled,
118119
fixme
119120

120121

packages/google-auth/pylintrc.tests

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ disable =
105105
wrong-import-position,
106106
no-name-in-module,
107107
locally-disabled,
108+
locally-enabled,
108109
missing-docstring,
109110
redefined-outer-name,
110111
no-self-use,

packages/google-auth/tests/transport/__init__.py

Whitespace-only changes.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# 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 and
13+
# limitations under the License.
14+
15+
import flask
16+
import pytest
17+
from pytest_localserver.http import WSGIServer
18+
from six.moves import http_client
19+
20+
from google.auth import exceptions
21+
22+
# .invalid will never resolve, see https://tools.ietf.org/html/rfc2606
23+
NXDOMAIN = 'test.invalid'
24+
25+
26+
class RequestResponseTests(object):
27+
28+
@pytest.fixture
29+
def server(self):
30+
"""Provides a test HTTP server.
31+
32+
The test server is automatically created before
33+
a test and destroyed at the end. The server is serving a test
34+
application that can be used to verify requests.
35+
"""
36+
app = flask.Flask(__name__)
37+
app.debug = True
38+
39+
# pylint: disable=unused-variable
40+
# (pylint thinks the flask routes are unusued.)
41+
@app.route('/basic')
42+
def index():
43+
header_value = flask.request.headers.get('x-test-header', 'value')
44+
headers = {'X-Test-Header': header_value}
45+
return 'Basic Content', http_client.OK, headers
46+
47+
@app.route('/server_error')
48+
def server_error():
49+
return 'Error', http_client.INTERNAL_SERVER_ERROR
50+
# pylint: enable=unused-variable
51+
52+
server = WSGIServer(application=app.wsgi_app)
53+
server.start()
54+
yield server
55+
server.stop()
56+
57+
def test_request_basic(self, server):
58+
request = self.make_request()
59+
response = request(url=server.url + '/basic', method='GET')
60+
61+
assert response.status == http_client.OK
62+
assert response.headers['x-test-header'] == 'value'
63+
assert response.data == b'Basic Content'
64+
65+
def test_request_timeout(self, server):
66+
request = self.make_request()
67+
response = request(url=server.url + '/basic', method='GET', timeout=2)
68+
69+
assert response.status == http_client.OK
70+
assert response.headers['x-test-header'] == 'value'
71+
assert response.data == b'Basic Content'
72+
73+
def test_request_headers(self, server):
74+
request = self.make_request()
75+
response = request(
76+
url=server.url + '/basic', method='GET', headers={
77+
'x-test-header': 'hello world'})
78+
79+
assert response.status == http_client.OK
80+
assert response.headers['x-test-header'] == 'hello world'
81+
assert response.data == b'Basic Content'
82+
83+
def test_request_error(self, server):
84+
request = self.make_request()
85+
response = request(url=server.url + '/server_error', method='GET')
86+
87+
assert response.status == http_client.INTERNAL_SERVER_ERROR
88+
assert response.data == b'Error'
89+
90+
def test_connection_error(self):
91+
request = self.make_request()
92+
with pytest.raises(exceptions.TransportError):
93+
request(url='http://{}'.format(NXDOMAIN), method='GET')
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# 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 and
13+
# limitations under the License.
14+
15+
import pytest
16+
17+
from google.auth import exceptions
18+
import google.auth.transport._http_client
19+
20+
from tests.transport import compliance
21+
22+
23+
class TestRequestResponse(compliance.RequestResponseTests):
24+
def make_request(self):
25+
return google.auth.transport._http_client.Request()
26+
27+
def test_non_http(self):
28+
request = self.make_request()
29+
with pytest.raises(exceptions.TransportError) as excinfo:
30+
request(url='https://{}'.format(compliance.NXDOMAIN), method='GET')
31+
32+
assert excinfo.match('https')

0 commit comments

Comments
 (0)