thirdparty/google_appengine/google/appengine/api/urlfetch.py
changeset 149 f2e327a7c5de
parent 109 620f9b141567
child 297 35211afcd563
equal deleted inserted replaced
148:37505d64e57b 149:f2e327a7c5de
    23 
    23 
    24 
    24 
    25 
    25 
    26 
    26 
    27 
    27 
       
    28 import os
    28 import UserDict
    29 import UserDict
       
    30 import urllib2
       
    31 import urlparse
    29 
    32 
    30 from google.appengine.api import apiproxy_stub_map
    33 from google.appengine.api import apiproxy_stub_map
    31 from google.appengine.api import urlfetch_service_pb
    34 from google.appengine.api import urlfetch_service_pb
    32 from google.appengine.api.urlfetch_errors import *
    35 from google.appengine.api.urlfetch_errors import *
    33 from google.appengine.runtime import apiproxy_errors
    36 from google.appengine.runtime import apiproxy_errors
    34 
    37 
       
    38 MAX_REDIRECTS = 5
    35 
    39 
    36 GET = 1
    40 GET = 1
    37 POST = 2
    41 POST = 2
    38 HEAD = 3
    42 HEAD = 3
    39 PUT = 4
    43 PUT = 4
   151   def copy(self):
   155   def copy(self):
   152     """Make a shallow, case sensitive copy of self."""
   156     """Make a shallow, case sensitive copy of self."""
   153     return dict(self)
   157     return dict(self)
   154 
   158 
   155 
   159 
   156 def fetch(url, payload=None, method=GET, headers={}, allow_truncated=False):
   160 def _is_fetching_self(url, method):
       
   161   """Checks if the fetch is for the same URL from which it originated.
       
   162 
       
   163   Args:
       
   164     url: str, The URL being fetched.
       
   165     method: value from _VALID_METHODS.
       
   166 
       
   167   Returns:
       
   168     boolean indicating whether or not it seems that the app is trying to fetch
       
   169       itself.
       
   170   """
       
   171   if (method != GET or
       
   172       "HTTP_HOST" not in os.environ or
       
   173       "PATH_INFO" not in os.environ):
       
   174     return False
       
   175 
       
   176   scheme, host_port, path, query, fragment = urlparse.urlsplit(url)
       
   177 
       
   178   if (host_port == os.environ['HTTP_HOST'] and
       
   179       urllib2.unquote(path) == urllib2.unquote(os.environ['PATH_INFO'])):
       
   180     return True
       
   181 
       
   182   return False
       
   183 
       
   184 
       
   185 def fetch(url, payload=None, method=GET, headers={}, allow_truncated=False,
       
   186           follow_redirects=True):
   157   """Fetches the given HTTP URL, blocking until the result is returned.
   187   """Fetches the given HTTP URL, blocking until the result is returned.
   158 
   188 
   159   Other optional parameters are:
   189   Other optional parameters are:
   160      method: GET, POST, HEAD, PUT, or DELETE
   190      method: GET, POST, HEAD, PUT, or DELETE
   161      payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE)
   191      payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE)
   162      headers: dictionary of HTTP headers to send with the request
   192      headers: dictionary of HTTP headers to send with the request
   163      allow_truncated: if true, truncate large responses and return them without
   193      allow_truncated: if true, truncate large responses and return them without
   164      error. otherwise, ResponseTooLargeError will be thrown when a response is
   194        error. otherwise, ResponseTooLargeError will be thrown when a response is
   165      truncated.
   195        truncated.
       
   196      follow_redirects: if true (the default), redirects are
       
   197        transparently followed and the response (if less than 5
       
   198        redirects) contains the final destination's payload and the
       
   199        response status is 200.  You lose, however, the redirect chain
       
   200        information.  If false, you see the HTTP response yourself,
       
   201        including the 'Location' header, and redirects are not
       
   202        followed.
   166 
   203 
   167   We use a HTTP/1.1 compliant proxy to fetch the result.
   204   We use a HTTP/1.1 compliant proxy to fetch the result.
   168 
   205 
   169   The returned data structure has the following fields:
   206   The returned data structure has the following fields:
   170      content: string containing the response from the server
   207      content: string containing the response from the server
   175   urlfetch.InvalidURLError. If the server cannot be contacted, we throw a
   212   urlfetch.InvalidURLError. If the server cannot be contacted, we throw a
   176   urlfetch.DownloadError.  Note that HTTP errors are returned as a part
   213   urlfetch.DownloadError.  Note that HTTP errors are returned as a part
   177   of the returned structure, so HTTP errors like 404 do not result in an
   214   of the returned structure, so HTTP errors like 404 do not result in an
   178   exception.
   215   exception.
   179   """
   216   """
   180   request = urlfetch_service_pb.URLFetchRequest()
       
   181   response = urlfetch_service_pb.URLFetchResponse()
       
   182   request.set_url(url)
       
   183 
       
   184   if isinstance(method, basestring):
   217   if isinstance(method, basestring):
   185     method = method.upper()
   218     method = method.upper()
   186   method = _URL_STRING_MAP.get(method, method)
   219   method = _URL_STRING_MAP.get(method, method)
   187   if method not in _VALID_METHODS:
   220   if method not in _VALID_METHODS:
   188     raise InvalidMethodError('Invalid method %s.' % str(method))
   221     raise InvalidMethodError('Invalid method %s.' % str(method))
       
   222 
       
   223   if _is_fetching_self(url, method):
       
   224     raise InvalidURLError("App cannot fetch the same URL as the one used for "
       
   225                           "the request.")
       
   226 
       
   227   request = urlfetch_service_pb.URLFetchRequest()
       
   228   response = urlfetch_service_pb.URLFetchResponse()
       
   229   request.set_url(url)
       
   230 
   189   if method == GET:
   231   if method == GET:
   190     request.set_method(urlfetch_service_pb.URLFetchRequest.GET)
   232     request.set_method(urlfetch_service_pb.URLFetchRequest.GET)
   191   elif method == POST:
   233   elif method == POST:
   192     request.set_method(urlfetch_service_pb.URLFetchRequest.POST)
   234     request.set_method(urlfetch_service_pb.URLFetchRequest.POST)
   193   elif method == HEAD:
   235   elif method == HEAD:
   203   for key, value in headers.iteritems():
   245   for key, value in headers.iteritems():
   204     header_proto = request.add_header()
   246     header_proto = request.add_header()
   205     header_proto.set_key(key)
   247     header_proto.set_key(key)
   206     header_proto.set_value(value)
   248     header_proto.set_value(value)
   207 
   249 
       
   250   request.set_followredirects(follow_redirects)
       
   251 
   208   try:
   252   try:
   209     apiproxy_stub_map.MakeSyncCall('urlfetch', 'Fetch', request, response)
   253     apiproxy_stub_map.MakeSyncCall('urlfetch', 'Fetch', request, response)
   210   except apiproxy_errors.ApplicationError, e:
   254   except apiproxy_errors.ApplicationError, e:
   211     if (e.application_error ==
   255     if (e.application_error ==
   212         urlfetch_service_pb.URLFetchServiceError.INVALID_URL):
   256         urlfetch_service_pb.URLFetchServiceError.INVALID_URL):