PageRenderTime 34ms CodeModel.GetById 16ms app.highlight 13ms RepoModel.GetById 2ms app.codeStats 0ms

/gdata/urlfetch.py

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