thirdparty/google_appengine/google/appengine/api/urlfetch.py
changeset 2413 d0b7dac5325c
parent 2309 be1b94099f2d
equal deleted inserted replaced
2412:c61d96e72e6f 2413:d0b7dac5325c
    28 import os
    28 import os
    29 import UserDict
    29 import UserDict
    30 import urllib2
    30 import urllib2
    31 import urlparse
    31 import urlparse
    32 
    32 
    33 from google.appengine.api import apiproxy_rpc
       
    34 from google.appengine.api import apiproxy_stub_map
    33 from google.appengine.api import apiproxy_stub_map
    35 from google.appengine.api import urlfetch_service_pb
    34 from google.appengine.api import urlfetch_service_pb
    36 from google.appengine.api.urlfetch_errors import *
    35 from google.appengine.api.urlfetch_errors import *
    37 from google.appengine.runtime import apiproxy_errors
    36 from google.appengine.runtime import apiproxy_errors
    38 
    37 
   185       return True
   184       return True
   186 
   185 
   187   return False
   186   return False
   188 
   187 
   189 
   188 
   190 def __create_rpc(deadline=None, callback=None):
   189 def create_rpc(deadline=None, callback=None):
   191   """DO NOT USE.  WILL CHANGE AND BREAK YOUR CODE.
   190   """Creates an RPC object for use with the urlfetch API.
   192 
       
   193   Creates an RPC object for use with the urlfetch API.
       
   194 
   191 
   195   Args:
   192   Args:
   196     deadline: deadline in seconds for the operation.
   193     deadline: Optional deadline in seconds for the operation; the default
   197     callback: callable to invoke on completion.
   194       is a system-specific deadline (typically 5 seconds).
       
   195     callback: Optional callable to invoke on completion.
   198 
   196 
   199   Returns:
   197   Returns:
   200     A _URLFetchRPC object.
   198     An apiproxy_stub_map.UserRPC object specialized for this service.
   201   """
   199   """
   202   return _URLFetchRPC(deadline, callback)
   200   return apiproxy_stub_map.UserRPC('urlfetch', deadline, callback)
   203 
   201 
   204 
   202 
   205 def fetch(url, payload=None, method=GET, headers={}, allow_truncated=False,
   203 def fetch(url, payload=None, method=GET, headers={},
   206           follow_redirects=True, deadline=None):
   204           allow_truncated=False, follow_redirects=True,
       
   205           deadline=None):
   207   """Fetches the given HTTP URL, blocking until the result is returned.
   206   """Fetches the given HTTP URL, blocking until the result is returned.
   208 
   207 
   209   Other optional parameters are:
   208   Other optional parameters are:
   210      method: GET, POST, HEAD, PUT, or DELETE
   209      method: GET, POST, HEAD, PUT, or DELETE
   211      payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE).
   210      payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE).
   212        this is ignored if the method is not POST or PUT.
   211        this is ignored if the method is not POST or PUT.
   213      headers: dictionary of HTTP headers to send with the request
   212      headers: dictionary of HTTP headers to send with the request
   214      allow_truncated: if true, truncate large responses and return them without
   213      allow_truncated: if true, truncate large responses and return them without
   215        error. otherwise, ResponseTooLargeError will be thrown when a response is
   214        error. Otherwise, ResponseTooLargeError is raised when a response is
   216        truncated.
   215        truncated.
   217      follow_redirects: if true (the default), redirects are
   216      follow_redirects: if true (the default), redirects are
   218        transparently followed and the response (if less than 5
   217        transparently followed and the response (if less than 5
   219        redirects) contains the final destination's payload and the
   218        redirects) contains the final destination's payload and the
   220        response status is 200.  You lose, however, the redirect chain
   219        response status is 200.  You lose, however, the redirect chain
   234   urlfetch.InvalidURLError. If the server cannot be contacted, we throw a
   233   urlfetch.InvalidURLError. If the server cannot be contacted, we throw a
   235   urlfetch.DownloadError.  Note that HTTP errors are returned as a part
   234   urlfetch.DownloadError.  Note that HTTP errors are returned as a part
   236   of the returned structure, so HTTP errors like 404 do not result in an
   235   of the returned structure, so HTTP errors like 404 do not result in an
   237   exception.
   236   exception.
   238   """
   237   """
   239   rpc = __create_rpc(deadline=deadline)
   238   rpc = create_rpc(deadline=deadline)
   240   rpc.make_call(url, payload, method, headers, follow_redirects)
   239   make_fetch_call(rpc, url, payload, method, headers,
   241   return rpc.get_result(allow_truncated)
   240                   allow_truncated, follow_redirects)
   242 
   241   return rpc.get_result()
   243 
   242 
   244 class _URLFetchRPC(object):
   243 
   245   """A RPC object that manages the urlfetch RPC.
   244 def make_fetch_call(rpc, url, payload=None, method=GET, headers={},
   246 
   245                     allow_truncated=False, follow_redirects=True):
   247   Its primary functions are the following:
   246   """Executes the RPC call to fetch a given HTTP URL.
   248   1. Convert error codes to the URLFetchServiceError namespace and raise them
   247 
   249      when get_result is called.
   248   The first argument is a UserRPC instance.  See urlfetch.fetch for a
   250   2. Wrap the urlfetch response with a _URLFetchResult object.
   249   thorough description of remaining arguments.
   251   """
   250   """
   252 
   251   assert rpc.service == 'urlfetch', repr(rpc.service)
   253   def __init__(self, deadline=None, callback=None):
   252   if isinstance(method, basestring):
   254     """Construct a new url fetch RPC.
   253     method = method.upper()
   255 
   254   method = _URL_STRING_MAP.get(method, method)
   256     Args:
   255   if method not in _VALID_METHODS:
   257       deadline: deadline in seconds for the operation.
   256     raise InvalidMethodError('Invalid method %s.' % str(method))
   258       callback: callable to invoke on completion.
   257 
   259     """
   258   if _is_fetching_self(url, method):
   260     self.__rpc = apiproxy_stub_map.CreateRPC('urlfetch')
   259     raise InvalidURLError("App cannot fetch the same URL as the one used for "
   261     self.__rpc.deadline = deadline
   260                           "the request.")
   262     self.__rpc.callback = callback
   261 
   263     self.__called_hooks = False
   262   request = urlfetch_service_pb.URLFetchRequest()
   264 
   263   response = urlfetch_service_pb.URLFetchResponse()
   265   def make_call(self, url, payload=None, method=GET, headers={},
   264   request.set_url(url)
   266                 follow_redirects=True):
   265 
   267     """Executes the RPC call to fetch a given HTTP URL.
   266   if method == GET:
   268 
   267     request.set_method(urlfetch_service_pb.URLFetchRequest.GET)
   269     See urlfetch.fetch for a thorough description of arguments.
   268   elif method == POST:
   270     """
   269     request.set_method(urlfetch_service_pb.URLFetchRequest.POST)
   271     assert self.__rpc.state is apiproxy_rpc.RPC.IDLE
   270   elif method == HEAD:
   272     if isinstance(method, basestring):
   271     request.set_method(urlfetch_service_pb.URLFetchRequest.HEAD)
   273       method = method.upper()
   272   elif method == PUT:
   274     method = _URL_STRING_MAP.get(method, method)
   273     request.set_method(urlfetch_service_pb.URLFetchRequest.PUT)
   275     if method not in _VALID_METHODS:
   274   elif method == DELETE:
   276       raise InvalidMethodError('Invalid method %s.' % str(method))
   275     request.set_method(urlfetch_service_pb.URLFetchRequest.DELETE)
   277 
   276 
   278     if _is_fetching_self(url, method):
   277   if payload and (method == POST or method == PUT):
   279       raise InvalidURLError("App cannot fetch the same URL as the one used for "
   278     request.set_payload(payload)
   280                             "the request.")
   279 
   281 
   280   for key, value in headers.iteritems():
   282     self.__request = urlfetch_service_pb.URLFetchRequest()
   281     header_proto = request.add_header()
   283     self.__response = urlfetch_service_pb.URLFetchResponse()
   282     header_proto.set_key(key)
   284     self.__result = None
   283     header_proto.set_value(str(value))
   285     self.__request.set_url(url)
   284 
   286 
   285   request.set_followredirects(follow_redirects)
   287     if method == GET:
   286 
   288       self.__request.set_method(urlfetch_service_pb.URLFetchRequest.GET)
   287   if rpc.deadline is not None:
   289     elif method == POST:
   288     request.set_deadline(rpc.deadline)
   290       self.__request.set_method(urlfetch_service_pb.URLFetchRequest.POST)
   289 
   291     elif method == HEAD:
   290   rpc.make_call('Fetch', request, response, _get_fetch_result, allow_truncated)
   292       self.__request.set_method(urlfetch_service_pb.URLFetchRequest.HEAD)
   291 
   293     elif method == PUT:
   292 
   294       self.__request.set_method(urlfetch_service_pb.URLFetchRequest.PUT)
   293 def _get_fetch_result(rpc):
   295     elif method == DELETE:
   294   """Check success, handle exceptions, and return converted RPC result.
   296       self.__request.set_method(urlfetch_service_pb.URLFetchRequest.DELETE)
   295 
   297 
   296   This method waits for the RPC if it has not yet finished, and calls the
   298     if payload and (method == POST or method == PUT):
   297   post-call hooks on the first invocation.
   299       self.__request.set_payload(payload)
   298 
   300 
   299   Args:
   301     for key, value in headers.iteritems():
   300     rpc: A UserRPC object.
   302       header_proto = self.__request.add_header()
   301 
   303       header_proto.set_key(key)
   302   Raises:
   304       header_proto.set_value(str(value))
   303     InvalidURLError if the url was invalid.
   305 
   304     DownloadError if there was a problem fetching the url.
   306     self.__request.set_followredirects(follow_redirects)
   305     ResponseTooLargeError if the response was either truncated (and
   307     if self.__rpc.deadline:
   306       allow_truncated=False was passed to make_fetch_call()), or if it
   308       self.__request.set_deadline(self.__rpc.deadline)
   307       was too big for us to download.
   309 
   308 
   310     apiproxy_stub_map.apiproxy.GetPreCallHooks().Call(
   309   Returns:
   311         'urlfetch', 'Fetch', self.__request, self.__response)
   310     A _URLFetchResult object.
   312     self.__rpc.MakeCall('urlfetch', 'Fetch', self.__request, self.__response)
   311   """
   313 
   312   assert rpc.service == 'urlfetch', repr(rpc.service)
   314   def wait(self):
   313   assert rpc.method == 'Fetch', repr(rpc.method)
   315     """Waits for the urlfetch RPC to finish.  Idempotent.
   314   try:
   316     """
   315     rpc.check_success()
   317     assert self.__rpc.state is not apiproxy_rpc.RPC.IDLE
   316   except apiproxy_errors.ApplicationError, err:
   318     if self.__rpc.state is apiproxy_rpc.RPC.RUNNING:
   317     if (err.application_error ==
   319       self.__rpc.Wait()
   318         urlfetch_service_pb.URLFetchServiceError.INVALID_URL):
   320 
   319       raise InvalidURLError(str(err))
   321   def check_success(self, allow_truncated=False):
   320     if (err.application_error ==
   322     """Check success and convert RPC exceptions to urlfetch exceptions.
   321         urlfetch_service_pb.URLFetchServiceError.UNSPECIFIED_ERROR):
   323 
   322       raise DownloadError(str(err))
   324     This method waits for the RPC if it has not yet finished, and calls the
   323     if (err.application_error ==
   325     post-call hooks on the first invocation.
   324         urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR):
   326 
   325       raise DownloadError(str(err))
   327     Args:
   326     if (err.application_error ==
   328       allow_truncated: if False, an error is raised if the response was
   327         urlfetch_service_pb.URLFetchServiceError.RESPONSE_TOO_LARGE):
   329         truncated.
   328       raise ResponseTooLargeError(None)
   330 
   329     if (err.application_error ==
   331     Raises:
   330         urlfetch_service_pb.URLFetchServiceError.DEADLINE_EXCEEDED):
   332       InvalidURLError if the url was invalid.
   331       raise DownloadError(str(err))
   333       DownloadError if there was a problem fetching the url.
   332     raise err
   334       ResponseTooLargeError if the response was either truncated (and
   333 
   335         allow_truncated is false) or if it was too big for us to download.
   334   response = rpc.response
   336     """
   335   allow_truncated = rpc.user_data
   337     assert self.__rpc.state is not apiproxy_rpc.RPC.IDLE
   336   result = _URLFetchResult(response)
   338     if self.__rpc.state is apiproxy_rpc.RPC.RUNNING:
   337   if response.contentwastruncated() and not allow_truncated:
   339       self.wait()
   338     raise ResponseTooLargeError(result)
   340 
   339   return result
   341     try:
       
   342       self.__rpc.CheckSuccess()
       
   343       if not self.__called_hooks:
       
   344         self.__called_hooks = True
       
   345         apiproxy_stub_map.apiproxy.GetPostCallHooks().Call(
       
   346             'urlfetch', 'Fetch', self.__request, self.__response)
       
   347     except apiproxy_errors.ApplicationError, e:
       
   348       if (e.application_error ==
       
   349           urlfetch_service_pb.URLFetchServiceError.INVALID_URL):
       
   350         raise InvalidURLError(str(e))
       
   351       if (e.application_error ==
       
   352           urlfetch_service_pb.URLFetchServiceError.UNSPECIFIED_ERROR):
       
   353         raise DownloadError(str(e))
       
   354       if (e.application_error ==
       
   355           urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR):
       
   356         raise DownloadError(str(e))
       
   357       if (e.application_error ==
       
   358           urlfetch_service_pb.URLFetchServiceError.RESPONSE_TOO_LARGE):
       
   359         raise ResponseTooLargeError(None)
       
   360       if (e.application_error ==
       
   361           urlfetch_service_pb.URLFetchServiceError.DEADLINE_EXCEEDED):
       
   362         raise DownloadError(str(e))
       
   363       raise e
       
   364 
       
   365     if self.__response.contentwastruncated() and not allow_truncated:
       
   366       raise ResponseTooLargeError(_URLFetchResult(self.__response))
       
   367 
       
   368   def get_result(self, allow_truncated=False):
       
   369     """Returns the RPC result or raises an exception if the rpc failed.
       
   370 
       
   371     This method waits for the RPC if not completed, and checks success.
       
   372 
       
   373     Args:
       
   374       allow_truncated: if False, an error is raised if the response was
       
   375         truncated.
       
   376 
       
   377     Returns:
       
   378       The urlfetch result.
       
   379 
       
   380     Raises:
       
   381       Error if the rpc has not yet finished.
       
   382       InvalidURLError if the url was invalid.
       
   383       DownloadError if there was a problem fetching the url.
       
   384       ResponseTooLargeError if the response was either truncated (and
       
   385         allow_truncated is false) or if it was too big for us to download.
       
   386     """
       
   387     if self.__result is None:
       
   388       self.check_success(allow_truncated)
       
   389       self.__result = _URLFetchResult(self.__response)
       
   390     return self.__result
       
   391 
   340 
   392 
   341 
   393 Fetch = fetch
   342 Fetch = fetch
   394 
   343 
   395 
   344