thirdparty/google_appengine/google/appengine/api/urlfetch_stub.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 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 """Stub version of the urlfetch API, based on httplib."""
       
    19 
       
    20 
       
    21 
       
    22 import httplib
       
    23 import logging
       
    24 import socket
       
    25 import urlparse
       
    26 
       
    27 from google.appengine.api import urlfetch_errors
       
    28 from google.appengine.api import urlfetch_service_pb
       
    29 from google.appengine.runtime import apiproxy_errors
       
    30 
       
    31 
       
    32 MAX_RESPONSE_SIZE = 2 ** 24
       
    33 
       
    34 MAX_REDIRECTS = 5
       
    35 
       
    36 REDIRECT_STATUSES = frozenset([
       
    37   httplib.MOVED_PERMANENTLY,
       
    38   httplib.FOUND,
       
    39   httplib.SEE_OTHER,
       
    40   httplib.TEMPORARY_REDIRECT,
       
    41 ])
       
    42 
       
    43 
       
    44 class URLFetchServiceStub(object):
       
    45   """Stub version of the urlfetch API to be used with apiproxy_stub_map."""
       
    46 
       
    47   def MakeSyncCall(self, service, call, request, response):
       
    48     """The main RPC entry point.
       
    49 
       
    50     Arg:
       
    51       service: Must be 'urlfetch'.
       
    52       call: A string representing the rpc to make.  Must be part of
       
    53         URLFetchService.
       
    54       request: A protocol buffer of the type corresponding to 'call'.
       
    55       response: A protocol buffer of the type corresponding to 'call'.
       
    56     """
       
    57     assert service == 'urlfetch'
       
    58     assert request.IsInitialized()
       
    59 
       
    60     attr = getattr(self, '_Dynamic_' + call)
       
    61     attr(request, response)
       
    62 
       
    63   def _Dynamic_Fetch(self, request, response):
       
    64     """Trivial implementation of URLFetchService::Fetch().
       
    65 
       
    66     Args:
       
    67       request: the fetch to perform, a URLFetchRequest
       
    68       response: the fetch response, a URLFetchResponse
       
    69     """
       
    70     (protocol, host, path, parameters, query, fragment) = urlparse.urlparse(request.url())
       
    71 
       
    72     payload = ''
       
    73     if request.method() == urlfetch_service_pb.URLFetchRequest.GET:
       
    74       method = 'GET'
       
    75     elif request.method() == urlfetch_service_pb.URLFetchRequest.POST:
       
    76       method = 'POST'
       
    77       payload = request.payload()
       
    78     elif request.method() == urlfetch_service_pb.URLFetchRequest.HEAD:
       
    79       method = 'HEAD'
       
    80     elif request.method() == urlfetch_service_pb.URLFetchRequest.PUT:
       
    81       method = 'PUT'
       
    82       payload = request.payload()
       
    83     elif request.method() == urlfetch_service_pb.URLFetchRequest.DELETE:
       
    84       method = 'DELETE'
       
    85     else:
       
    86       logging.error('Invalid method: %s', request.method())
       
    87       raise apiproxy_errors.ApplicationError(
       
    88         urlfetch_service_pb.URLFetchServiceError.UNSPECIFIED_ERROR)
       
    89 
       
    90     if not (protocol == 'http' or protocol == 'https'):
       
    91       logging.error('Invalid protocol: %s', protocol)
       
    92       raise apiproxy_errors.ApplicationError(
       
    93         urlfetch_service_pb.URLFetchServiceError.INVALID_URL)
       
    94 
       
    95     self._RetrieveURL(request.url(), payload, method,
       
    96                       request.header_list(), response)
       
    97 
       
    98   def _RetrieveURL(self, url, payload, method, headers, response):
       
    99     """Retrieves a URL.
       
   100 
       
   101     Args:
       
   102       url: String containing the URL to access.
       
   103       payload: Request payload to send, if any.
       
   104       method: HTTP method to use (e.g., 'GET')
       
   105       headers: List of additional header objects to use for the request.
       
   106       response: Response object
       
   107 
       
   108     Raises:
       
   109       Raises an apiproxy_errors.ApplicationError exception with FETCH_ERROR
       
   110       in cases where:
       
   111         - MAX_REDIRECTS is exceeded
       
   112         - The protocol of the redirected URL is bad or missing.
       
   113     """
       
   114     last_protocol = ''
       
   115     last_host = ''
       
   116 
       
   117     for redirect_number in xrange(MAX_REDIRECTS + 1):
       
   118       (protocol, host, path, parameters, query, fragment) = urlparse.urlparse(url)
       
   119 
       
   120       if host == '' and protocol == '':
       
   121         host = last_host
       
   122         protocol = last_protocol
       
   123 
       
   124       adjusted_headers = {
       
   125         'Content-Length': len(payload),
       
   126         'Host': host,
       
   127         'Accept': '*/*',
       
   128       }
       
   129       if method == 'POST' and payload:
       
   130         adjusted_headers['Content-Type'] = 'application/x-www-form-urlencoded'
       
   131 
       
   132       for header in headers:
       
   133         adjusted_headers[header.key().title()] = header.value()
       
   134 
       
   135       logging.debug('Making HTTP request: host = %s, '
       
   136                     'url = %s, payload = %s, headers = %s',
       
   137                     host, url, payload, adjusted_headers)
       
   138       try:
       
   139         if protocol == 'http':
       
   140           connection = httplib.HTTPConnection(host)
       
   141         elif protocol == 'https':
       
   142           connection = httplib.HTTPSConnection(host)
       
   143         else:
       
   144           error_msg = 'Redirect specified invalid protocol: "%s"' % protocol
       
   145           logging.error(error_msg)
       
   146           raise apiproxy_errors.ApplicationError(
       
   147               urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, error_msg)
       
   148 
       
   149         last_protocol = protocol
       
   150         last_host = host
       
   151 
       
   152         if query != '':
       
   153           full_path = path + '?' + query
       
   154         else:
       
   155           full_path = path
       
   156 
       
   157         try:
       
   158           connection.request(method, full_path, payload, adjusted_headers)
       
   159           http_response = connection.getresponse()
       
   160           http_response_data = http_response.read()
       
   161         finally:
       
   162           connection.close()
       
   163       except (httplib.error, socket.error, IOError), e:
       
   164         raise apiproxy_errors.ApplicationError(
       
   165           urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, str(e))
       
   166 
       
   167       if http_response.status in REDIRECT_STATUSES:
       
   168         url = http_response.getheader('Location', None)
       
   169         if url is None:
       
   170           error_msg = 'Redirecting response was missing "Location" header'
       
   171           logging.error(error_msg)
       
   172           raise apiproxy_errors.ApplicationError(
       
   173               urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, error_msg)
       
   174         else:
       
   175           method = 'GET'
       
   176       else:
       
   177         response.set_statuscode(http_response.status)
       
   178         response.set_content(http_response_data[:MAX_RESPONSE_SIZE])
       
   179         for header_key, header_value in http_response.getheaders():
       
   180           header_proto = response.add_header()
       
   181           header_proto.set_key(header_key)
       
   182           header_proto.set_value(header_value)
       
   183 
       
   184         if len(http_response_data) > MAX_RESPONSE_SIZE:
       
   185           response.set_contentwastruncated(True)
       
   186 
       
   187         break
       
   188     else:
       
   189       error_msg = 'Too many repeated redirects'
       
   190       logging.error(error_msg)
       
   191       raise apiproxy_errors.ApplicationError(
       
   192           urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, error_msg)