Skip to content

Commit fe22a15

Browse files
committed
Change the App Engine specific connection objects to be subclasses
of the httplib ones, which gives better behavior on App Engine.
1 parent 21fef11 commit fe22a15

File tree

2 files changed

+46
-78
lines changed

2 files changed

+46
-78
lines changed

python2/httplib2/__init__.py

+18-68
Original file line numberDiff line numberDiff line change
@@ -1064,83 +1064,33 @@ def connect(self):
10641064
from google.appengine.api.urlfetch import fetch
10651065
from google.appengine.api.urlfetch import InvalidURLError
10661066

1067-
class ResponseDict(dict):
1068-
"""Dictionary with a read() method; can pass off as httplib.HTTPResponse."""
1069-
def __init__(self, *args, **kwargs):
1070-
self.content = kwargs.pop('content', None)
1071-
return super(ResponseDict, self).__init__(*args, **kwargs)
1067+
def _new_fixed_fetch(validate_certificate):
1068+
def fixed_fetch(url, payload=None, method="GET", headers={}, allow_truncated=False, follow_redirects=True, deadline=5):
1069+
return fetch(url, payload=payload, method=method, headers=header, allow_truncated=allow_truncated, follow_redirects=follow_redirects, deadline=deadline, validate_certificate=validate_certificate)
1070+
return fixed_fetch
10721071

