/gdata/urlfetch.py

http://radioappz.googlecode.com/ · Python · 247 lines · 202 code · 13 blank · 32 comment · 22 complexity · 3c80c9a52d016c7e98bf38566462dae7 MD5 · raw file

  1. #!/usr/bin/python
  2. #
  3. # Copyright (C) 2008 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Provides HTTP functions for gdata.service to use on Google App Engine
  17. AppEngineHttpClient: Provides an HTTP request method which uses App Engine's
  18. urlfetch API. Set the http_client member of a GDataService object to an
  19. instance of an AppEngineHttpClient to allow the gdata library to run on
  20. Google App Engine.
  21. run_on_appengine: Function which will modify an existing GDataService object
  22. to allow it to run on App Engine. It works by creating a new instance of
  23. the AppEngineHttpClient and replacing the GDataService object's
  24. http_client.
  25. HttpRequest: Function that wraps google.appengine.api.urlfetch.Fetch in a
  26. common interface which is used by gdata.service.GDataService. In other
  27. words, this module can be used as the gdata service request handler so
  28. that all HTTP requests will be performed by the hosting Google App Engine
  29. server.
  30. """
  31. __author__ = 'api.jscudder (Jeff Scudder)'
  32. import StringIO
  33. import atom.service
  34. import atom.http_interface
  35. from google.appengine.api import urlfetch
  36. def run_on_appengine(gdata_service):
  37. """Modifies a GDataService object to allow it to run on App Engine.
  38. Args:
  39. gdata_service: An instance of AtomService, GDataService, or any
  40. of their subclasses which has an http_client member.
  41. """
  42. gdata_service.http_client = AppEngineHttpClient()
  43. class AppEngineHttpClient(atom.http_interface.GenericHttpClient):
  44. def __init__(self, headers=None):
  45. self.debug = False
  46. self.headers = headers or {}
  47. def request(self, operation, url, data=None, headers=None):
  48. """Performs an HTTP call to the server, supports GET, POST, PUT, and
  49. DELETE.
  50. Usage example, perform and HTTP GET on http://www.google.com/:
  51. import atom.http
  52. client = atom.http.HttpClient()
  53. http_response = client.request('GET', 'http://www.google.com/')
  54. Args:
  55. operation: str The HTTP operation to be performed. This is usually one
  56. of 'GET', 'POST', 'PUT', or 'DELETE'
  57. data: filestream, list of parts, or other object which can be converted
  58. to a string. Should be set to None when performing a GET or DELETE.
  59. If data is a file-like object which can be read, this method will
  60. read a chunk of 100K bytes at a time and send them.
  61. If the data is a list of parts to be sent, each part will be
  62. evaluated and sent.
  63. url: The full URL to which the request should be sent. Can be a string
  64. or atom.url.Url.
  65. headers: dict of strings. HTTP headers which should be sent
  66. in the request.
  67. """
  68. all_headers = self.headers.copy()
  69. if headers:
  70. all_headers.update(headers)
  71. # Construct the full payload.
  72. # Assume that data is None or a string.
  73. data_str = data
  74. if data:
  75. if isinstance(data, list):
  76. # If data is a list of different objects, convert them all to strings
  77. # and join them together.
  78. converted_parts = [__ConvertDataPart(x) for x in data]
  79. data_str = ''.join(converted_parts)
  80. else:
  81. data_str = __ConvertDataPart(data)
  82. # If the list of headers does not include a Content-Length, attempt to
  83. # calculate it based on the data object.
  84. if data and 'Content-Length' not in all_headers:
  85. all_headers['Content-Length'] = len(data_str)
  86. # Set the content type to the default value if none was set.
  87. if 'Content-Type' not in all_headers:
  88. all_headers['Content-Type'] = 'application/atom+xml'
  89. # Lookup the urlfetch operation which corresponds to the desired HTTP verb.
  90. if operation == 'GET':
  91. method = urlfetch.GET
  92. elif operation == 'POST':
  93. method = urlfetch.POST
  94. elif operation == 'PUT':
  95. method = urlfetch.PUT
  96. elif operation == 'DELETE':
  97. method = urlfetch.DELETE
  98. else:
  99. method = None
  100. return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
  101. method=method, headers=all_headers))
  102. def HttpRequest(service, operation, data, uri, extra_headers=None,
  103. url_params=None, escape_params=True, content_type='application/atom+xml'):
  104. """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
  105. This function is deprecated, use AppEngineHttpClient.request instead.
  106. To use this module with gdata.service, you can set this module to be the
  107. http_request_handler so that HTTP requests use Google App Engine's urlfetch.
  108. import gdata.service
  109. import gdata.urlfetch
  110. gdata.service.http_request_handler = gdata.urlfetch
  111. Args:
  112. service: atom.AtomService object which contains some of the parameters
  113. needed to make the request. The following members are used to
  114. construct the HTTP call: server (str), additional_headers (dict),
  115. port (int), and ssl (bool).
  116. operation: str The HTTP operation to be performed. This is usually one of
  117. 'GET', 'POST', 'PUT', or 'DELETE'
  118. data: filestream, list of parts, or other object which can be
  119. converted to a string.
  120. Should be set to None when performing a GET or PUT.
  121. If data is a file-like object which can be read, this method will read
  122. a chunk of 100K bytes at a time and send them.
  123. If the data is a list of parts to be sent, each part will be evaluated
  124. and sent.
  125. uri: The beginning of the URL to which the request should be sent.
  126. Examples: '/', '/base/feeds/snippets',
  127. '/m8/feeds/contacts/default/base'
  128. extra_headers: dict of strings. HTTP headers which should be sent
  129. in the request. These headers are in addition to those stored in
  130. service.additional_headers.
  131. url_params: dict of strings. Key value pairs to be added to the URL as
  132. URL parameters. For example {'foo':'bar', 'test':'param'} will
  133. become ?foo=bar&test=param.
  134. escape_params: bool default True. If true, the keys and values in
  135. url_params will be URL escaped when the form is constructed
  136. (Special characters converted to %XX form.)
  137. content_type: str The MIME type for the data being sent. Defaults to
  138. 'application/atom+xml', this is only used if data is set.
  139. """
  140. full_uri = atom.service.BuildUri(uri, url_params, escape_params)
  141. (server, port, ssl, partial_uri) = atom.service.ProcessUrl(service, full_uri)
  142. # Construct the full URL for the request.
  143. if ssl:
  144. full_url = 'https://%s%s' % (server, partial_uri)
  145. else:
  146. full_url = 'http://%s%s' % (server, partial_uri)
  147. # Construct the full payload.
  148. # Assume that data is None or a string.
  149. data_str = data
  150. if data:
  151. if isinstance(data, list):
  152. # If data is a list of different objects, convert them all to strings
  153. # and join them together.
  154. converted_parts = [__ConvertDataPart(x) for x in data]
  155. data_str = ''.join(converted_parts)
  156. else:
  157. data_str = __ConvertDataPart(data)
  158. # Construct the dictionary of HTTP headers.
  159. headers = {}
  160. if isinstance(service.additional_headers, dict):
  161. headers = service.additional_headers.copy()
  162. if isinstance(extra_headers, dict):
  163. for header, value in extra_headers.iteritems():
  164. headers[header] = value
  165. # Add the content type header (we don't need to calculate content length,
  166. # since urlfetch.Fetch will calculate for us).
  167. if content_type:
  168. headers['Content-Type'] = content_type
  169. # Lookup the urlfetch operation which corresponds to the desired HTTP verb.
  170. if operation == 'GET':
  171. method = urlfetch.GET
  172. elif operation == 'POST':
  173. method = urlfetch.POST
  174. elif operation == 'PUT':
  175. method = urlfetch.PUT
  176. elif operation == 'DELETE':
  177. method = urlfetch.DELETE
  178. else:
  179. method = None
  180. return HttpResponse(urlfetch.Fetch(url=full_url, payload=data_str,
  181. method=method, headers=headers))
  182. def __ConvertDataPart(data):
  183. if not data or isinstance(data, str):
  184. return data
  185. elif hasattr(data, 'read'):
  186. # data is a file like object, so read it completely.
  187. return data.read()
  188. # The data object was not a file.
  189. # Try to convert to a string and send the data.
  190. return str(data)
  191. class HttpResponse(object):
  192. """Translates a urlfetch resoinse to look like an hhtplib resoinse.
  193. Used to allow the resoinse from HttpRequest to be usable by gdata.service
  194. methods.
  195. """
  196. def __init__(self, urlfetch_response):
  197. self.body = StringIO.StringIO(urlfetch_response.content)
  198. self.headers = urlfetch_response.headers
  199. self.status = urlfetch_response.status_code
  200. self.reason = ''
  201. def read(self, length=None):
  202. if not length:
  203. return self.body.read()
  204. else:
  205. return self.body.read(length)
  206. def getheader(self, name):
  207. if not self.headers.has_key(name):
  208. return self.headers[name.lower()]
  209. return self.headers[name]