def prepare_discovery_request(self, response_body):
   self._response = test_utils.MockConnectionResponse(200, response_body)
   discovery = discovery_service.DiscoveryService(
       self.api_config_manager)
   discovery._discovery_proxy = self.mox.CreateMock(
       discovery_api_proxy.DiscoveryApiProxy)
   return discovery
Example #2
0
 def test_generate_discovery_doc_rest_unknown_api(self):
     request = test_utils.build_request('/_ah/api/foo',
                                        '{"api": "blah", "version": "v1"}')
     discovery_api = discovery_service.DiscoveryService(
         self.api_config_manager)
     discovery_api.handle_discovery_request(
         discovery_service.DiscoveryService._GET_REST_API, request,
         self.start_response)
     self.assertEquals(self.response_status, '404')
Example #3
0
    def call_spi(self, orig_request, start_response):
        """Generate SPI call (from earlier-saved request).

    This calls start_response and returns the response body.

    Args:
      orig_request: An ApiRequest, the original request from the user.
      start_response: A function with semantics defined in PEP-333.

    Returns:
      A string containing the response body.
    """
        if orig_request.is_rpc():
            method_config = self.lookup_rpc_method(orig_request)
            params = None
        else:
            method_config, params = self.lookup_rest_method(orig_request)
        if not method_config:
            cors_handler = EndpointsDispatcher.__CheckCorsHeaders(orig_request)
            return util.send_wsgi_not_found_response(start_response,
                                                     cors_handler=cors_handler)

        # Prepare the request for the back end.
        spi_request = self.transform_request(orig_request, params,
                                             method_config)

        # Check if this SPI call is for the Discovery service.  If so, route
        # it to our Discovery handler.
        discovery = discovery_service.DiscoveryService(self.config_manager)
        discovery_response = discovery.handle_discovery_request(
            spi_request.path, spi_request, start_response)
        if discovery_response:
            return discovery_response

        # Send the request to the user's SPI handlers.
        url = _SPI_ROOT_FORMAT % spi_request.path
        spi_request.headers['Content-Type'] = 'application/json'
        response = self._dispatcher.add_request('POST', url,
                                                spi_request.headers.items(),
                                                spi_request.body,
                                                spi_request.source_ip)
        return self.handle_spi_response(orig_request, spi_request, response,
                                        start_response)