1073-
def read(self):
1074-
return self.content
1072+
class AppEngineHttpConnection(httplib.HTTPConnection):
1073+
"""Use httplib on App Engine, but compensate for its weirdness.
10751074
1076-
1077-
class AppEngineHttpConnection(object):
1078-
"""Emulates an httplib.HTTPConnection object, but actually uses the Google
1079-
App Engine urlfetch library. This allows the timeout to be properly used on
1080-
Google App Engine, and avoids using httplib, which on Google App Engine is
1081-
just another wrapper around urlfetch.
1075+
The parameters key_file, cert_file, proxy_info, ca_certs, and
1076+
disable_ssl_certificate_validation are all dropped on the ground.
10821077
"""
10831078
def __init__(self, host, port=None, key_file=None, cert_file=None,
10841079
strict=None, timeout=None, proxy_info=None, ca_certs=None,
10851080
disable_ssl_certificate_validation=False):
1086-
self.host = host
1087-
self.port = port
1088-
self.timeout = timeout
1089-
if key_file or cert_file or proxy_info or ca_certs:
1090-
raise NotSupportedOnThisPlatform()
1091-
self.response = None
1092-
self.scheme = 'http'
1093-
self.validate_certificate = not disable_ssl_certificate_validation
1094-
self.sock = True
1095-
1096-
def request(self, method, url, body, headers):
1097-
# Calculate the absolute URI, which fetch requires
1098-
netloc = self.host
1099-
if self.port:
1100-
netloc = '%s:%s' % (self.host, self.port)
1101-
absolute_uri = '%s://%s%s' % (self.scheme, netloc, url)
1102-
try:
1103-
try: # 'body' can be a stream.
1104-
body = body.read()
1105-
except AttributeError:
1106-
pass
1107-
response = fetch(absolute_uri, payload=body, method=method,
1108-
headers=headers, allow_truncated=False, follow_redirects=False,
1109-
deadline=self.timeout,
1110-
validate_certificate=self.validate_certificate)
1111-
self.response = ResponseDict(response.headers, content=response.content)
1112-
self.response['status'] = str(response.status_code)
1113-
self.response['reason'] = httplib.responses.get(response.status_code, 'Ok')
1114-
self.response.status = response.status_code
1115-
1116-
# Make sure the exceptions raised match the exceptions expected.
1117-
except InvalidURLError:
1118-
raise socket.gaierror('')
1119-
1120-
def getresponse(self):
1121-
if self.response:
1122-
return self.response
1123-
else:
1124-
raise httplib.HTTPException()
1125-
1126-
def set_debuglevel(self, level):
1127-
pass
1128-
1129-
def connect(self):
1130-
pass
1131-
1132-
def close(self):
1133-
pass
1134-
1081+
httplib.HTTPConnection.__init__(self, host, port=port, strict=strict,
1082+
timeout=timeout)
11351083

1136-
class AppEngineHttpsConnection(AppEngineHttpConnection):
1084+
class AppEngineHttpsConnection(httplib.HTTPSConnection):
11371085
"""Same as AppEngineHttpConnection, but for HTTPS URIs."""
11381086
def __init__(self, host, port=None, key_file=None, cert_file=None,
11391087
strict=None, timeout=None, proxy_info=None, ca_certs=None,
11401088
disable_ssl_certificate_validation=False):
1141-
AppEngineHttpConnection.__init__(self, host, port, key_file, cert_file,
1142-
strict, timeout, proxy_info, ca_certs, disable_ssl_certificate_validation)
1143-
self.scheme = 'https'
1089+
httplib.HTTPSConnection.__init__(self, host, port=port,
1090+
key_file=key_file,
1091+
cert_file=cert_file, strict=strict,
1092+
timeout=timeout)
1093+
self._fetch = _new_fixed_fetch(not disable_ssl_certificate_validation)
11441094

11451095
# Update the connection classes to use the Googel App Engine specific ones.
11461096
SCHEME_TO_CONNECTION = {
@@ -1277,7 +1227,7 @@ def clear_credentials(self):
12771227
def _conn_request(self, conn, request_uri, method, body, headers):
12781228
for i in range(RETRIES):
12791229
try:
1280-
if conn.sock is None:
1230+
if hasattr(conn, 'sock') and conn.sock is None:
12811231
conn.connect()
12821232
conn.request(method, request_uri, body, headers)
12831233
except socket.timeout:
@@ -1299,7 +1249,7 @@ def _conn_request(self, conn, request_uri, method, body, headers):
12991249
except httplib.HTTPException:
13001250
# Just because the server closed the connection doesn't apparently mean
13011251
# that the server didn't send a response.
1302-
if conn.sock is None:
1252+
if hasattr(conn, 'sock') and conn.sock is None:
13031253
if i < RETRIES-1:
13041254
conn.close()
13051255
conn.connect()

python2/httplib2test_appengine.py

+28-10
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
testbed.activate()
2929
testbed.init_urlfetch_stub()
3030

31+
from google.appengine.runtime import DeadlineExceededError
32+
3133
import httplib2
3234

3335
class AppEngineHttpTest(unittest.TestCase):
3436
def setUp(self):
35-
if os.path.exists(cacheDirName):
37+
if os.path.exists(cacheDirName):
3638
[os.remove(os.path.join(cacheDirName, file)) for file in os.listdir(cacheDirName)]
3739

3840
if sys.version_info < (2, 6):
@@ -45,20 +47,36 @@ def test(self):
4547
response, content = h.request("http://bitworking.org")
4648
self.assertEqual(httplib2.SCHEME_TO_CONNECTION['https'],
4749
httplib2.AppEngineHttpsConnection)
48-
print h.connections
4950
self.assertEquals(1, len(h.connections))
50-
self.assertEquals(type(h.connections['http:bitworking.org']),
51-
httplib2.AppEngineHttpConnection)
5251
self.assertEquals(response.status, 200)
5352
self.assertEquals(response['status'], '200')
5453

55-
def test_no_key_or_cert_file(self):
54+
# It would be great to run the test below, but it really tests the
55+
# aberrant behavior of httplib on App Engine, but that special aberrant
56+
# httplib only appears when actually running on App Engine and not when
57+
# running via the SDK. When running via the SDK the httplib in std lib is
58+
# loaded, which throws a different error when a timeout occurs.
59+
#
60+
#def test_timeout(self):
61+
# # The script waits 3 seconds, so a timeout of more than that should succeed.
62+
# h = httplib2.Http(timeout=7)
63+
# r, c = h.request('http://bitworking.org/projects/httplib2/test/timeout/timeout.cgi')
64+
#
65+
# import httplib
66+
# print httplib.__file__
67+
# h = httplib2.Http(timeout=1)
68+
# try:
69+
# r, c = h.request('http://bitworking.org/projects/httplib2/test/timeout/timeout.cgi')
70+
# self.fail('Timeout should have raised an exception.')
71+
# except DeadlineExceededError:
72+
# pass
73+
74+
75+
76+
def test_proxy_info_ignored(self):
5677
h = httplib2.Http(proxy_info='foo.txt')
57-
try:
58-
response, content = h.request("http://bitworking.org")
59-
self.fail('Should raise exception.')
60-
except httplib2.NotSupportedOnThisPlatform:
61-
pass
78+
response, content = h.request("http://bitworking.org")
79+
self.assertEquals(response.status, 200)
6280

6381
if __name__ == '__main__':
6482
unittest.main()

0 commit comments

Comments
 (0)