Got test coverage to 97%. Added missing file.
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index cbba1dc..9eec549 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -102,7 +102,7 @@
       return simplejson.loads(content)['data']
     else:
       logging.debug('Content from bad request was: %s' % content)
-      if resp['content-type'] != 'application/json':
+      if resp.get('content-type', '') != 'application/json':
         raise HttpError('%d %s' % (resp.status, resp.reason))
       else:
         raise HttpError(simplejson.loads(content)['error'])
@@ -263,7 +263,7 @@
         for key in methodDesc['location']:
           p = p[key]
         url = p
-      except KeyError:
+      except (KeyError, TypeError):
         return None
 
       headers = {}
diff --git a/apiclient/http.py b/apiclient/http.py
new file mode 100644
index 0000000..16591fb
--- /dev/null
+++ b/apiclient/http.py
@@ -0,0 +1,34 @@
+# Copyright 2010 Google Inc. All Rights Reserved.
+
+"""One-line documentation for http module.
+
+A detailed description of http.
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
+class HttpRequest(object):
+  """Encapsulate an HTTP request.
+  """
+
+  def __init__(self, http, uri, method="GET", body=None, headers=None, postproc=None):
+    self.uri = uri
+    self.method = method
+    self.body = body
+    self.headers = headers or {}
+    self.http = http
+    self.postproc = postproc
+
+  def execute(self, http=None):
+    """Execute the request.
+
+    If an http object is passed in it is used instead of the
+    httplib2.Http object that the request was constructed with.
+    """
+    if http is None:
+      http = self.http
+    resp, content = http.request(self.uri, self.method,
+                                      body=self.body,
+                                      headers=self.headers)
+    return self.postproc(resp, content)