Example #4
0
class EndpointsDispatcher(object):
  """Dispatcher that handles requests to the built-in apiserver handlers."""

  _API_EXPLORER_URL = 'https://developers.google.com/apis-explorer/?base='

  def __init__(self, dispatcher, config_manager=None):
    """Constructor for EndpointsDispatcher.

    Args:
      dispatcher: A Dispatcher instance that can be used to make HTTP requests.
      config_manager: An ApiConfigManager instance that allows a caller to
        set up an existing configuration for testing.
    """
    self._dispatcher = dispatcher
    if config_manager is None:
      config_manager = api_config_manager.ApiConfigManager()
    self.config_manager = config_manager
    self._dispatchers = []
    self._add_dispatcher('/_ah/api/explorer/?$',
                         self.handle_api_explorer_request)
    self._add_dispatcher('/_ah/api/static/.*$',
                         self.handle_api_static_request)

  def _add_dispatcher(self, path_regex, dispatch_function):
    """Add a request path and dispatch handler.

    Args:
      path_regex: A string regex, the path to match against incoming requests.
      dispatch_function: The function to call for these requests.  The function
        should take (request, start_response) as arguments and
        return the contents of the response body.
    """
    self._dispatchers.append((re.compile(path_regex), dispatch_function))

  def __call__(self, environ, start_response):
    """Handle an incoming request.

    Args:
      environ: An environ dict for the request as defined in PEP-333.
      start_response: A function used to begin the response to the caller.
        This follows the semantics defined in PEP-333.  In particular, it's
        called with (status, response_headers, exc_info=None), and it returns
        an object with a write(body_data) function that can be used to write
        the body of the response.

    Yields:
      An iterable over strings containing the body of the HTTP response.
    """
    request = api_request.ApiRequest(environ)

    # PEP-333 requires that we return an iterator that iterates over the
    # response body.  Yielding the returned body accomplishes this.
    yield self.dispatch(request, start_response)

  def dispatch(self, request, start_response):
    """Handles dispatch to apiserver handlers.

    This typically ends up calling start_response and returning the entire
      body of the response.

    Args:
      request: An ApiRequest, the request from the user.
      start_response: A function with semantics defined in PEP-333.

    Returns:
      A string, the body of the response.
    """
    # Check if this matches any of our special handlers.
    dispatched_response = self.dispatch_non_api_requests(request,
                                                         start_response)
    if dispatched_response is not None:
      return dispatched_response

    # Get API configuration first.  We need this so we know how to
    # call the back end.
    api_config_response = self.get_api_configs()
    if not self.handle_get_api_configs_response(api_config_response):
      return self.fail_request(request, 'BackendService.getApiConfigs Error',
                               start_response)

    # Call the service.
    return self.call_spi(request, start_response)

  def dispatch_non_api_requests(self, request, start_response):
    """Dispatch this request if this is a request to a reserved URL.

    If the request matches one of our reserved URLs, this calls
    start_response and returns the response body.

    Args:
      request: An ApiRequest, the request from the user.
      start_response: A function with semantics defined in PEP-333.

    Returns:
      None if the request doesn't match one of the reserved URLs this
      handles.  Otherwise, returns the response body.
    """
    for path_regex, dispatch_function in self._dispatchers:
      if path_regex.match(request.relative_url):
        return dispatch_function(request, start_response)
    return None

  def handle_api_explorer_request(self, request, start_response):
    """Handler for requests to _ah/api/explorer.

    This calls start_response and returns the response body.

    Args:
      request: An ApiRequest, the request from the user.
      start_response: A function with semantics defined in PEP-333.

    Returns:
      A string containing the response body (which is empty, in this case).
    """
    base_url = 'http://%s:%s/_ah/api' % (request.server, request.port)
    redirect_url = self._API_EXPLORER_URL + base_url
    return util.send_wsgi_redirect_response(redirect_url, start_response)

  def handle_api_static_request(self, request, start_response):
    """Handler for requests to _ah/api/static/.*.

    This calls start_response and returns the response body.

    Args:
      request: An ApiRequest, the request from the user.
      start_response: A function with semantics defined in PEP-333.

    Returns:
      A string containing the response body.
    """
    discovery_api = discovery_api_proxy.DiscoveryApiProxy()
    response, body = discovery_api.get_static_file(request.relative_url)
    status_string = '%d %s' % (response.status, response.reason)
    if response.status == 200:
      # Some of the headers that come back from the server can't be passed
      # along in our response.  Specifically, the response from the server has
      # transfer-encoding: chunked, which doesn't apply to the response that
      # we're forwarding.  There may be other problematic headers, so we strip
      # off everything but Content-Type.
      return util.send_wsgi_response(status_string,
                                     [('Content-Type',
                                       response.getheader('Content-Type'))],
                                     body, start_response)
    else:
      logging.error('Discovery API proxy failed on %s with %d. Details: %s',
                    request.relative_url, response.status, body)
      return util.send_wsgi_response(status_string, response.getheaders(), body,
                                     start_response)

  def get_api_configs(self):
    """Makes a call to the BackendService.getApiConfigs endpoint.

    Returns:
      A ResponseTuple containing the response information from the HTTP
      request.
    """
    headers = [('Content-Type', 'application/json')]
    request_body = '{}'
    response = self._dispatcher.add_request(
        'POST', '/_ah/spi/BackendService.getApiConfigs',
        headers, request_body, _SERVER_SOURCE_IP)
    return response

  @staticmethod
  def verify_response(response, status_code, content_type=None):
    """Verifies that a response has the expected status and content type.

    Args:
      response: The ResponseTuple to be checked.
      status_code: An int, the HTTP status code to be compared with response
        status.
      content_type: A string with the acceptable Content-Type header value.
        None allows any content type.

    Returns:
      True if both status_code and content_type match, else False.
    """
    status = int(response.status.split(' ', 1)[0])
    if status != status_code:
      return False
    if content_type is None:
      return True
    for header, value in response.headers:
      if header.lower() == 'content-type':
        return value == content_type
    else:
      return False

  def handle_get_api_configs_response(self, api_config_response):
    """Parses the result of GetApiConfigs and stores its information.

    Args:
      api_config_response: The ResponseTuple from the GetApiConfigs call.

    Returns:
      True on success, False on failure
    """
    if self.verify_response(api_config_response, 200, 'application/json'):
      self.config_manager.parse_api_config_response(
          api_config_response.content)
      return True
    else:
      return False

  def call_spi(self, orig_request, start_response):
    """Generate SPI call (from earlier-saved request).

    This calls start_response and returns the response body.

    Args:
      orig_request: An ApiRequest, the original request from the user.
      start_response: A function with semantics defined in PEP-333.

    Returns:
      A string containing the response body.
    """
    if orig_request.is_rpc():
      method_config = self.lookup_rpc_method(orig_request)
      params = None
    else:
      method_config, params = self.lookup_rest_method(orig_request)
    if not method_config:
      cors_handler = EndpointsDispatcher.__CheckCorsHeaders(orig_request)
      return util.send_wsgi_not_found_response(start_response,
                                               cors_handler=cors_handler)

    # Prepare the request for the back end.
    try:
      spi_request = self.transform_request(orig_request, params, method_config)
    except errors.RequestRejectionError, rejection_error:
      cors_handler = EndpointsDispatcher.__CheckCorsHeaders(orig_request)
      return util.send_wsgi_rejected_response(rejection_error, start_response,
                                              cors_handler=cors_handler)

    # Check if this SPI call is for the Discovery service.  If so, route
    # it to our Discovery handler.
    discovery = discovery_service.DiscoveryService(self.config_manager)
    discovery_response = discovery.handle_discovery_request(
        spi_request.path, spi_request, start_response)
    if discovery_response:
      return discovery_response

    # Send the request to the user's SPI handlers.
    url = _SPI_ROOT_FORMAT % spi_request.path
    spi_request.headers['Content-Type'] = 'application/json'
    response = self._dispatcher.add_request('POST', url,
                                            spi_request.headers.items(),
                                            spi_request.body,
                                            spi_request.source_ip)
    return self.handle_spi_response(orig_request, spi_request, response,
                                    start_response)